eesyphp/src/Tpl.php
Benjamin Renard f2edf4910a Switch to YAML configuration file, add Config & App classes in EesyPHP namespace
App initialization is now handle by App::init() method and all 
configuration information is now retreive using Config::get() method.
2023-02-08 02:27:15 +01:00

363 lines
11 KiB
PHP

<?php
namespace EesyPHP;
use Exception;
use Smarty;
use Smarty_Security;
class Tpl {
/**
* Smarty object
* @var \Smarty
*/
public static Smarty $smarty;
/**
* Smarty_Security object
* @var \Smarty_Security|null
*/
public static $smarty_security_policy = null;
/**
* Smarty templates directory path
* @var string
*/
public static string $templates_dir;
/**
* Smarty cache templates directory path
* @var string
*/
public static string $templates_c_dir;
/**
* Enable/disable AJAX returned data debugging in logs
* @var bool
*/
public static bool $_debug_ajax;
/**
* CSS files to load in next displayed page
* @var array<string>
*/
private static array $css_files = array();
/**
* JavaScript files to load in next displayed page
* @var array<string>
*/
private static array $js_files = array();
/**
* 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)
* @return void
*/
public static function init($templates_dir=null, $templates_c_dir=null, $debug_ajax=null) {
// Check templates/templates_c directories
if (is_null($templates_dir))
$templates_dir = Config::get('template.directory', null, 'string');
if (is_null($templates_c_dir))
$templates_c_dir = Config::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 = Config::get('template.debug_ajax', Config::get('debug_ajax'));
self :: $_debug_ajax = boolval($debug_ajax);
Log :: register_fatal_error_handler(array('\\EesyPHP\\Tpl', 'fatal_error'));
}
/**
* Enable security in mode to limit functions (in IF clauses) and modifiers usable from
* template files
* @param array<string>|null $functions List of function names granted in IF clauses
* @param array<string>|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 array<string|array<string>> $args CSS files to load
* @return void
*/
public static function add_css_file(...$args) {
foreach ($args as $files) {
if (!is_array($files)) $files = array($files);
foreach ($files as $file) {
if (!in_array($file, self :: $css_files))
self :: $css_files[] = $file;
}
}
}
/**
* Register JS file(s) to load on next displayed page
* @param array<string|array<string>> $args JS files to load
* @return void
*/
public static function add_js_file(...$args) {
foreach ($args as $files) {
if (!is_array($files)) $files = array($files);
foreach ($files as $file) {
if (!in_array($file, self :: $js_files))
self :: $js_files[] = $file;
}
}
}
/**
* 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;
}
}