feat: add IMAP connection testing and website settings optimization

- Add dynamic IMAP connection testing for multiple account types (public, premium)
  - Implement testIMAPConnection method using ZEmail::connectMailBox for reliable testing
  - Add comprehensive error handling and user-friendly notifications
  - Support easy extension for future IMAP configurations (vip, etc.)
  - Add queued artisan command execution in WebsiteSettings (optimize, optimize:clear)
  - Enhance website settings with performance optimization controls
  - Add validation for IMAP extension availability and helpful error messages
This commit is contained in:
idevakk
2025-11-17 05:27:19 -08:00
parent cb05040c96
commit 7ac0a436b1
2 changed files with 285 additions and 0 deletions

View File

@@ -2,14 +2,18 @@
namespace App\Filament\Pages;
use App\Models\ZEmail;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Inerba\DbConfig\AbstractPageSettings;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
class ImapSettings extends AbstractPageSettings
{
@@ -48,6 +52,19 @@ class ImapSettings extends AbstractPageSettings
];
}
public function getHeaderActions(): array
{
return [
Action::make('test_connection')
->label('Test Connection')
->color('gray')
->icon('heroicon-o-paper-airplane')
->action(fn () => $this->testIMAPConnection()),
...parent::getHeaderActions(),
];
}
public function form(Schema $schema): Schema
{
return $schema
@@ -92,4 +109,216 @@ class ImapSettings extends AbstractPageSettings
])
->statePath('data');
}
private function testIMAPConnection(): void
{
$settings = $this->data;
$results = [];
$hasSuccess = false;
$hasFailure = false;
// Define IMAP configuration sections - easy to extend
$imapSections = ['public', 'premium'];
foreach ($imapSections as $sectionName) {
$sectionConfig = $this->getImapSectionConfig($settings, $sectionName);
$result = $this->testSingleImapConnection($sectionName, $sectionConfig);
$results[] = $result;
if ($result['success']) {
$hasSuccess = true;
} else {
$hasFailure = true;
}
}
// Send appropriate notification based on results
$this->sendImapTestNotification($results, $hasSuccess, $hasFailure);
}
/**
* Get IMAP section configuration for a specific parent key.
*/
private function getImapSectionConfig(array $settings, string $sectionName): array
{
$config = [];
$fields = ['host', 'port', 'username', 'password', 'encryption', 'validate_cert', 'protocol'];
foreach ($fields as $field) {
$key = $sectionName . '.' . $field;
// Try different data structure approaches
$value = null;
// 1. Direct key access (flat structure with dots)
if (isset($settings[$key])) {
$value = $settings[$key];
}
// 2. Nested structure access
elseif (isset($settings[$sectionName][$field])) {
$value = $settings[$sectionName][$field];
}
// 3. Alternative nested structure
elseif (isset($settings[$sectionName]) && is_array($settings[$sectionName])) {
$nested = $settings[$sectionName];
if (isset($nested[$field])) {
$value = $nested[$field];
}
}
$config[$field] = $value;
}
return $config;
}
/**
* Test a single IMAP connection configuration.
*/
private function testSingleImapConnection(string $sectionName, array $config): array
{
$requiredFields = ['host', 'port', 'username', 'password'];
// Check for missing required fields
$missingFields = collect($requiredFields)->filter(fn ($field): bool => empty($config[$field]));
if ($missingFields->isNotEmpty()) {
return [
'section' => ucfirst($sectionName),
'success' => false,
'message' => "Missing required fields: " . $missingFields->join(', '),
'details' => null
];
}
try {
// First check if IMAP extension is available
if (! function_exists('imap_open')) {
return [
'section' => ucfirst($sectionName),
'success' => false,
'message' => 'IMAP extension is not loaded in your web server. Please check your Herd PHP configuration or restart your server.',
'details' => null
];
}
// Build IMAP configuration array in the format ZEmail expects
$imapConfig = [
'host' => $config['host'],
'port' => (int) $config['port'],
'username' => $config['username'],
'password' => $config['password'],
'encryption' => $config['encryption'] ?? 'none',
'validate_cert' => $config['validate_cert'] ?? false,
'protocol' => $config['protocol'] ?? 'imap'
];
// Test connection using the existing ZEmail::connectMailBox method
ZEmail::connectMailBox($imapConfig);
return [
'section' => ucfirst($sectionName),
'success' => true,
'message' => 'Connection successful',
'details' => [
'host' => $config['host'],
'port' => $config['port'],
'encryption' => $config['encryption'] ?? 'none',
'protocol' => $config['protocol'] ?? 'imap'
]
];
} catch (\Exception $e) {
$errorMessage = $e->getMessage();
// Provide more helpful error messages
if (str_contains($errorMessage, 'IMAP extension must be enabled')) {
$errorMessage = 'IMAP extension is not properly loaded in the web server. Try restarting Herd or check your PHP configuration.';
}
return [
'section' => ucfirst($sectionName),
'success' => false,
'message' => $errorMessage,
'details' => [
'host' => $config['host'] ?? null,
'port' => $config['port'] ?? null,
'encryption' => $config['encryption'] ?? 'none',
'protocol' => $config['protocol'] ?? 'imap'
]
];
}
}
/**
* Send appropriate notification based on test results.
*/
private function sendImapTestNotification(array $results, bool $hasSuccess, bool $hasFailure): void
{
$totalTests = count($results);
$successCount = count(array_filter($results, fn ($r): bool => $r['success']));
if ($hasSuccess && ! $hasFailure) {
// All successful
Notification::make()
->title("All IMAP connections successful! ({$successCount}/{$totalTests})")
->success()
->body($this->formatSuccessNotification($results))
->send();
} elseif (! $hasSuccess && $hasFailure) {
// All failed
Notification::make()
->title("All IMAP connections failed (0/{$totalTests})")
->danger()
->body($this->formatFailureNotification($results))
->send();
} else {
// Mixed results
Notification::make()
->title("IMAP connection test completed ({$successCount}/{$totalTests} successful)")
->warning()
->body($this->formatMixedNotification($results))
->send();
}
}
/**
* Format success notification details.
*/
private function formatSuccessNotification(array $results): string
{
$details = [];
foreach ($results as $result) {
if ($result['success'] && isset($result['details']['messages'])) {
$details[] = "{$result['section']}: {$result['details']['messages']} messages";
}
}
return implode(' | ', $details);
}
/**
* Format failure notification details.
*/
private function formatFailureNotification(array $results): string
{
$details = [];
foreach ($results as $result) {
$details[] = "{$result['section']}: {$result['message']}";
}
return implode(' | ', $details);
}
/**
* Format mixed results notification details.
*/
private function formatMixedNotification(array $results): string
{
$details = [];
foreach ($results as $result) {
$status = $result['success'] ? '✅' : '❌';
$details[] = "{$status} {$result['section']}";
}
return implode(' | ', $details);
}
}