| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  | <?php | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Theme Helper | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Handles theme management and template/asset loading for the application. | 
					
						
							|  |  |  |  * Supports multiple themes with fallback to default theme when needed. | 
					
						
							|  |  |  |  * The default theme uses app/templates and public_html/static as fallbacks/ | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace App\Helpers; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | use Exception; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-23 06:58:50 +00:00
										 |  |  | // Include Session class
 | 
					
						
							|  |  |  | require_once __DIR__ . '/../classes/session.php'; | 
					
						
							|  |  |  | use Session; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  | class Theme | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @var array Theme configuration | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private static $config; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 10:55:08 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-23 06:58:50 +00:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Get the theme configuration | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return array | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static function getConfig() | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2025-06-10 08:55:06 +00:00
										 |  |  |         // Always reload the config to get the latest changes
 | 
					
						
							|  |  |  |         self::$config = require __DIR__ . '/../config/theme.php'; | 
					
						
							| 
									
										
										
										
											2025-05-23 06:58:50 +00:00
										 |  |  |         return self::$config; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 10:55:08 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * @var string Current theme name | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private static $currentTheme; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 10:55:08 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Initialize the theme system | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static function init() | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2025-06-10 08:55:06 +00:00
										 |  |  |         // Only load config if not already loaded
 | 
					
						
							|  |  |  |         if (self::$config === null) { | 
					
						
							|  |  |  |             self::$config = require __DIR__ . '/../config/theme.php'; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |         self::$currentTheme = self::getCurrentThemeName(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 10:55:08 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Get the current theme name | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return string | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static function getCurrentThemeName() | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2025-06-10 08:55:06 +00:00
										 |  |  |         // Ensure session is started
 | 
					
						
							|  |  |  |         if (session_status() === PHP_SESSION_NONE) { | 
					
						
							|  |  |  |             Session::startSession(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |         // Check if already determined
 | 
					
						
							|  |  |  |         if (self::$currentTheme !== null) { | 
					
						
							|  |  |  |             return self::$currentTheme; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 10:55:08 +00:00
										 |  |  |         // Try to get from session first
 | 
					
						
							| 
									
										
										
										
											2025-06-23 11:08:11 +00:00
										 |  |  |         $sessionTheme = isset($_SESSION['theme']) ? $_SESSION['theme'] : null; | 
					
						
							| 
									
										
										
										
											2025-06-20 10:55:08 +00:00
										 |  |  |         if ($sessionTheme && isset(self::$config['available_themes'][$sessionTheme])) { | 
					
						
							|  |  |  |             self::$currentTheme = $sessionTheme; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             // Fall back to default theme
 | 
					
						
							|  |  |  |             self::$currentTheme = self::$config['active_theme']; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return self::$currentTheme; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Get the URL for a theme asset | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param string $themeId Theme ID | 
					
						
							|  |  |  |      * @param string $assetPath Path to the asset relative to theme directory (e.g., 'css/style.css') | 
					
						
							|  |  |  |      * @return string|null URL to the asset or null if not found | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static function getAssetUrl($themeId, $assetPath = '') | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // Clean and validate the asset path
 | 
					
						
							|  |  |  |         $assetPath = ltrim($assetPath, '/'); | 
					
						
							|  |  |  |         if (empty($assetPath)) { | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Only allow alphanumeric, hyphen, underscore, dot, and forward slash
 | 
					
						
							|  |  |  |         if (!preg_match('/^[a-zA-Z0-9_\-\.\/]+$/', $assetPath)) { | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Prevent directory traversal
 | 
					
						
							|  |  |  |         if (strpos($assetPath, '..') !== false) { | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $fullPath = __DIR__ . "/../../themes/$themeId/$assetPath"; | 
					
						
							|  |  |  |         if (!file_exists($fullPath) || !is_readable($fullPath)) { | 
					
						
							|  |  |  |             return null; | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-26 11:27:54 +00:00
										 |  |  |         // Generate URL that goes through index.php
 | 
					
						
							| 
									
										
										
										
											2025-06-20 10:55:08 +00:00
										 |  |  |         global $app_root; | 
					
						
							| 
									
										
										
										
											2025-06-26 11:27:54 +00:00
										 |  |  |         // Remove any trailing slash from app_root to avoid double slashes
 | 
					
						
							|  |  |  |         $baseUrl = rtrim($app_root, '/'); | 
					
						
							|  |  |  |         return "$baseUrl/?page=theme-asset&theme=" . urlencode($themeId) . "&path=" . urlencode($assetPath); | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 10:55:08 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Set the current theme for the session | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param string $themeName | 
					
						
							|  |  |  |      * @return bool | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static function setCurrentTheme(string $themeName): bool | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         if (!self::themeExists($themeName)) { | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-10 08:55:06 +00:00
										 |  |  |         // Update session
 | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |         if (Session::isValidSession()) { | 
					
						
							| 
									
										
										
										
											2025-06-26 11:48:51 +00:00
										 |  |  |             $_SESSION['theme'] = $themeName; | 
					
						
							| 
									
										
										
										
											2025-06-10 08:55:06 +00:00
										 |  |  |         } else { | 
					
						
							|  |  |  |             return false; | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-26 11:48:51 +00:00
										 |  |  |         // Clear the current theme cache
 | 
					
						
							|  |  |  |         self::$currentTheme = null; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-10 08:55:06 +00:00
										 |  |  |         // Update config file
 | 
					
						
							|  |  |  |         $configFile = __DIR__ . '/../config/theme.php'; | 
					
						
							|  |  |  |         if (file_exists($configFile) && is_writable($configFile)) { | 
					
						
							|  |  |  |             $config = file_get_contents($configFile); | 
					
						
							|  |  |  |             // Update the active_theme in the config
 | 
					
						
							|  |  |  |             $newConfig = preg_replace( | 
					
						
							|  |  |  |                 "/'active_theme'\s*=>\s*'[^']*'/", | 
					
						
							|  |  |  |                 "'active_theme' => '" . addslashes($themeName) . "'", | 
					
						
							|  |  |  |                 $config | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if ($newConfig !== $config) { | 
					
						
							|  |  |  |                 if (file_put_contents($configFile, $newConfig) === false) { | 
					
						
							|  |  |  |                     return false; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             self::$currentTheme = $themeName; | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-06-26 11:48:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-10 08:55:06 +00:00
										 |  |  |         return false; | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 10:55:08 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Check if a theme exists | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param string $themeName | 
					
						
							|  |  |  |      * @return bool | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static function themeExists(string $themeName): bool | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2025-06-10 08:55:06 +00:00
										 |  |  |         // Default theme always exists as it uses core templates
 | 
					
						
							|  |  |  |         if ($themeName === 'default') { | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |         $themePath = self::getThemePath($themeName); | 
					
						
							|  |  |  |         return is_dir($themePath) && file_exists("$themePath/config.php"); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 10:55:08 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Get the path to a theme | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param string|null $themeName | 
					
						
							|  |  |  |      * @return string | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static function getThemePath(?string $themeName = null): string | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $themeName = $themeName ?? self::getCurrentThemeName(); | 
					
						
							|  |  |  |         $config = self::getConfig(); | 
					
						
							|  |  |  |         return rtrim($config['paths']['themes'], '/') . "/$themeName"; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 10:55:08 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Get the URL for a theme asset | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param string $path | 
					
						
							|  |  |  |      * @param bool $includeVersion | 
					
						
							|  |  |  |      * @return string | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static function asset($path, $includeVersion = false) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $themeName = self::getCurrentThemeName(); | 
					
						
							|  |  |  |         $config = self::getConfig(); | 
					
						
							|  |  |  |         $baseUrl = rtrim($GLOBALS['app_root'] ?? '', '/'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // For non-default themes, use theme assets
 | 
					
						
							|  |  |  |         if ($themeName !== 'default') { | 
					
						
							|  |  |  |             $assetPath = "/themes/{$themeName}/assets/" . ltrim($path, '/'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Add version query string for cache busting
 | 
					
						
							|  |  |  |             if ($includeVersion) { | 
					
						
							|  |  |  |                 $version = self::getThemeVersion($themeName); | 
					
						
							|  |  |  |                 $assetPath .= (strpos($assetPath, '?') !== false ? '&' : '?') . 'v=' . $version; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             // For default theme, use public_html directly
 | 
					
						
							|  |  |  |             $assetPath = '/' . ltrim($path, '/'); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $baseUrl . $assetPath; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 10:55:08 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2025-05-23 06:58:50 +00:00
										 |  |  |      * Include a theme template file | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2025-05-23 06:58:50 +00:00
										 |  |  |      * @param string $template Template name without .php extension | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |      * @return void | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2025-05-23 06:58:50 +00:00
										 |  |  |     public static function include($template) | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2025-06-16 09:22:26 +00:00
										 |  |  |         global $config; | 
					
						
							|  |  |  |         $config = $config ?? []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $themeConfig = self::getConfig(); | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |         $themeName = self::getCurrentThemeName(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-23 06:58:50 +00:00
										 |  |  |         // We need this here, otherwise because this helper
 | 
					
						
							|  |  |  |         // between index and the views breaks the session vars
 | 
					
						
							|  |  |  |         extract($GLOBALS, EXTR_SKIP | EXTR_REFS); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-16 09:22:26 +00:00
										 |  |  |         // Ensure config is always available in templates
 | 
					
						
							|  |  |  |         $config = array_merge($config, $themeConfig); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-23 06:58:50 +00:00
										 |  |  |         // For non-default themes, look in the theme directory first
 | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |         if ($themeName !== 'default') { | 
					
						
							|  |  |  |             $themePath = $config['paths']['themes'] . '/' . $themeName . '/views/' . $template . '.php'; | 
					
						
							|  |  |  |             if (file_exists($themePath)) { | 
					
						
							|  |  |  |                 include $themePath; | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-05-23 06:58:50 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-23 06:58:50 +00:00
										 |  |  |         // Fallback to default template location
 | 
					
						
							|  |  |  |         $defaultPath = $config['paths']['templates'] . '/' . $template . '.php'; | 
					
						
							|  |  |  |         if (file_exists($defaultPath)) { | 
					
						
							|  |  |  |             include $defaultPath; | 
					
						
							|  |  |  |             return; | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Log error if template not found
 | 
					
						
							|  |  |  |         error_log("Template not found: {$template} in theme: {$themeName}"); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 10:55:08 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Get all available themes | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return array | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static function getAvailableThemes(): array | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2025-06-01 07:39:39 +00:00
										 |  |  |         $config = self::getConfig(); | 
					
						
							|  |  |  |         $availableThemes = $config['available_themes'] ?? []; | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |         $themes = []; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-01 07:39:39 +00:00
										 |  |  |         // Add default theme if not already present
 | 
					
						
							|  |  |  |         if (!isset($availableThemes['default'])) { | 
					
						
							|  |  |  |             $availableThemes['default'] = 'Default built-in theme'; | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-01 07:39:39 +00:00
										 |  |  |         // Verify each theme exists and has a config file
 | 
					
						
							|  |  |  |         $themesDir = $config['paths']['themes'] ?? (__DIR__ . '/../../themes'); | 
					
						
							|  |  |  |         foreach ($availableThemes as $id => $name) { | 
					
						
							| 
									
										
										
										
											2025-06-10 08:55:06 +00:00
										 |  |  |             if ($id === 'default' || (is_dir("$themesDir/$id") && file_exists("$themesDir/$id/config.php"))) { | 
					
						
							| 
									
										
										
										
											2025-06-01 07:39:39 +00:00
										 |  |  |                 $themes[$id] = $name; | 
					
						
							| 
									
										
										
										
											2025-05-15 11:54:21 +00:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $themes; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Initialize the theme system
 | 
					
						
							|  |  |  | Theme::init(); |