| 
									
										
										
										
											2025-06-19 10:47:09 +00:00
										 |  |  | <?php | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-06-26 11:27:15 +00:00
										 |  |  |  * Theme Asset handler | 
					
						
							| 
									
										
										
										
											2025-06-19 10:47:09 +00:00
										 |  |  |  * | 
					
						
							|  |  |  |  * Serves theme assets (images, CSS, JS, etc.) securely by checking if the requested | 
					
						
							|  |  |  |  * theme and asset path are valid and accessible. | 
					
						
							| 
									
										
										
										
											2025-06-26 11:27:15 +00:00
										 |  |  |  * | 
					
						
							|  |  |  |  * This is a standalone handler that doesn't require the full application initialization. | 
					
						
							| 
									
										
										
										
											2025-06-19 10:47:09 +00:00
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-26 11:27:15 +00:00
										 |  |  | // Set error reporting
 | 
					
						
							|  |  |  | error_reporting(E_ALL); | 
					
						
							|  |  |  | ini_set('display_errors', '1'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Define base path if not defined
 | 
					
						
							|  |  |  | if (!defined('APP_ROOT')) { | 
					
						
							|  |  |  |     define('APP_ROOT', dirname(__DIR__)); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-06-19 10:47:09 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Basic security checks
 | 
					
						
							|  |  |  | if (!isset($_GET['theme']) || !preg_match('/^[a-zA-Z0-9_-]+$/', $_GET['theme'])) { | 
					
						
							|  |  |  |     http_response_code(400); | 
					
						
							|  |  |  |     exit('Invalid theme specified'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if (!isset($_GET['path']) || empty($_GET['path'])) { | 
					
						
							|  |  |  |     http_response_code(400); | 
					
						
							|  |  |  |     exit('No asset path specified'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | $themeId = $_GET['theme']; | 
					
						
							|  |  |  | $assetPath = $_GET['path']; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Validate asset path (only alphanumeric, hyphen, underscore, dot, and forward slash)
 | 
					
						
							|  |  |  | if (!preg_match('/^[a-zA-Z0-9_\-\.\/]+$/', $assetPath)) { | 
					
						
							|  |  |  |     http_response_code(400); | 
					
						
							|  |  |  |     exit('Invalid asset path'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Prevent directory traversal
 | 
					
						
							|  |  |  | if (strpos($assetPath, '..') !== false) { | 
					
						
							|  |  |  |     http_response_code(400); | 
					
						
							|  |  |  |     exit('Invalid asset path'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Build full path to the asset
 | 
					
						
							| 
									
										
										
										
											2025-06-26 11:27:15 +00:00
										 |  |  | $themesDir = dirname(dirname(__DIR__)) . '/themes'; | 
					
						
							|  |  |  | $fullPath = realpath("$themesDir/$themeId/$assetPath"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Additional security check to ensure the path is within the themes directory
 | 
					
						
							|  |  |  | if ($fullPath === false) { | 
					
						
							|  |  |  |     http_response_code(404); | 
					
						
							|  |  |  |     header('Content-Type: text/plain'); | 
					
						
							|  |  |  |     error_log("Asset not found: $themesDir/$themeId/$assetPath"); | 
					
						
							|  |  |  |     exit("Asset not found: $themesDir/$themeId/$assetPath"); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if (strpos($fullPath, realpath($themesDir)) !== 0) { | 
					
						
							|  |  |  |     http_response_code(400); | 
					
						
							|  |  |  |     header('Content-Type: text/plain'); | 
					
						
							|  |  |  |     error_log("Security violation: Attempted to access path outside themes directory: $fullPath"); | 
					
						
							|  |  |  |     exit('Invalid asset path'); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-06-19 10:47:09 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Check if the file exists and is readable
 | 
					
						
							|  |  |  | if (!file_exists($fullPath) || !is_readable($fullPath)) { | 
					
						
							|  |  |  |     http_response_code(404); | 
					
						
							| 
									
										
										
										
											2025-06-26 11:27:15 +00:00
										 |  |  |     header('Content-Type: text/plain'); | 
					
						
							|  |  |  |     error_log("File not found or not readable: $fullPath"); | 
					
						
							|  |  |  |     exit("File not found or not readable: " . basename($fullPath)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Clear any previous output
 | 
					
						
							|  |  |  | if (ob_get_level()) { | 
					
						
							|  |  |  |     ob_clean(); | 
					
						
							| 
									
										
										
										
											2025-06-19 10:47:09 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Determine content type based on file extension
 | 
					
						
							|  |  |  | $extension = strtolower(pathinfo($assetPath, PATHINFO_EXTENSION)); | 
					
						
							|  |  |  | $contentTypes = [ | 
					
						
							|  |  |  |     'css'  => 'text/css', | 
					
						
							|  |  |  |     'js'   => 'application/javascript', | 
					
						
							|  |  |  |     'json' => 'application/json', | 
					
						
							|  |  |  |     'png'  => 'image/png', | 
					
						
							|  |  |  |     'jpg'  => 'image/jpeg', | 
					
						
							|  |  |  |     'jpeg' => 'image/jpeg', | 
					
						
							|  |  |  |     'gif'  => 'image/gif', | 
					
						
							|  |  |  |     'svg'  => 'image/svg+xml', | 
					
						
							|  |  |  |     'webp' => 'image/webp', | 
					
						
							|  |  |  |     'woff' => 'font/woff', | 
					
						
							|  |  |  |     'woff2' => 'font/woff2', | 
					
						
							|  |  |  |     'ttf'  => 'font/ttf', | 
					
						
							|  |  |  |     'eot'  => 'application/vnd.ms-fontobject', | 
					
						
							|  |  |  | ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | $contentType = $contentTypes[$extension] ?? 'application/octet-stream'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Set proper headers
 | 
					
						
							|  |  |  | header('Content-Type: ' . $contentType); | 
					
						
							|  |  |  | header('Content-Length: ' . filesize($fullPath)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Cache for 24 hours (86400 seconds)
 | 
					
						
							|  |  |  | $expires = 86400; | 
					
						
							|  |  |  | header('Cache-Control: public, max-age=' . $expires); | 
					
						
							|  |  |  | header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT'); | 
					
						
							|  |  |  | header('Pragma: cache'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Output the file
 | 
					
						
							|  |  |  | readfile($fullPath); |