*/ private static array $css_files = array(); /** * JavaScript files to load in next displayed page * @var array */ private static array $js_files = array(); /** * MIME type detector object * @var null|string */ private static $static_root_url; /** * MIME type detector object * @var ExtensionMimeTypeDetector|null */ private static $mime_type_detector = null; /** * Initialization * @param string $templates_dir Smarty templates directory path * (optional, default: from template.directory config key) * @param string $templates_c_dir Smarty cache templates directory path * (optional, default: from template.cache_directory config key) * @param bool $debug_ajax Enable/disable AJAX returned data debugging in logs * (optional, default: from template.debug_ajax or debug_ajax config keys if set, * false otherwise) * @param bool $static_root_url Configure custom root URL path for static files * (optional, default: from template.static_root_url config key if set, * '/static' otherwise. Set to False to disable) * @return void */ public static function init($templates_dir=null, $templates_c_dir=null, $debug_ajax=null, $static_root_url=null) { // Check templates/templates_c directories if (is_null($templates_dir)) $templates_dir = App::get('template.directory', null, 'string'); if (is_null($templates_c_dir)) $templates_c_dir = App::get('template.cache_directory', null, 'string'); if (!$templates_dir || !is_dir($templates_dir)) { Log :: fatal( "Template directory not found (%s)", $templates_dir?$templates_dir:'not set'); return; } if (!$templates_c_dir || !is_dir($templates_c_dir) || !is_writable($templates_c_dir)) { Log :: fatal( "Template cache directory not found or not writable (%s)", $templates_c_dir?$templates_c_dir:'not set'); return; } self :: $smarty = new Smarty(); self :: $smarty->setTemplateDir($templates_dir); self :: $smarty->setCompileDir($templates_c_dir); if (is_null($debug_ajax)) $debug_ajax = App::get('template.debug_ajax', App::get('debug_ajax')); self :: $_debug_ajax = boolval($debug_ajax); Log :: register_fatal_error_handler(array('\\EesyPHP\\Tpl', 'fatal_error')); if (is_null($static_root_url)) $static_root_url = App::get('template.static_root_url', 'static/', 'string'); if ($static_root_url) { if (substr($static_root_url, 0, 1) == '/') $static_root_url = substr($static_root_url, 1); if (substr($static_root_url, -1) != '/') $static_root_url = "$static_root_url/"; self :: $static_root_url = $static_root_url; $default_static_directory = realpath(__DIR__."/../static"); self :: register_static_directory($default_static_directory, 100); self :: register_function('static_url', array('EesyPHP\\Tpl', 'smarty_static_url')); foreach(App :: get('templates.static_directories', array(), 'array') as $path) self :: register_static_directory($path); } } /** * Enable security in mode to limit functions (in IF clauses) and modifiers usable from * template files * @param array|null $functions List of function names granted in IF clauses * @param array|null $modifiers List of modifier names granted * @return void */ public static function enable_security_mode($functions=null, $modifiers=null) { // Define security policy self :: $smarty_security_policy = new Smarty_Security(self :: $smarty); // Allow functions in IF clauses if (is_array($functions)) foreach($functions as $function) self :: $smarty_security_policy->php_functions[] = $function; // Allow modifier functions if (is_array($modifiers)) foreach($modifiers as $modifier) self :: $smarty_security_policy->php_modifiers[] = $modifier; // Enable security self :: $smarty -> enableSecurity(self :: $smarty_security_policy); // Initialize errors & messages session variables if (!isset($_SESSION['errors'])) $_SESSION['errors'] = array(); if (!isset($_SESSION['messages'])) $_SESSION['messages'] = array(); } /** * Register a function usable from template files * @param string $name The function name * @param callable $callable The function * @return void */ public static function register_function($name, $callable) { self :: $smarty -> registerPlugin("function", $name, $callable); } /** * Assign template variable * @param string $name The variable name * @param mixed $value The variable value * @return void */ public static function assign($name, $value) { self :: $smarty -> assign($name, $value); } /** * Add error message * @param string $error The message * @param array $extra_args Extra arguments to use to compute error message using sprintf * @return void */ public static function add_error($error, ...$extra_args) { // If extra arguments passed, format error message using sprintf if ($extra_args) { $error = call_user_func_array( 'sprintf', array_merge(array($error), $extra_args) ); } $_SESSION['errors'][] = $error; } /** * Add informational message * @param string $message The message * @param array $extra_args Extra arguments to use to compute message using sprintf * @return void */ public static function add_message($message, ...$extra_args) { // If extra arguments passed, format message using sprintf if ($extra_args) { $message = call_user_func_array( 'sprintf', array_merge(array($message), $extra_args) ); } $_SESSION['messages'][] = $message; } /** * Register CSS file(s) to load on next displayed page * @param string|array $args CSS files to load * @return void */ public static function add_css_file(...$args) { // Check if the first argument is a custom static root URL $root_url = self :: $static_root_url; if ( $args && is_string($args[0]) && array_key_exists( self :: clean_static_root_url($args[0]), self :: $static_directories ) ) $root_url = self :: clean_static_root_url(array_shift($args)); foreach ($args as $files) { if (!is_array($files)) $files = array($files); foreach ($files as $file) { $path = $root_url.$file; if (!in_array($path, self :: $css_files)) self :: $css_files[] = $path; } } } /** * Register JS file(s) to load on next displayed page * @param string|array $args JS files to load * @return void */ public static function add_js_file(...$args) { // Check if the first argument is a custom static root URL $root_url = self :: $static_root_url; if ( $args && is_string($args[0]) && array_key_exists( self :: clean_static_root_url($args[0]), self :: $static_directories ) ) $root_url = self :: clean_static_root_url(array_shift($args)); foreach ($args as $files) { if (!is_array($files)) $files = array($files); foreach ($files as $file) { $path = $root_url.$file; if (!in_array($path, self :: $js_files)) self :: $js_files[] = $path; } } } /** * Define common variables * @param string|null $pagetitle The page title * @return void */ protected static function define_common_variables($pagetitle=null) { global $auth_user; self :: assign('pagetitle', $pagetitle); // Messages self :: assign('errors', (isset($_SESSION['errors'])?$_SESSION['errors']:array())); self :: assign('messages', (isset($_SESSION['messages'])?$_SESSION['messages']:array())); // Files inclusions self :: assign('css', self :: $css_files); self :: assign('js', self :: $js_files); // Authenticated user info if (isset($auth_user)) self :: assign('auth_user', $auth_user); } /** * Display the template * @param string $template The template to display * @param string|null $pagetitle The page title (optional) * @param array $extra_args Extra arguments to use to compute the page title using sprintf * @return void */ public static function display($template, $pagetitle=null, ...$extra_args) { if (!$template) { Log :: fatal(_("No template specified.")); return; } // If refresh parameter is present, remove it and redirect if (isset($_GET['refresh'])) { unset($_GET['refresh']); $url = Url :: get_current_url(); if (!empty($_GET)) $url .= '?'.http_build_query($_GET); Url :: redirect($url); return; } $sentry_span = new SentrySpan('smarty.display_template', "Display Smarty template"); // If extra arguments passed, format pagetitle using sprintf if ($pagetitle && $extra_args) { $pagetitle = call_user_func_array( 'sprintf', array_merge(array($pagetitle), $extra_args) ); } try { Hook :: trigger('before_displaying_template'); self :: define_common_variables($pagetitle); self :: $smarty->display($template); } catch (Exception $e) { Log :: exception($e, "Smarty - An exception occured displaying template '$template'"); if ($template != 'fatal_error.tpl') Log :: fatal(_("An error occurred while displaying this page.")); return; } unset($_SESSION['errors']); unset($_SESSION['messages']); Hook :: trigger('after_displaying_template'); $sentry_span->finish(); } /** * Display AJAX return * @param array|null $data AJAX returned data (optional) * @param bool $pretty AJAX returned data * (optional, default: true if $_REQUEST['pretty'] is set, False otherwise) * @return void */ public static function display_ajax_return($data=null, $pretty=false) { if (!is_array($data)) $data = array(); // Adjust HTTP error code on unsuccessfull request elseif (isset($data['success']) && !$data['success'] && http_response_code() == 200) http_response_code(400); if (isset($_SESSION['messages']) && !empty($_SESSION['messages'])) { $data['messages'] = $_SESSION['messages']; unset($_SESSION['messages']); } if (isset($_SESSION['errors']) && !empty($_SESSION['errors'])) { $data['errors'] = $_SESSION['errors']; unset($_SESSION['errors']); } if (self :: $_debug_ajax) Log :: debug("AJAX Response : ".vardump($data)); header('Content-Type: application/json'); echo json_encode($data, (($pretty||isset($_REQUEST['pretty']))?JSON_PRETTY_PRINT:0)); exit(); } /** * Handle a fatal error * @param string $error The error message * @param array $extra_args Extra arguments to use to compute the error message using sprintf * @return void */ public static function fatal_error($error, ...$extra_args) { // If extra arguments passed, format error message using sprintf if ($extra_args) { $error = call_user_func_array( 'sprintf', array_merge(array($error), $extra_args) ); } if (php_sapi_name() == "cli") die("FATAL ERROR : $error\n"); // Set HTTP reponse code to 500 http_response_code(500); // Handle API mode if (Url :: api_mode()) { self :: display_ajax_return(array('success' => false, 'error' => $error)); return; } self :: assign('fatal_error', $error); self :: display('fatal_error.tpl'); exit(); } /** * Get/set AJAX debug mode * @param bool|null $value If boolean, the current API mode will be changed * @return bool Current API mode */ public static function debug_ajax($value=null) { if (is_bool($value)) self :: $_debug_ajax = $value; return self :: $_debug_ajax; } /** * Check if initialized * @return bool */ public static function initialized() { return isset(self :: $smarty); } /** * Get the templates directory path * @return string|null */ public static function templates_directory() { return isset(self :: $templates_directory)?self :: $templates_directory:null; } /** * Clean static root URL helper * @param string $value * @return string */ public static function clean_static_root_url($value) { if (substr($value, 0, 1) == '/') $value = substr($value, 1); if (substr($value, -1) != '/') $value = "$value/"; return $value; } /** * Register a static directory * @param string $path The static directory path * @param int|null $priority The priority of this static directory * (optional, default: prior than all other registered directories) * @return void */ public static function register_static_directory($path, $priority=null, $root_url=null) { if (is_null($root_url)) { if (!self :: $static_root_url) Log :: fatal( 'register_static_directory(%s): no root URL provided and no default value configured', $path); $root_url = self :: $static_root_url; } if (!array_key_exists($root_url, self :: $static_directories)) { self :: $static_directories[$root_url] = array(); if (is_null($priority)) $priority = 100; $pattern = "#^(?P$root_url)(?P.*)#"; Log :: trace( 'Register static file URL handler for root URL "%s" with pattern "%s" and directory '. '"%s" (priority: %d)', $root_url, $pattern, $path, $priority); Url :: add_url_handler( $pattern, array('EesyPHP\\Tpl', 'handle_static_file'), false, // authenticated false, // override false, // API mode array('GET') // methods ); if (is_null(self :: $mime_type_detector)) self :: $mime_type_detector = new ExtensionMimeTypeDetector(); } else { if (is_null($priority)) { $priority = max(self :: $static_directories[$root_url]); $priority++; } Log :: trace( 'Register additionnal static directory "%s" for root URL "%s" (priority: %d)', $path, $root_url, $priority); } if (substr($path, -1) == PATH_SEPARATOR) $path = substr($path, 0, -1); self :: $static_directories[$root_url][$path] = $priority; arsort(self :: $static_directories[$root_url]); } /** * Resolve static path against registered static directories * @param string $path * @return string|false */ public static function resolve_static_path($root_url, $path) { if (!array_key_exists($root_url, self :: $static_directories)) { Log::error( 'No static directory registered for root URL "%s". Can no resolve static file "%s" path.', $root_url, $path); return false; } foreach(array_keys(self :: $static_directories[$root_url]) as $dir) { $fullpath = "$dir/$path"; if (file_exists($fullpath)) return $fullpath; } Log::trace('Static file "%s%s" not found', $root_url, $path); return false; } /** * Handle URL request for static file * Note: this URL handler is registered in EesyPHP\Url by self::init(). * @see self::init() * @param UrlRequest $request * @return void */ public static function handle_static_file($request) { $path = self :: resolve_static_path($request->root_url, $request->path); Log::trace('Resolved static file path for "%s": "%s"', $request->path, $path); if (!$path) Url :: trigger_error_404($request); $mime_type = self :: $mime_type_detector->detectMimeTypeFromFile($path); Log::trace('MIME type detected for "%s" is "%s"', $path, $mime_type); dump_file($path, $mime_type); } /** * Function to retrieve static file URL * @param string $path The file path * @return string|false */ public static function static_url($path) { if (self :: $static_root_url) return self :: $static_root_url.$path; return false; } /** * Smarty function to print static file URL * @param array $params Parameters from template file * @param Smarty $smarty The smarty object * @return void */ public static function smarty_static_url($params, $smarty) { if (!isset($params['path'])) return; $url = self :: static_url($params['path']); if ($url) echo $url; } }