From 54f056ca5f0b6ae9ca6b775ea00ade6aa7bd1c7a Mon Sep 17 00:00:00 2001 From: Nebulae Date: Mon, 29 Dec 2025 18:23:32 +0100 Subject: [PATCH] wip(ISP config connections and command) --- .env.example | 22 ++- .../Commands/SyncISPConfigMembersCommand.php | 112 ++++++++++++++ .../Memberships/Schemas/MembershipForm.php | 2 +- .../ISPConfig/ISPConfigMailService.php | 90 ++++++++++++ app/Services/ISPConfig/ISPConfigService.php | 139 ++++++++++++++++++ .../ISPConfig/ISPConfigWebService.php | 19 +++ app/Services/ISPConfigService.php | 89 ----------- app/Services/MemberService.php | 8 +- config/services.php | 35 +++-- ...540_add_ispconfig_ids_to_members_table.php | 31 ++++ package-lock.json | 2 +- routes/web.php | 21 +++ 12 files changed, 455 insertions(+), 115 deletions(-) create mode 100644 app/Console/Commands/SyncISPConfigMembersCommand.php create mode 100644 app/Services/ISPConfig/ISPConfigMailService.php create mode 100644 app/Services/ISPConfig/ISPConfigService.php create mode 100644 app/Services/ISPConfig/ISPConfigWebService.php delete mode 100644 app/Services/ISPConfigService.php create mode 100644 database/migrations/2025_12_29_150540_add_ispconfig_ids_to_members_table.php diff --git a/.env.example b/.env.example index c62877b..ddb024a 100644 --- a/.env.example +++ b/.env.example @@ -68,13 +68,23 @@ DOLIBARR_URL= LIBARR_USERNAME= DOLIBARR_PWD= -MAIL_ISPAPI_URL= -MAIL_ISPAPI_USERNAME= -MAIL_ISPAPI_PWD= +# ISPConfig Mail Server +ISPCONFIG_MAIL_SOAP_LOCATION= +ISPCONFIG_MAIL_SOAP_URI= +ISPCONFIG_MAIL_USERNAME= +ISPCONFIG_MAIL_PASSWORD= -HOSTING_ISPAPI_URL= -HOSTING_ISPAPI_USERNAME= -HOSTING_ISPAPI_PWD= +# ISPConfig Web Server +ISPCONFIG_WEB_SOAP_LOCATION= +ISPCONFIG_WEB_SOAP_URI= +ISPCONFIG_WEB_USERNAME= +ISPCONFIG_WEB_PASSWORD= + +# ISPConfig Test server +ISPCONFIG_TEST_SOAP_LOCATION= +ISPCONFIG_TEST_SOAP_URI= +ISPCONFIG_TEST_USERNAME= +ISPCONFIG_TEST_PASSWORD= #NEXTCLOUD_API_ID= #NEXCLOUD_API_PWD= diff --git a/app/Console/Commands/SyncISPConfigMembersCommand.php b/app/Console/Commands/SyncISPConfigMembersCommand.php new file mode 100644 index 0000000..0afe3c3 --- /dev/null +++ b/app/Console/Commands/SyncISPConfigMembersCommand.php @@ -0,0 +1,112 @@ +info('Début de la synchronisation ISPConfig Mail'); + + $ispConfigMailService = new ISPConfigMailService(); + + // TESTS + /*$ispConfigWebService = new ISPConfigWebService(); + + $webClients = []; + + $allWebClients = collect($ispConfigWebService->getAllClients()); + + foreach ($allWebClients as $wclientId) + { + $webClients[] = $ispConfigWebService->getClientData($wclientId); + } + + dd($webClients); + + $allClients = collect($ispConfigMailService->getAllClients()); + + $clients = []; + + foreach ($allClients as $clientId) { + $clients[] = $ispConfigMailService->getClientData($clientId); + } + + dd($clients);*/ + + // Récupération de tous les utilisateurs mail ISPConfig + $mailUsers = collect($ispConfigMailService->getAllMailUsers()); + + dd($mailUsers); + // Construction d'une map email => mailuser_id (!= client_id car indispo via API ISP) + $emailToClientId = $mailUsers + ->filter(fn ($user) => isset($user['email'], $user['mailuser_id'])) + ->mapWithKeys(fn ($user) => [ + strtolower($user['email']) => (int) $user['mailuser_id'] + ]); + + dd($emailToClientId); + + // Tableau des changements + $membersAddedOrUpdated = []; + + // Parcours des members + Member::query() + ->whereNotNull('email') + ->chunk(100, function ($members) use ($emailToClientId) { + foreach ($members as $member) { + + // Emails séparés par ; + $emails = array_map('trim', explode(';', $member->email)); + + // On récupère uniquement l'email @retzien.fr + $retzienEmail = collect($emails) + ->first(fn ($email) => str_ends_with(strtolower($email), '@retzien.fr')); + + if (!$retzienEmail) { + continue; + } + + $retzienEmail = strtolower($retzienEmail); + + // Recherche du client ISPConfig correspondant + $clientId = $emailToClientId->get($retzienEmail); + + if (!$clientId) { + $this->warn("Client ISPConfig non trouvé pour {$retzienEmail}"); + continue; + } + + // Mise à jour si nécessaire + if ($member->ispconfig_mail_client_id !== $clientId) { + + // Debug => Ajout au tableau des modifs + $membersAddedOrUpdated[] = [ + 'member_id' => $member->id, + 'isp_id' => $clientId + ]; + + //$member->update([ + //'ispconfig_mail_client_id' => $clientId, + //]); + + //$this->info("Member {$member->id} synchronisé → client ISPConfig {$clientId}"); + } + } + }); + + // Debug + dd($membersAddedOrUpdated); + + $this->info('Synchronisation ISPConfig Mail terminée'); + } +} diff --git a/app/Filament/Resources/Memberships/Schemas/MembershipForm.php b/app/Filament/Resources/Memberships/Schemas/MembershipForm.php index e19d2ee..925c19c 100644 --- a/app/Filament/Resources/Memberships/Schemas/MembershipForm.php +++ b/app/Filament/Resources/Memberships/Schemas/MembershipForm.php @@ -9,7 +9,7 @@ use Filament\Forms\Components\CheckboxList; use Filament\Forms\Components\DatePicker; use Filament\Forms\Components\Select; use Filament\Forms\Components\TextInput; -use Filament\Forms\Components\Toggle; +use Filament\Infolists\Infolist; use Filament\Infolists\Components\TextEntry; use Filament\Schemas\Components\Grid; use Filament\Schemas\Components\Section; diff --git a/app/Services/ISPConfig/ISPConfigMailService.php b/app/Services/ISPConfig/ISPConfigMailService.php new file mode 100644 index 0000000..16694c1 --- /dev/null +++ b/app/Services/ISPConfig/ISPConfigMailService.php @@ -0,0 +1,90 @@ + $this->call('mail_domain_get', ['primary_id' => -1]) + ); + } + + /** + * Récupère tous les utilisateurs mail + */ + public function getAllMailUsers(): array + { + return Cache::remember( + "ispconfig.mail.users.all", + config('services.ispconfig.cache_ttl'), + fn() => $this->call('mail_user_get', ['primary_id' => -1]) + ); + } + + /** + * Récupère les domaines mail d'un client ISPConfig + */ + public function getMailDomainsForClient(int $ispConfigClientId): Collection + { + $allDomains = $this->getAllMailDomains(); + + return collect($allDomains)->filter(function ($domain) use ($ispConfigClientId) { + return isset($domain['sys_groupid']) && $domain['sys_groupid'] == $ispConfigClientId; + }); + } + + /** + * Récupère les boîtes mail pour un domaine + */ + public function getMailUsersForDomain(string $domain): Collection + { + $allUsers = $this->getAllMailUsers(); + + return collect($allUsers)->filter(function ($user) use ($domain) { + return str_ends_with($user['email'], '@' . $domain); + }); + } + + /** + * Récupère les détails d'une boîte mail + */ + public function getMailUserDetails(string $email): ?array + { + $allUsers = $this->getAllMailUsers(); + + $user = collect($allUsers)->firstWhere('email', $email); + + if (!$user) { + return null; + } + + return [ + 'email' => $user['email'], + 'name' => $user['name'] ?? '', + 'quota' => (int) ($user['quota'] ?? 0), + 'usage' => (int) ($user['maildir_usage'] ?? 0), + 'usage_mb' => round(($user['maildir_usage'] ?? 0) / 1024 / 1024, 2), + 'active' => $user['postfix'] === 'y', + 'imap_enabled' => $user['disableimap'] === 'n', + 'pop3_enabled' => $user['disablepop3'] === 'n', + 'smtp_enabled' => $user['disablesmtp'] === 'n', + 'autoresponder' => $user['autoresponder'] === 'y', + 'spam_filter' => $user['move_junk'] === 'y', + ]; + } +} diff --git a/app/Services/ISPConfig/ISPConfigService.php b/app/Services/ISPConfig/ISPConfigService.php new file mode 100644 index 0000000..a0f7720 --- /dev/null +++ b/app/Services/ISPConfig/ISPConfigService.php @@ -0,0 +1,139 @@ +serverType = $serverType; + $this->config = config("services.ispconfig.servers.{$serverType}"); + + if (!$this->config) { + throw new Exception("ISPConfig server configuration not found for: {$serverType}"); + } + } + + protected function connect(): void + { + if ($this->client !== null && $this->sessionId !== null) { + return; + } + + try { + $this->client = new SoapClient(null, [ + 'location' => $this->config['soap_location'], + 'uri' => $this->config['soap_uri'], + 'trace' => 1, + 'exceptions' => 1, + 'stream_context' => stream_context_create([ + 'ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + ] + ]) + ]); + + $this->sessionId = $this->client->login( + $this->config['username'], + $this->config['password'] + ); + + Log::info("ISPConfig connected", [ + 'server' => $this->serverType, + 'session_id' => $this->sessionId + ]); + + } catch (SoapFault $e) { + Log::error("ISPConfig connection failed", [ + 'server' => $this->serverType, + 'error' => $e->getMessage() + ]); + throw new Exception("Failed to connect to ISPConfig: " . $e->getMessage()); + } + } + + public function disconnect(): void + { + if ($this->client && $this->sessionId) { + try { + $this->client->logout($this->sessionId); + Log::info("ISPConfig disconnected", ['server' => $this->serverType]); + } catch (SoapFault $e) { + Log::warning("ISPConfig logout failed", [ + 'server' => $this->serverType, + 'error' => $e->getMessage() + ]); + } + + $this->sessionId = null; + $this->client = null; + } + } + + /** + * @throws Exception + */ + protected function call(string $method, array $params = []): mixed + { + $this->connect(); + + try { + array_unshift($params, $this->sessionId); + + $result = $this->client->__soapCall($method, $params); + + Log::debug("ISPConfig API call", [ + 'method' => $method, + 'server' => $this->serverType, + 'success' => true + ]); + + return $result; + + } catch (SoapFault $e) { + Log::error("ISPConfig API call failed", [ + 'method' => $method, + 'server' => $this->serverType, + 'error' => $e->getMessage() + ]); + throw new Exception("ISPConfig API call failed: " . $e->getMessage()); + } + } + + public function __destruct() + { + $this->disconnect(); + } + + /** + * Récupère tous les clients + */ + public function getAllClients(): array + { + return Cache::remember( + "ispconfig.mail.clients.all", + config('services.ispconfig.cache_ttl'), + fn() => $this->call('client_get_all') + ); + } + + public function getClientData(string $clientId): array + { + return $this->call('client_get', ['client_id' => $clientId]); + } +} diff --git a/app/Services/ISPConfig/ISPConfigWebService.php b/app/Services/ISPConfig/ISPConfigWebService.php new file mode 100644 index 0000000..b363c91 --- /dev/null +++ b/app/Services/ISPConfig/ISPConfigWebService.php @@ -0,0 +1,19 @@ +call('sites_web_domain_get', [['primary_id' => -1]]); + } +} diff --git a/app/Services/ISPConfigService.php b/app/Services/ISPConfigService.php deleted file mode 100644 index 42599f6..0000000 --- a/app/Services/ISPConfigService.php +++ /dev/null @@ -1,89 +0,0 @@ -client = new SoapClient(null, [ - 'location' => config('services.ispconfig' . $type . '.base_url'), - 'trace' => true, - 'exceptions' => true, - 'stream_context' => stream_context_create([ - 'ssl' => [ - 'verify_peer' => false, - 'verify_peer_name' => false, - 'allow_self_signed' => true, - ], - ]), - ]); - - $this->sessionId = $this->client->login($username, $password); - } catch (SoapFault $e) { - throw new Exception("An error occurred : " . $e->getMessage()); - } - } - - /** - * Get all clients - */ - public function getAllClients(string $username = null, string $password = null): array - { - if (!$this->sessionId) { - $this->connect($username, $password); - } - - try { - $clientIds = $this->client->client_get_all($this->sessionId); - $clients = []; - - foreach ($clientIds as $id) { - $details = $this->client->client_get($this->sessionId, (int)$id); - if (!empty($details)) { - $clients[] = (array) $details; - } - } - - return $clients; - } catch (SoapFault $e) { - throw new Exception("An error occurred : " . $e->getMessage()); - } finally { - $this->disconnect(); - } - } - - /** - * Logout - */ - public function disconnect(): void - { - if ($this->client && $this->sessionId) { - try { - $this->client->logout($this->sessionId); - } catch (SoapFault $e) { - Log::info('ISP Config logout succeeded'); - } - } - } -} diff --git a/app/Services/MemberService.php b/app/Services/MemberService.php index fd8780a..a118a83 100644 --- a/app/Services/MemberService.php +++ b/app/Services/MemberService.php @@ -6,21 +6,15 @@ use App\Events\MemberRegistered; use App\Models\Member; use App\Models\MemberGroup; use App\Models\Package; -use Carbon\Carbon; class MemberService { - public function __construct() - { - // No repositories used in this project - } - public function registerNewMember(array $data): Member { // Check if the member already exists $member = Member::where('email', $data['email'])->first(); - if (! $member) { + if (!$member) { // Create a new member $member = new Member(); $member->status = 'pending'; diff --git a/config/services.php b/config/services.php index 089882e..a4acb06 100644 --- a/config/services.php +++ b/config/services.php @@ -41,18 +41,31 @@ return [ 'password' => env('DOLIBARR_PWD'), 'api_key' => env('DOLIBARR_APIKEY') ], - 'ispconfig' => [ - 'hosting' => [ - 'base_url' => env('HOSTING_ISPAPI_URL'), - 'username' => env('HOSTING_ISPAPI_USERNAME'), - 'password' => env('HOSTING_ISPAPI_PWD'), + 'servers' => [ + 'mail_server' => [ + 'name' => 'ISP Config Mail', + 'soap_location' => env('ISPCONFIG_MAIL_SOAP_LOCATION'), + 'soap_uri' => env('ISPCONFIG_MAIL_SOAP_URI'), + 'username' => env('ISPCONFIG_MAIL_USERNAME'), + 'password' => env('ISPCONFIG_MAIL_PASSWORD'), + ], + 'web_server' => [ + 'name' => 'ISP Config Web', + 'soap_location' => env('ISPCONFIG_WEB_SOAP_LOCATION'), + 'soap_uri' => env('ISPCONFIG_WEB_SOAP_URI'), + 'username' => env('ISPCONFIG_WEB_USERNAME'), + 'password' => env('ISPCONFIG_WEB_PASSWORD'), + ], + 'test_server' => [ + 'name' => 'ISP Config TEST', + 'soap_location' => env('ISPCONFIG_TEST_SOAP_LOCATION'), + 'soap_uri' => env('ISPCONFIG_TEST_SOAP_URI'), + 'username' => env('ISPCONFIG_TEST_USERNAME'), + 'password' => env('ISPCONFIG_TEST_PASSWORD'), + ], ], - 'mailbox' => [ - 'base_url' => env('MAIL_ISPAPI_URL'), - 'username' => env('MAIL_ISPAPI_USERNAME'), - 'password' => env('MAIL_ISPAPI_PWD'), - ] - ] + 'cache_ttl' => env('ISPCONFIG_CACHE_TTL', 300), // 5 minutes + ], ]; diff --git a/database/migrations/2025_12_29_150540_add_ispconfig_ids_to_members_table.php b/database/migrations/2025_12_29_150540_add_ispconfig_ids_to_members_table.php new file mode 100644 index 0000000..b93cfca --- /dev/null +++ b/database/migrations/2025_12_29_150540_add_ispconfig_ids_to_members_table.php @@ -0,0 +1,31 @@ +string('ispconfig_mail_client_id')->after('dolibarr_id')->nullable(); + $table->string('ispconfig_web_client_id')->after('ispconfig_mail_client_id')->nullable(); + + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('members', function (Blueprint $table) { + $table->dropColumn('ispconfig_mail_client_id'); + $table->dropColumn('ispconfig_web_client_id'); + }); + } +}; diff --git a/package-lock.json b/package-lock.json index 0ac9071..577899f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "roxane", + "name": "app", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/routes/web.php b/routes/web.php index 2745936..4d8f7d9 100644 --- a/routes/web.php +++ b/routes/web.php @@ -17,6 +17,27 @@ Route::middleware(['auth', 'verified'])->group(function () { })->name('dashboard'); }); +// TEST + +Route::get('/test/sync-ispconfig', function () { + + if (!app()->isLocal()) { + abort(403); + } + + Artisan::call('sync:ispconfig-members'); + + return response()->json([ + 'status' => 'ok', + 'output' => Artisan::output(), + ]); +}); + +Route::get('/test/isp-mails', function() { + $ispService = new \App\Services\ISPConfig\ISPConfigMailService; + + return $ispService->getAllMailDomains(); +}); require __DIR__.'/settings.php'; require __DIR__.'/auth.php';