- Add highly optimized Dockerfile with Nginx and PHP-FPM 8.4 - Add docker-compose.yml configured with Redis and MariaDB 10.11 - Implement entrypoint.sh and supervisord.conf for background workers - Refactor legacy IMAP scripts into scheduled Artisan Commands - Secure app by removing old routes with hardcoded basic auth credentials - Configure email attachments to use Laravel Storage instead of insecure public/tmp
326 lines
12 KiB
PHP
326 lines
12 KiB
PHP
<?php
|
|
|
|
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;
|
|
|
|
class ImapSettings extends AbstractPageSettings
|
|
{
|
|
/**
|
|
* @var array<string, mixed> | null
|
|
*/
|
|
public ?array $data = [];
|
|
|
|
protected static ?string $title = 'Imap Settings';
|
|
|
|
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedEnvelope; // Uncomment if you want to set a custom navigation icon
|
|
|
|
// protected ?string $subheading = ''; // Uncomment if you want to set a custom subheading
|
|
|
|
// protected static ?string $slug = 'imap-settings'; // Uncomment if you want to set a custom slug
|
|
|
|
protected string $view = 'filament.pages.imap-settings';
|
|
|
|
protected function settingName(): string
|
|
{
|
|
return 'imap';
|
|
}
|
|
|
|
/**
|
|
* Provide default values.
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function getDefaultData(): array
|
|
{
|
|
return [
|
|
'public.default_account' => 'default',
|
|
'public.protocol' => 'imap',
|
|
'premium.default_email' => 'default',
|
|
'premium.protocol' => 'imap',
|
|
];
|
|
}
|
|
|
|
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
|
|
->components([
|
|
Section::make('Public Mailbox Imap')
|
|
->description('Enter your imap server')
|
|
->collapsible()
|
|
->schema([
|
|
TextInput::make('public.host')->label('Hostname')->required(),
|
|
TextInput::make('public.port')->label('Port')->required(),
|
|
Select::make('public.encryption')->options([
|
|
'none' => 'None',
|
|
'ssl' => 'SSL',
|
|
'tls' => 'TLS',
|
|
]),
|
|
Checkbox::make('public.validate_cert')->label('Validate Encryption Certificate')->default(false),
|
|
TextInput::make('public.username')->label('Username')->required(),
|
|
TextInput::make('public.password')->label('Password')->required(),
|
|
TextInput::make('public.default_account')->label('Default Account')->placeholder('default'),
|
|
TextInput::make('public.protocol')->label('Protocol')->placeholder('imap'),
|
|
Checkbox::make('public.cc_check')->label('Check CC Field')->default(false)->helperText('If enabled, we will check the CC field as well while fetching mails.'),
|
|
]),
|
|
|
|
Section::make('Premium Mailbox Imap')
|
|
->description('Enter your imap server')
|
|
->collapsed()
|
|
->schema([
|
|
TextInput::make('premium.host')->label('Hostname')->required(),
|
|
TextInput::make('premium.port')->label('Port')->required(),
|
|
Select::make('premium.encryption')->options([
|
|
'none' => 'None',
|
|
'ssl' => 'SSL',
|
|
'tls' => 'TLS',
|
|
]),
|
|
Checkbox::make('premium.validate_cert')->label('Validate Encryption Certificate')->default(false),
|
|
TextInput::make('premium.username')->label('Username')->required(),
|
|
TextInput::make('premium.password')->label('Password')->required(),
|
|
TextInput::make('premium.default_account')->label('Default Account')->placeholder('default'),
|
|
TextInput::make('premium.protocol')->label('Protocol')->placeholder('imap'),
|
|
Checkbox::make('premium.cc_check')->label('Check CC Field')->default(false)->helperText('If enabled, we will check the CC field as well while fetching mails.'),
|
|
]),
|
|
])
|
|
->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);
|
|
}
|
|
}
|