'Light', 'dark' => 'Dark', ]; } return app(\MintyPHP\Service\Settings\ThemeConfigGateway::class)->all(); } /** * Resolve default theme from settings, then APP_THEME, then light. */ function appDefaultTheme(): string { $themes = appThemes(); $tenantTheme = strtolower(trim((string) ($_SESSION['current_tenant']['default_theme'] ?? ''))); if ($tenantTheme !== '' && isset($themes[$tenantTheme])) { return $tenantTheme; } $setting = appSetting('app_theme'); if ($setting !== null && isset($themes[$setting])) { return $setting; } $envTheme = getenv('APP_THEME') ?: 'light'; $envTheme = strtolower(trim((string) $envTheme)); return isset($themes[$envTheme]) ? $envTheme : 'light'; } /** * Resolve effective theme with optional per-user override. */ function currentTheme(): string { $themes = appThemes(); $theme = appDefaultTheme(); if (!allowUserTheme()) { return $theme; } $user = $_SESSION['user'] ?? []; $userTheme = strtolower(trim((string) ($user['theme'] ?? ''))); if ($userTheme !== '' && isset($themes[$userTheme])) { return $userTheme; } return $theme; } /** * Resolve active primary color (tenant override -> app setting). */ function appPrimaryColor(): ?string { // Tenant-scoped branding has precedence over global settings. $tenantColor = $_SESSION['current_tenant']['primary_color'] ?? null; if ($tenantColor !== null) { $tenantColor = strtolower(trim((string) $tenantColor)); if (preg_match('/^#([0-9a-f]{3}|[0-9a-f]{6})$/i', $tenantColor)) { return $tenantColor; } } $value = appSetting('app_primary_color'); if ($value === null) { return null; } $value = strtolower(trim($value)); if (!preg_match('/^#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) { return null; } return $value; } /** * Convert current hex color to CSS HSL custom properties. */ function appPrimaryCssVars(): string { $hex = appPrimaryColor(); if ($hex === null) { return ''; } $hex = ltrim($hex, '#'); if (strlen($hex) === 3) { $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; } $r = hexdec(substr($hex, 0, 2)) / 255; $g = hexdec(substr($hex, 2, 2)) / 255; $b = hexdec(substr($hex, 4, 2)) / 255; // Short-circuit grayscale values to avoid unstable hue math. $monoThreshold = 0.000001; if (abs($r - $g) < $monoThreshold && abs($g - $b) < $monoThreshold) { $l = round($r * 100, 2) . '%'; return "--app-primary-h-base: 0; --app-primary-s-base: 0%; --app-primary-l-base: {$l}; --app-primary-h-light: 0; --app-primary-s-light: 0%; --app-primary-l-light: {$l};"; } $max = max($r, $g, $b); $min = min($r, $g, $b); $delta = $max - $min; if ($delta === 0.0) { $l = round((($max + $min) / 2) * 100, 2) . '%'; return "--app-primary-h-base: 0; --app-primary-s-base: 0%; --app-primary-l-base: {$l}; --app-primary-h-light: 0; --app-primary-s-light: 0%; --app-primary-l-light: {$l};"; } $h = 0.0; if ($max === $r) { $h = 60 * fmod((($g - $b) / $delta), 6); } elseif ($max === $g) { $h = 60 * ((($b - $r) / $delta) + 2); } else { $h = 60 * ((($r - $g) / $delta) + 4); } if ($h < 0) { $h += 360; } $l = ($max + $min) / 2; $denominator = 1 - abs(2 * $l - 1); if ($delta === 0.0 || $denominator === 0.0) { $s = 0.0; } else { $s = $delta / $denominator; } $h = round($h, 2); $s = round($s * 100, 2) . '%'; $l = round($l * 100, 2) . '%'; return "--app-primary-h-base: {$h}; --app-primary-s-base: {$s}; --app-primary-l-base: {$l}; --app-primary-h-light: {$h}; --app-primary-s-light: {$s}; --app-primary-l-light: {$l};"; } /** * Resolve app logo path (custom upload with fallback asset). */ function appLogoUrl(?int $size = null): string { if (class_exists('MintyPHP\\Service\\Branding\\BrandingLogoService')) { $logoService = app(\MintyPHP\Service\Branding\BrandingLogoService::class); if ($logoService->hasLogo()) { $query = $size ? '?size=' . (int) $size : ''; return lurl('branding/logo' . $query); } } return asset('brand/logo.svg'); } /** * Resolve logo for auth pages: tenant avatar -> global logo -> default SVG. * * After logout the tenant context (`$_SESSION['current_tenant']`) is preserved, * so the login page can show the tenant avatar instead of the generic app logo. */ function appAuthLogoUrl(?int $size = null): string { $tenantUuid = $_SESSION['current_tenant']['uuid'] ?? ''; if ($tenantUuid !== '' && class_exists('MintyPHP\\Service\\Tenant\\TenantAvatarService')) { if (app(\MintyPHP\Service\Tenant\TenantAvatarService::class)->hasAvatar($tenantUuid)) { $query = $size ? '&size=' . (int) $size : ''; return lurl('auth/tenant-avatar-file?uuid=' . rawurlencode($tenantUuid) . $query); } } return appLogoUrl($size); } /** * Absolute logo URL (used in e-mails and metadata). */ function appLogoUrlAbsolute(int $size = 128): string { $url = appLogoUrl($size); return appUrl($url); } /** * Resolve favicon path (tenant favicon with global fallback). */ function appFaviconUrl(string $file): string { $tenantUuid = $_SESSION['current_tenant']['uuid'] ?? ''; if ($tenantUuid !== '' && class_exists('MintyPHP\\Service\\Tenant\\TenantFaviconService')) { if (app(\MintyPHP\Service\Tenant\TenantFaviconService::class)->hasFavicon($tenantUuid)) { return asset('favicon/tenants/' . $tenantUuid . '/favicon/' . ltrim($file, '/')); } } return asset('favicon/' . ltrim($file, '/')); }