getConfig(); // 1. Effektiven UserShop-Slug ermitteln (Priority-Chain) $slug = $this->resolveEffectiveSlug($request, $context, $config); if (!$slug) { // Kein UserShop → Session bereinigen $this->clearUserShopSession($config); return; } // 2. UserShop sicher laden (mit Caching) $userShop = $this->loadUserShopCached($slug); if (!$userShop) { // Ungültiger Slug → Session bereinigen $this->clearUserShopSession($config); return; } // 3. Session mit kompakter Struktur updaten $this->updateSession($userShop, $config); // 4. Sicheren Cookie setzen $this->updateCookie($userShop, $config); // 5. Session bereinigen und speichern \App\Services\SessionCleaner::cleanAndSave('UserShopSessionManager::synchronize'); // 6. Minimal Debug-Logging $this->logSynchronization($userShop, $slug); } /** * Ermittelt den effektiven UserShop-Slug mit Priority-Chain */ private function resolveEffectiveSlug(Request $request, ?DomainContext $context, array $config): ?string { // Priorität 1: Domain-Context (aktueller UserShop) if ($context?->userShop?->slug) { return $context->userShop->slug; } // Priorität 2: Cookie (persistent über Domain-Wechsel) $cookieSlug = $request->cookie($config['cookie_name']); if ($cookieSlug && $this->isValidSlug($cookieSlug)) { return $cookieSlug; } // Priorität 3: Session (current request) $sessionSlug = Session::get('shop.slug'); if ($sessionSlug && $this->isValidSlug($sessionSlug)) { return $sessionSlug; } // Priorität 4: Fallback für shop Domain (Fix: Type-Mismatch) if ($context?->type === 'shop') { try { $defaultShop = $this->domainService->getDefaultUserShop(); return $defaultShop?->slug; } catch (\Throwable $e) { Log::warning('Default shop loading failed', ['error' => $e->getMessage()]); } } return null; } /** * UserShop mit Request-Level Caching laden */ private function loadUserShopCached(string $slug): ?UserShop { // Request-Cache check if (isset(self::$userShopCache[$slug])) { return self::$userShopCache[$slug]; } try { $userShop = $this->domainService->getUserShop($slug); // Cache für wiederholte Zugriffe im gleichen Request self::$userShopCache[$slug] = $userShop; return $userShop; } catch (\Throwable $e) { Log::warning('UserShop loading failed', [ 'slug' => $slug, 'error' => $e->getMessage() ]); // Cache negative result um wiederholte Fehlversuche zu vermeiden self::$userShopCache[$slug] = null; return null; } } /** * Session mit kompakter Struktur updaten (50% weniger Daten) */ private function updateSession(UserShop $userShop, array $config): void { // Kompakte Session-Keys (shop.* statt ctx.user_shop.*) Session::put('shop.id', $userShop->id); Session::put('shop.slug', $userShop->slug); // Legacy-Support optional (für Backward-Compatibility) if ($config['legacy_support']) { Session::put('user_shop', $userShop); Session::put('user_shop_domain', $this->buildUserShopHost($userShop->slug)); } } /** * Sicheren Cookie mit XSS-Protection setzen (Duplikate-vermeidend) */ private function updateCookie(UserShop $userShop, array $config): void { $cookieValue = $this->sanitizeCookieValue($userShop->slug); $sessionDomain = Config::get('session.domain'); // Anti-Duplikate: Prüfen ob Cookie-Value sich geändert hat $currentCookieValue = request()->cookie($config['cookie_name']); if ($currentCookieValue === $cookieValue) { // Cookie ist bereits korrekt gesetzt → Skip um Duplikate zu vermeiden if ($config['debug_logging']) { Log::debug('UserShop cookie unchanged, skipping update', [ 'cookie_name' => $config['cookie_name'], 'current_value' => $currentCookieValue, 'user_shop_slug' => $userShop->slug ]); } return; } // Cookie-Value hat sich geändert → Update notwendig cookie()->queue( cookie( $config['cookie_name'], $cookieValue, $config['cookie_ttl_minutes'], path: '/', domain: $sessionDomain, secure: $config['cookie_secure'], httpOnly: true, // XSS-Protection sameSite: $config['cookie_same_site'] // Fix: SameSite konfigurierbar ) ); // Debug-Logging für Cookie-Updates if ($config['debug_logging']) { Log::debug('UserShop cookie updated', [ 'cookie_name' => $config['cookie_name'], 'old_value' => $currentCookieValue ?? 'none', 'new_value' => $cookieValue, 'domain' => $sessionDomain, 'ttl_minutes' => $config['cookie_ttl_minutes'], 'user_shop_slug' => $userShop->slug ]); } } /** * Session bereinigen */ private function clearUserShopSession(array $config): void { // Kompakte Keys entfernen Session::forget(['shop.id', 'shop.slug']); // Legacy-Keys optional entfernen if ($config['legacy_support']) { Session::forget(['user_shop', 'user_shop_domain']); } } /** * Konfiguration mit sinnvollen Defaults laden */ private function getConfig(): array { $config = Config::get('subdomain', []); return [ 'cookie_name' => $config['cookie']['name'] ?? 'mivita_shop', 'cookie_ttl_minutes' => ($config['cookie']['ttl_days'] ?? 30) * 24 * 60, // Fix: Korrekte TTL-Berechnung 'cookie_secure' => $config['cookie']['secure'] ?? (config('app.env') === 'production'), 'cookie_same_site' => $config['cookie']['same_site'] ?? 'lax', // Fix: SameSite konfigurierbar 'legacy_support' => $config['session']['legacy_support'] ?? true, 'debug_logging' => $config['debug']['log_domain_switches'] ?? false, ]; } /** * UserShop-Host URL erstellen */ private function buildUserShopHost(string $slug): string { try { return parse_url($this->domainService->buildUrl('user-shop', null, $slug), PHP_URL_HOST) ?? ''; } catch (\Throwable $e) { Log::warning('UserShop host generation failed', [ 'slug' => $slug, 'error' => $e->getMessage() ]); return ''; } } /** * Cookie-Value gegen XSS sanitizen */ private function sanitizeCookieValue(string $value): string { // Nur alphanumerische Zeichen und Bindestriche erlauben return preg_replace('/[^a-z0-9-]/i', '', $value); } /** * Slug-Format validieren */ private function isValidSlug(string $slug): bool { return !empty($slug) && strlen($slug) >= 3 && strlen($slug) <= 50 && preg_match('/^[a-z0-9-]+$/', $slug); } /** * Minimal Debug-Logging */ private function logSynchronization(UserShop $userShop, string $slug): void { if (!$this->getConfig()['debug_logging']) { return; } Log::debug('UserShop synchronized', [ 'user_shop_id' => $userShop->id, 'slug' => $slug, 'session_id' => Session::getId(), 'cache_entries' => count(self::$userShopCache), 'memory_mb' => round(memory_get_usage() / 1024 / 1024, 2) ]); } /** * Aktuellen UserShop aus Session laden (Helper für Controller/Views) */ public function getCurrentUserShop(): ?UserShop { $slug = Session::get('shop.slug'); return $slug ? $this->loadUserShopCached($slug) : null; } /** * Prüfen ob aktuell ein UserShop aktiv ist */ public function hasActiveUserShop(): bool { return Session::has('shop.id') && Session::has('shop.slug'); } /** * Cache-Statistiken für Debugging */ public static function getCacheStats(): array { return [ 'cached_shops' => count(self::$userShopCache), 'memory_usage_kb' => round(memory_get_usage() / 1024, 2) ]; } /** * Cache für Testing zurücksetzen */ public static function clearCache(): void { self::$userShopCache = []; } }