timeStart = microtime(true); $userId = $this->argument('user_id'); $force = $this->option('force'); $startId = $this->option('start'); $createMissing = $this->option('create-missing'); $debug = $this->option('debug'); if ($debug) { $this->warn("DEBUG-MODUS (DRY-RUN): Es werden keine tatsächlichen Änderungen vorgenommen!"); } $this->info("Starte Subdomain-Verwaltung" . ($force ? " (erzwungener Modus)" : "")); try { if ($userId) { $this->info("Verarbeite einzelnen Benutzer mit ID: {$userId}"); $result = $this->syncSingleUser($userId, $force, $createMissing, $debug); if ($result) { $this->info("Benutzer {$userId} erfolgreich synchronisiert" . ($debug ? " (simuliert)" : "")); } else { $this->warn("Benutzer {$userId} konnte nicht vollständig synchronisiert werden" . ($debug ? " (simuliert)" : "")); } } else { $this->info("Verarbeite alle Benutzer ab ID: {$startId}"); $result = $this->syncAllUsers($force, $startId, $createMissing, $debug); // Zusammenfassung der Ergebnisse $this->displaySummary($result, $debug); } $this->logExecutionTime("Subdomain-Verwaltung abgeschlossen" . ($debug ? " (DEBUG-MODUS)" : "")); return 0; } catch (Exception $e) { $this->error("Ein Fehler ist aufgetreten: " . $e->getMessage()); Log::error("Shopping User Sync Fehler: ", [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), 'user_id' => $userId, 'force' => $force, 'start_id' => $startId, 'debug' => $debug ]); return 1; } } /** * Zeigt eine Zusammenfassung der Synchronisationsergebnisse an * * @param array $result Ergebnisdaten der Synchronisation * @param bool $debug Debug-Modus (Dry-Run) * @return void */ private function displaySummary($result, $debug = false) { $this->line(""); $this->info("=== Zusammenfassung " . ($debug ? "(DEBUG-MODUS)" : "") . " ==="); $this->info("Verarbeitete Shops: " . count($result['shops'])); $this->info("Aktualisierte PHP-Versionen: " . $result['updatedCount'] . ($debug ? " (simuliert)" : "")); $this->info("Aktivierte SSL-Zertifikate: " . $result['sslEnabledCount'] . ($debug ? " (simuliert)" : "")); $this->info("Aktualisierte SSL-Konfigurationen: " . $result['sslConfiguredCount'] . ($debug ? " (simuliert)" : "")); if (!empty($result['createdSubdomains'])) { $this->info("Neu erstellte Subdomains: " . count($result['createdSubdomains']) . ($debug ? " (simuliert)" : "")); } if (!empty($result['missingSubdomains'])) { $this->warn("Fehlende Subdomains: " . count($result['missingSubdomains'])); } if (!empty($result['unusedSubdomains'])) { $this->warn("Ungenutzte Subdomains: " . count($result['unusedSubdomains'])); } if (!empty($result['doubleDomains'])) { $this->warn("Benutzer mit mehreren Shops: " . count($result['doubleDomains'])); } } /** * Synchronisiert einen einzelnen Benutzer und seine Shops * * @param int $userId Benutzer-ID * @param bool $force Erzwingt die Aktualisierung aller Subdomains * @param bool $createMissing Erstellt fehlende Subdomains * @param bool $debug Debug-Modus (Dry-Run) * @return bool Erfolg der Operation */ private function syncSingleUser($userId, $force = false, $createMissing = false, $debug = false) { $this->info("Synchronisiere Benutzer mit ID: {$userId}"); // Benutzer-Shops abrufen $userShops = UserShop::where('user_id', $userId)->get(); if ($userShops->isEmpty()) { $this->warn("Keine Shops für Benutzer {$userId} gefunden"); return false; } $this->info("Gefundene Shops für Benutzer {$userId}: " . $userShops->count()); // Subdomains abrufen und filtern $subdomains = $this->getFilteredSubdomains(); $success = true; // Benutzer-Shops durchlaufen und mit Subdomains abgleichen foreach ($userShops as $userShop) { $fullDomainName = $userShop->slug . '.' . $this->domain; $this->info("Verarbeite Shop: {$fullDomainName}"); // Prüfen, ob Subdomain existiert if (array_key_exists($fullDomainName, $subdomains)) { $success = $this->processExistingSubdomain($userShop, $subdomains[$fullDomainName], $force, $debug) && $success; } else { // Subdomain fehlt $this->warn("Shop {$userShop->slug}: Keine Subdomain gefunden"); // Optional: Neue Subdomain erstellen if ($createMissing) { $this->info("Erstelle fehlende Subdomain für Shop {$userShop->slug}" . ($debug ? " (simuliert)" : "")); $success = $this->createSubdomain($userShop->slug, $debug) && $success; } else { $success = false; } } } return $success; } /** * Verarbeitet eine existierende Subdomain und aktualisiert sie bei Bedarf * * @param UserShop $userShop Shop-Objekt * @param array $subdomainInfo Subdomain-Informationen * @param bool $force Erzwingt die Aktualisierung * @param bool $debug Debug-Modus (Dry-Run) * @return bool Erfolg der Operation */ private function processExistingSubdomain($userShop, $subdomainInfo, $force, $debug = false) { $success = true; $hasSSL = ($subdomainInfo['ssl_certificate_sni'] === 'Y' || $subdomainInfo['ssl_proxy'] === 'Y'); $sslActive = ($subdomainInfo['ssl_certificate_sni_is_active'] ?? 'N') === 'Y'; $phpVersion = $subdomainInfo['php_version']; $this->info("Shop {$userShop->slug}: PHP-Version: {$phpVersion}, SSL: " . ($hasSSL ? "Aktiviert" : "Nicht aktiviert") . ($hasSSL ? ", SSL aktiv: " . ($sslActive ? "Ja" : "Nein") : "")); // Prüfen, ob PHP-Version aktualisiert werden muss $requiredPhpVersion = config('app.php_version'); if ($force || $phpVersion !== $requiredPhpVersion) { $this->info("Shop {$userShop->slug}: PHP-Version aktualisieren von {$phpVersion} auf {$requiredPhpVersion}" . ($debug ? " (simuliert)" : "")); if (!$debug && !$this->updateSubdomainParams($userShop->slug, $requiredPhpVersion)) { $this->error("PHP-Version für {$userShop->slug}.{$this->domain} konnte nicht aktualisiert werden"); $success = false; } } // Prüfen, ob SSL aktiviert werden muss if ($force || !$hasSSL) { $this->info("Shop {$userShop->slug}: SSL aktivieren" . ($debug ? " (simuliert)" : "")); if (!$debug && !$this->enableSSL($userShop->slug)) { $this->error("SSL für {$userShop->slug}.{$this->domain} konnte nicht aktiviert werden"); $success = false; } } // Prüfen, ob SSL-Konfiguration aktualisiert werden muss else if ($force || ($hasSSL && !$sslActive)) { $this->info("Shop {$userShop->slug}: SSL-Konfiguration aktualisieren" . ($debug ? " (simuliert)" : "")); if (!$debug && !$this->updateSSL($userShop->slug . '.' . $this->domain)) { $this->error("SSL-Konfiguration für {$userShop->slug}.{$this->domain} konnte nicht aktualisiert werden"); $success = false; } } return $success; } /** * Synchronisiert alle Benutzer-Shops * * @param bool $force Erzwingt die Aktualisierung aller Subdomains * @param int $startId Beginnt die Synchronisation ab dieser ID * @param bool $createMissing Erstellt fehlende Subdomains * @param bool $debug Debug-Modus (Dry-Run) * @return array Ergebnisdaten der Synchronisation */ private function syncAllUsers($force, $startId, $createMissing = false, $debug = false) { $this->info("Starte Synchronisation aller Benutzer-Shops ab ID: {$startId}"); // Benutzer-Shops abrufen $userShopsQuery = UserShop::query(); if ($startId > 1) { $userShopsQuery->where('id', '>=', $startId); } $userShops = $userShopsQuery->limit(1000)->get(); $this->info("Gefundene Benutzer-Shops: " . $userShops->count()); // Subdomains abrufen und filtern $subdomains = $this->getFilteredSubdomains(); $this->info("Gefilterte Subdomains: " . count($subdomains)); // Ergebnis-Arrays initialisieren $doubleDomains = []; $missingSubdomains = []; $outdatedPhpVersions = []; $missingSSL = []; $sslConfigurationNeeded = []; $createdSubdomains = []; $updatedCount = 0; $sslEnabledCount = 0; $sslConfiguredCount = 0; // Benutzer-Shops durchlaufen und mit Subdomains abgleichen foreach ($userShops as $userShop) { $fullDomainName = $userShop->slug . '.' . $this->domain; // Status der Subdomain setzen $userShop->hasSubdomain = false; $userShop->hasSSL = false; $userShop->sslActive = false; $userShop->PHPversion = ""; // Prüfen, ob Subdomain existiert if (array_key_exists($fullDomainName, $subdomains)) { $userShop->hasSubdomain = true; $userShop->hasSSL = ($subdomains[$fullDomainName]['ssl_certificate_sni'] === 'Y' || $subdomains[$fullDomainName]['ssl_proxy'] === 'Y'); $userShop->sslActive = ($subdomains[$fullDomainName]['ssl_certificate_sni_is_active'] ?? 'N') === 'Y'; $userShop->PHPversion = $subdomains[$fullDomainName]['php_version']; // Prüfen, ob PHP-Version aktualisiert werden muss $requiredPhpVersion = config('app.php_version'); if ($force || $userShop->PHPversion !== $requiredPhpVersion) { $this->info("Shop {$userShop->slug}: PHP-Version aktualisieren von {$userShop->PHPversion} auf {$requiredPhpVersion}" . ($debug ? " (simuliert)" : "")); $outdatedPhpVersions[] = $userShop->slug; if (!$debug && $this->updateSubdomainParams($userShop->slug, $requiredPhpVersion)) { $updatedCount++; } else if ($debug) { // Im Debug-Modus zählen wir trotzdem, als ob es erfolgreich wäre $updatedCount++; } }else{ $this->info("Shop {$userShop->slug}: PHP-Version ist aktuell: {$userShop->PHPversion}"); } // Prüfen, ob SSL aktiviert werden muss /* if ($force || !$userShop->hasSSL) { $this->info("Shop {$userShop->slug}: SSL aktivieren" . ($debug ? " (simuliert)" : "")); $missingSSL[] = $userShop->slug; if (!$debug && $this->enableSSL($userShop->slug)) { $sslEnabledCount++; } else if ($debug) { // Im Debug-Modus zählen wir trotzdem, als ob es erfolgreich wäre $sslEnabledCount++; } } // Prüfen, ob SSL-Konfiguration aktualisiert werden muss else if ($force || ($userShop->hasSSL && !$userShop->sslActive)) { $this->info("Shop {$userShop->slug}: SSL-Konfiguration aktualisieren" . ($debug ? " (simuliert)" : "")); $sslConfigurationNeeded[] = $userShop->slug; if (!$debug && $this->updateSSL($fullDomainName)) { $sslConfiguredCount++; } else if ($debug) { // Im Debug-Modus zählen wir trotzdem, als ob es erfolgreich wäre $sslConfiguredCount++; } }*/ // Subdomain aus der Liste entfernen, um später ungenutzte zu identifizieren unset($subdomains[$fullDomainName]); } else { // Subdomain fehlt $missingSubdomains[] = $userShop->slug; $this->warn("Shop {$userShop->slug}: Keine Subdomain gefunden"); // Optional: Neue Subdomain erstellen if ($createMissing) { $this->info("Erstelle fehlende Subdomain für Shop {$userShop->slug}" . ($debug ? " (simuliert)" : "")); if (!$debug && $this->createSubdomain($userShop->slug)) { $createdSubdomains[] = $userShop->slug; } else if ($debug) { // Im Debug-Modus zählen wir trotzdem, als ob es erfolgreich wäre $createdSubdomains[] = $userShop->slug; } } } // Doppelte Domains pro Benutzer erfassen $doubleDomains[$userShop->user_id][$userShop->id] = $fullDomainName; } // Bereinigen der doppelten Domains (nur Benutzer mit mehreren Shops behalten) foreach ($doubleDomains as $userId => $domains) { if (count($domains) === 1) { unset($doubleDomains[$userId]); } } $this->logExecutionTime("Synchronisation abgeschlossen" . ($debug ? " (DEBUG-MODUS)" : "")); // Ergebnisdaten zurückgeben return [ 'shops' => $userShops, 'unusedSubdomains' => $subdomains, 'doubleDomains' => $doubleDomains, 'missingSubdomains' => $missingSubdomains, 'outdatedPhpVersions' => $outdatedPhpVersions, 'missingSSL' => $missingSSL, 'sslConfigurationNeeded' => $sslConfigurationNeeded, 'createdSubdomains' => $createdSubdomains, 'updatedCount' => $updatedCount, 'sslEnabledCount' => $sslEnabledCount, 'sslConfiguredCount' => $sslConfiguredCount ]; } /** * Ruft alle Subdomains ab und filtert sie * * @return array Gefilterte Subdomains */ private function getFilteredSubdomains() { $kas = new KasController(); $subdomains = []; // Alle Subdomains abrufen $this->info("Rufe Subdomains von KAS ab..."); $getSubdomains = $kas->action('get_subdomains'); // Subdomains filtern und in ein leicht zugängliches Array umwandeln foreach ($getSubdomains as $subdomain) { if (!isset($subdomain['subdomain_name'])) { continue; } // Spezielle Subdomains überspringen $skip = false; foreach ($this->skipPrefixes as $prefix) { if (strpos($subdomain['subdomain_name'], $prefix) !== false) { $skip = true; break; } } if ($skip) { continue; } // Subdomain-Informationen speichern $subdomains[$subdomain['subdomain_name']] = [ 'ssl_certificate_sni' => $subdomain['ssl_certificate_sni'] ?? 'N', 'php_version' => $subdomain['php_version'] ?? '', 'ssl_proxy' => $subdomain['ssl_proxy'] ?? 'N', 'ssl_certificate_sni_is_active' => $subdomain['ssl_certificate_sni_is_active'] ?? 'N', ]; } return $subdomains; } /** * Ändert Parameter einer Subdomain, insbesondere die PHP-Version * * @param string $subdomain Name der Subdomain ohne Domain * @param string $phpVersion Neue PHP-Version (z.B. '8.2') * @param array $additionalParams Weitere zu ändernde Parameter * @param bool $debug Debug-Modus (Dry-Run) * @return bool Erfolg der Operation */ private function updateSubdomainParams($subdomain, $phpVersion, $additionalParams = [], $debug = false) { $this->info("Aktualisiere Parameter für: {$subdomain}.{$this->domain}" . ($debug ? " (simuliert)" : "")); if ($debug) { $this->line(" - PHP-Version: {$phpVersion}"); if (!empty($additionalParams)) { $this->line(" - Zusätzliche Parameter: " . json_encode($additionalParams)); } return true; } try { $kas = new KasController(); // Standardparameter $params = [ 'subdomain_name' => $subdomain . '.' . $this->domain, // Vollständigen Domainnamen verwenden 'php_version' => $phpVersion ]; // Oder alternativ, falls die API die Subdomain und Domain getrennt erwartet: // $params = [ // 'subdomain_name' => $subdomain, // 'domain_name' => $this->domain, // 'php_version' => $phpVersion // ]; // Zusätzliche Parameter hinzufügen $params = array_merge($params, $additionalParams); // Subdomain aktualisieren $result = $kas->action('update_subdomain', $params); $this->info("Parameter: ".json_encode($params)); if ($result) { $this->info("Parameter für {$subdomain}.{$this->domain} erfolgreich aktualisiert " . json_encode($result)); return true; } else { $this->error("Fehler beim Aktualisieren der Parameter für {$subdomain}.{$this->domain}: " . json_encode($result)); return false; } } catch (Exception $e) { $this->error("Fehler beim Aktualisieren der Parameter für {$subdomain}.{$this->domain}: " . $e->getMessage()); Log::error("Subdomain Parameter Update Fehler", [ 'subdomain' => $subdomain, 'domain' => $this->domain, 'php_version' => $phpVersion, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return false; } } /** * Aktualisiert die SSL-Konfiguration einer Subdomain mit erweiterten Parametern * * @param string $subdomainName Vollständiger Domainname (z.B. 'shop.mivita.care') * @param bool $debug Debug-Modus (Dry-Run) * @return bool Erfolg der Operation */ private function updateSSL($subdomainName, $debug = false) { $this->info("Aktualisiere SSL-Konfiguration für: {$subdomainName}" . ($debug ? " (simuliert)" : "")); if ($debug) { $this->line(" - SSL-Parameter werden aktualisiert"); return true; } try { $kas = new KasController(); $ssl = KasSLLController::getApiSSLParameter(); $params = array_merge(['hostname' => $subdomainName], $ssl); $result = $kas->action('update_ssl', $params); if ($result === "TRUE" || $result === true) { $this->info("SSL-Konfiguration für {$subdomainName} erfolgreich aktualisiert"); return true; } else { $this->warn("SSL-Konfiguration für {$subdomainName} nicht vollständig aktualisiert: " . json_encode($result)); return false; } } catch (Exception $e) { $this->error("Fehler bei der SSL-Konfiguration für {$subdomainName}: " . $e->getMessage()); Log::error("SSL Update Fehler", [ 'subdomain' => $subdomainName, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return false; } } /** * Aktiviert SSL für eine Subdomain mit vollständiger Konfiguration * * @param string $subdomain Name der Subdomain ohne Domain * @param bool $debug Debug-Modus (Dry-Run) * @return bool Erfolg der Operation */ private function enableSSL($subdomain, $debug = false) { $fullDomainName = $subdomain . '.' . $this->domain; $this->info("Aktiviere SSL für: {$fullDomainName}" . ($debug ? " (simuliert)" : "")); if ($debug) { $this->line(" - SSL-Proxy wird aktiviert"); $this->line(" - HTTP zu HTTPS Weiterleitung wird eingerichtet"); $this->line(" - SSL-Konfiguration wird aktualisiert"); return true; } // Schritt 1: Subdomain-Parameter aktualisieren (ssl_proxy aktivieren) $subdomainResult = $this->updateSubdomainParams($subdomain, config('app.php_version'), [ 'ssl_proxy' => 'Y', 'redirect_status' => 301 // Weiterleitung von HTTP auf HTTPS ]); if (!$subdomainResult) { $this->error("SSL-Aktivierung für {$fullDomainName} fehlgeschlagen: Subdomain-Parameter konnten nicht aktualisiert werden"); return false; } // Schritt 2: SSL-Konfiguration aktualisieren $sslResult = $this->updateSSL($fullDomainName); if (!$sslResult) { $this->warn("SSL-Aktivierung für {$fullDomainName} teilweise erfolgreich: SSL-Konfiguration konnte nicht aktualisiert werden"); return false; } $this->info("SSL für {$fullDomainName} vollständig aktiviert"); return true; } /** * Erstellt eine neue Subdomain für einen Shop * * @param string $slug Shop-Slug * @param bool $debug Debug-Modus (Dry-Run) * @return bool Erfolg der Operation */ private function createSubdomain($slug, $debug = false) { $fullDomainName = $slug . '.' . $this->domain; $this->info("Erstelle neue Subdomain: {$fullDomainName}" . ($debug ? " (simuliert)" : "")); if ($debug) { $this->line(" - Pfad: /mein.mivita.care/public/"); $this->line(" - PHP-Version: " . config('app.php_version')); $this->line(" - SSL wird direkt aktiviert"); return true; } try { $kas = new KasController(); // Parameter für die neue Subdomain $params = [ 'subdomain_name' => $slug, 'domain_name' => $this->domain, 'subdomain_path' => '/mein.mivita.care/public/', 'php_version' => config('app.php_version'), ]; // Subdomain erstellen $result = $kas->action('add_subdomain', $params); if ($result === $fullDomainName) { $this->info("Subdomain {$fullDomainName} erfolgreich erstellt"); // SSL direkt aktivieren $this->enableSSL($slug); return true; } else { $this->error("Fehler beim Erstellen der Subdomain {$fullDomainName}: " . json_encode($result)); return false; } } catch (Exception $e) { $this->error("Fehler beim Erstellen der Subdomain {$fullDomainName}: " . $e->getMessage()); Log::error("Subdomain Erstellung Fehler", [ 'slug' => $slug, 'domain' => $this->domain, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return false; } } /** * Protokolliert die Ausführungszeit einer Operation * * @param string $message Nachricht für die Protokollierung * @return void */ private function logExecutionTime($message) { $diff = microtime(true) - $this->timeStart; $sec = intval($diff); $micro = $diff - $sec; $this->info($message. ' | Time: '.$sec. 'sec :' . round($micro * 1000, 4) . " ms"); } }