Tpl: Add stuff to handle static files

* Move example application in example sub-directory
* Widely use App::get() instead of Config::get()
This commit is contained in:
Benjamin Renard 2023-02-12 00:30:36 +01:00
parent f2edf4910a
commit 4f47dc056d
1148 changed files with 319 additions and 2959 deletions

1
.gitignore vendored
View file

@ -3,6 +3,7 @@
.*.swp .*.swp
# Exclude composer installed libs # Exclude composer installed libs
/vendor /vendor
/composer.lock
# Common UNIX user home directory files # Common UNIX user home directory files
/.bash* /.bash*
/.vim* /.vim*

View file

@ -26,7 +26,8 @@
"sentry/sdk": "^3.3", "sentry/sdk": "^3.3",
"ext-pdo": "^7.3", "ext-pdo": "^7.3",
"ext-json": "^7.3", "ext-json": "^7.3",
"ext-yaml": "^2.0" "ext-yaml": "^2.0",
"league/mime-type-detection": "^1.11"
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "^1.9" "phpstan/phpstan": "^1.9"

2866
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,6 @@
<?php <?php
use EesyPHP\App; use EesyPHP\App;
use EesyPHP\Config;
use EesyPHP\I18n; use EesyPHP\I18n;
use EesyPHP\SentrySpan; use EesyPHP\SentrySpan;
@ -25,7 +24,7 @@ $root_dir_path = realpath(dirname($script).'/../');
set_include_path($root_dir_path.'/includes' . PATH_SEPARATOR . get_include_path()); set_include_path($root_dir_path.'/includes' . PATH_SEPARATOR . get_include_path());
// Load composer autoload.php // Load composer autoload.php
require("$root_dir_path/vendor/autoload.php"); require("$root_dir_path/../vendor/autoload.php");
// Initialize EesyPHP application // Initialize EesyPHP application
App::init( App::init(
@ -34,6 +33,11 @@ App::init(
'overwrite_config_files' => array( 'overwrite_config_files' => array(
"$root_dir_path/includes/config.local.yml", "$root_dir_path/includes/config.local.yml",
), ),
'templates' => array(
'static_directories' => array(
"$root_dir_path/static"
),
),
), ),
$root_dir_path $root_dir_path
); );

View file

@ -1,6 +1,6 @@
<?php <?php
use EesyPHP\Config; use EesyPHP\App;
use EesyPHP\Db; use EesyPHP\Db;
use EesyPHP\Hook; use EesyPHP\Hook;
use EesyPHP\Log; use EesyPHP\Log;
@ -8,12 +8,12 @@ use EesyPHP\Log;
use Unidecode\Unidecode; use Unidecode\Unidecode;
$db = new Db( $db = new Db(
Config::get('db.dsn', null, 'string'), App::get('db.dsn', null, 'string'),
Config::get('db.user', null, 'string'), App::get('db.user', null, 'string'),
Config::get('db.password', null, 'string'), App::get('db.password', null, 'string'),
Config::get('db.options', array(), 'array'), App::get('db.options', array(), 'array'),
Config::get('db.date_format', null, 'string'), App::get('db.date_format', null, 'string'),
Config::get('db.datetime_format', null, 'string'), App::get('db.datetime_format', null, 'string'),
); );
/* /*
@ -154,7 +154,7 @@ function search_items($params) {
global $db; global $db;
// Detect PgSQL backend // Detect PgSQL backend
$is_pgsql = (strpos(Config::get('db.dsn', '', 'string'), "pgsql:") === 0); $is_pgsql = (strpos(App::get('db.dsn', '', 'string'), "pgsql:") === 0);
$where = array(); $where = array();
if (isset($params['status']) && $params['status'] && $params['status'] != 'all') if (isset($params['status']) && $params['status'] && $params['status'] != 'all')

View file

@ -1,6 +1,6 @@
<?php <?php
use EesyPHP\Config; use EesyPHP\App;
use EesyPHP\Date; use EesyPHP\Date;
use EesyPHP\Hook; use EesyPHP\Hook;
use EesyPHP\Log; use EesyPHP\Log;
@ -23,13 +23,13 @@ Tpl :: enable_security_mode(
); );
// Defined some global template variables // Defined some global template variables
Tpl :: assign('public_root_url', Config::get('public_root_url', '/', 'string')); Tpl :: assign('public_root_url', App::get('public_root_url', '/', 'string'));
Tpl :: assign('main_pagetitle', Config::get('main_pagetitle', null, 'string')); Tpl :: assign('main_pagetitle', App::get('main_pagetitle', null, 'string'));
Tpl :: assign('session_key', $_SESSION['session_key']); Tpl :: assign('session_key', $_SESSION['session_key']);
// Handle CSS & JS files included // Handle CSS & JS files included
Tpl :: add_css_file(Config::get('included_css_files', array(), 'array')); Tpl :: add_css_file(App::get('included_css_files', array(), 'array'));
Tpl :: add_js_file(Config::get('included_js_files', array(), 'array')); Tpl :: add_js_file(App::get('included_js_files', array(), 'array'));
function define_common_template_variables($event) { function define_common_template_variables($event) {
global $status_list, $admin; global $status_list, $admin;
@ -41,7 +41,7 @@ function define_common_template_variables($event) {
Tpl :: assign('admin', isset($admin) && $admin); Tpl :: assign('admin', isset($admin) && $admin);
Tpl :: assign( Tpl :: assign(
'webstats_js_code', 'webstats_js_code',
Config::get('webstats_js_code', null, 'string')); App::get('webstats_js_code', null, 'string'));
} }
Hook :: register('before_displaying_template', 'define_common_template_variables'); Hook :: register('before_displaying_template', 'define_common_template_variables');

View file

@ -102,11 +102,11 @@ function handle_search($request) {
Tpl :: assign('nbs_by_page', $nbs_by_page); Tpl :: assign('nbs_by_page', $nbs_by_page);
Tpl :: assign('status_list', $status_list); Tpl :: assign('status_list', $status_list);
Tpl :: add_js_file(array( Tpl :: add_js_file(
'lib/bootstrap4dialog/dist/js/bootstrap4dialog.min.js', 'lib/bootstrap4dialog/dist/js/bootstrap4dialog.min.js',
'js/myconfirm.js', 'js/myconfirm.js',
'js/search.js' 'js/search.js'
)); );
Tpl :: display("search.tpl", _("Search")); Tpl :: display("search.tpl", _("Search"));
} }
@ -124,10 +124,10 @@ function handle_show($request) {
Tpl :: assign('item', $item); Tpl :: assign('item', $item);
// Dialog // Dialog
Tpl :: add_js_file(array( Tpl :: add_js_file(
'lib/bootstrap4dialog/dist/js/bootstrap4dialog.min.js', 'lib/bootstrap4dialog/dist/js/bootstrap4dialog.min.js',
'js/myconfirm.js', 'js/myconfirm.js',
)); );
Tpl :: display( Tpl :: display(
"show.tpl", _("Element %s"), "show.tpl", _("Element %s"),

View file

@ -1,30 +1,29 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
<meta name="description" content=""> <meta name="description" content=""/>
<meta name="author" content=""> <meta name="author" content=""/>
<base href="{$public_root_url}/"/> <base href="{$public_root_url}/"/>
{block name="head"}{/block} {block name="head"}{/block}
<link rel="icon" href="images/favicon.png"> <link rel="icon" href="{static_url path="images/favicon.png"}"/>
<title>{$main_pagetitle}{if $pagetitle} - {$pagetitle}{/if}</title> <title>{$main_pagetitle}{if $pagetitle} - {$pagetitle}{/if}</title>
<!-- Bootstrap --> <!-- Bootstrap -->
<link href="lib/bootstrap4/css/bootstrap.min.css" rel="stylesheet"> <link href="{static_url path="lib/bootstrap4/css/bootstrap.min.css"}" rel="stylesheet"/>
<!-- Font Awesome --> <!-- Font Awesome -->
<link href="lib/Fork-Awesome-1.1.7/css/fork-awesome.min.css" rel="stylesheet"> <link href="{static_url path="lib/Fork-Awesome-1.1.7/css/fork-awesome.min.css"}" rel="stylesheet"/>
<link href="css/style.css" rel="stylesheet"> <link href="{static_url path="css/style.css"}" rel="stylesheet"/>
{foreach $css as $file} {foreach $css as $path}
<link href="{$file}" rel="stylesheet"> <link href="{$path|escape:"quotes"}" rel="stylesheet"/>
{/foreach} {/foreach}
</head> </head>
<body> <body>
@ -34,7 +33,7 @@
{block name="navbar"} {block name="navbar"}
<nav class="navbar navbar-expand-lg navbar-light bg-light"> <nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href=""> <a class="navbar-brand" href="">
<img id="logo" src="images/logo.png" alt="Logo" title="Logo"/> <img id="logo" src="{static_url path="images/logo.png"}" alt="Logo" title="Logo"/>
</a> </a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
@ -89,12 +88,12 @@
<!-- Jquery & Bootstrap --> <!-- Jquery & Bootstrap -->
<script src="lib/jquery-3.4.1.min.js"></script> <script src="{static_url path="lib/jquery-3.4.1.min.js"}"></script>
<script src="lib/bootstrap4/js/bootstrap.bundle.min.js"></script> <script src="{static_url path="lib/bootstrap4/js/bootstrap.bundle.min.js"}"></script>
<!-- Other libs & JavaScript scripts --> <!-- Other libs & JavaScript scripts -->
{foreach $js as $file} {foreach $js as $path}
<script language="javascript" src="{$file}"></script> <script language="javascript" src="{$path|escape:"quotes"}"></script>
{/foreach} {/foreach}
{if $webstats_js_code}{$webstats_js_code}{/if} {if $webstats_js_code}{$webstats_js_code}{/if}

View file

@ -2,11 +2,12 @@ parameters:
level: 5 level: 5
paths: paths:
- src - src
- includes - example
- public_html
- bin/eesyphp
excludePaths: excludePaths:
- includes/config.local.php - example/includes/config.local.php
- example/data/tmp/templates_c
universalObjectCratesClasses:
- EesyPHP\UrlRequest
ignoreErrors: ignoreErrors:
- -
message: "#Instantiated class Mail_mime not found\\.#" message: "#Instantiated class Mail_mime not found\\.#"
@ -23,5 +24,5 @@ parameters:
- -
message: "#Variable \\$status_list might not be defined\\.#" message: "#Variable \\$status_list might not be defined\\.#"
paths: paths:
- includes/cli.php - example/includes/cli.php

View file

@ -40,35 +40,43 @@ class App {
} }
// Load overwrite configuration file // Load overwrite configuration file
foreach (self :: get_option('overwrite_config_files', array(), 'array') as $file) { foreach (self :: get('overwrite_config_files', array(), 'array') as $file) {
$file = Config::replace_variables($file); $file = Config::replace_variables($file);
if (is_file($file)) Config::load($file, true); if (is_file($file)) Config::load($file, true);
} }
if (self :: get_option('sentry.enabled', true, 'bool')) if (self :: get('sentry.enabled', true, 'bool'))
SentryIntegration :: init(); SentryIntegration :: init();
$sentry_transaction = new SentryTransaction(); $sentry_transaction = new SentryTransaction();
$sentry_span = new SentrySpan('app.init', 'Application initialization'); $sentry_span = new SentrySpan('app.init', 'Application initialization');
// Define upload_tmp_dir // Define upload_tmp_dir
if (is_string(Config::get('upload_tmp_directory'))) if (is_string(self::get('upload_tmp_directory')))
ini_set('upload_tmp_dir', Config::get('upload_tmp_directory')); ini_set('upload_tmp_dir', self::get('upload_tmp_directory'));
if (self :: get_option('log.enabled', true, 'bool')) if (self :: get('log.enabled', true, 'bool'))
Log::init(); Log::init();
if (self :: get_option('session.enabled', true, 'bool')) if (self :: get('session.enabled', true, 'bool'))
Session::init(); Session::init();
if (self :: get_option('template.enabled', true, 'bool')) if (self :: get('template.enabled', true, 'bool'))
Tpl :: init(); Tpl :: init();
if (self :: get_option('url.enabled', true, 'bool')) if (self :: get('url.enabled', true, 'bool'))
Url::init(); Url::init();
if (self :: get_option('mail.enabled', true, 'bool')) if (self :: get('mail.enabled', true, 'bool'))
Email :: init(); Email :: init();
if (self :: get_option('i18n.enabled', true, 'bool')) if (self :: get('i18n.enabled', true, 'bool'))
I18n::init(); I18n::init();
$sentry_span->finish(); $sentry_span->finish();
} }
/**
* Check if the application is initialized
* @return bool
*/
public static function initialized() {
return !is_null(self :: $root_directory_path);
}
/** /**
* Get a specific option value * Get a specific option value
* *
@ -82,7 +90,7 @@ class App {
* is a string, split the value by comma (optional, default: true) * is a string, split the value by comma (optional, default: true)
* @return mixed The configuration variable value * @return mixed The configuration variable value
**/ **/
public static function get_option($key, $default=null, $cast=null, $split=true) { public static function get($key, $default=null, $cast=null, $split=true) {
return Config::get( return Config::get(
$key, $key,
Config::loaded()?Config::get($key, $default, $cast, $split):$default, Config::loaded()?Config::get($key, $default, $cast, $split):$default,

View file

@ -68,31 +68,31 @@ class Email {
public static function init($sender=null, $send_method=null, $send_params=null, $catch_all=null, public static function init($sender=null, $send_method=null, $send_params=null, $catch_all=null,
$headers=null, $php_mail_path=null, $php_mail_mime_path=null) { $headers=null, $php_mail_path=null, $php_mail_mime_path=null) {
if (is_null($sender)) if (is_null($sender))
$sender = Config::get('email.sender', null, 'string'); $sender = App::get('email.sender', null, 'string');
if ($sender) self :: $sender = $sender; if ($sender) self :: $sender = $sender;
if (is_null($send_method)) if (is_null($send_method))
$send_method = Config::get('email.send_method', null, 'string'); $send_method = App::get('email.send_method', null, 'string');
if ($send_method) self :: $send_method = $send_method; if ($send_method) self :: $send_method = $send_method;
if (is_null($send_params)) if (is_null($send_params))
$send_params = Config::get('email.send_params', null, 'array'); $send_params = App::get('email.send_params', null, 'array');
if ($send_params) self :: $send_params = $send_params; if ($send_params) self :: $send_params = $send_params;
if (is_null($catch_all)) if (is_null($catch_all))
$catch_all = Config::get('email.catch_all'); $catch_all = App::get('email.catch_all');
if ($catch_all) self :: $catch_all = $catch_all; if ($catch_all) self :: $catch_all = $catch_all;
if (is_null($headers)) if (is_null($headers))
$headers = Config::get('email.headers', null, 'array'); $headers = App::get('email.headers', null, 'array');
if ($headers) self :: $headers = $headers; if ($headers) self :: $headers = $headers;
if (is_null($php_mail_path)) if (is_null($php_mail_path))
$php_mail_path = Config::get('email.php_mail_path', null, 'string'); $php_mail_path = App::get('email.php_mail_path', null, 'string');
if ($php_mail_path) self :: $php_mail_path = $php_mail_path; if ($php_mail_path) self :: $php_mail_path = $php_mail_path;
if (is_null($php_mail_mime_path)) if (is_null($php_mail_mime_path))
$php_mail_mime_path = Config::get('email.php_mail_mime_path', null, 'string'); $php_mail_mime_path = App::get('email.php_mail_mime_path', null, 'string');
if ($php_mail_mime_path) self :: $php_mail_mime_path = $php_mail_mime_path; if ($php_mail_mime_path) self :: $php_mail_mime_path = $php_mail_mime_path;
} }

View file

@ -36,13 +36,13 @@ class I18n {
*/ */
public static function init($root_path=null, $default_locale=null) { public static function init($root_path=null, $default_locale=null) {
if (is_null($root_path)) if (is_null($root_path))
self :: $root_path = Config::get( self :: $root_path = App::get(
'i18n.root_directory', '${root_directory_path}/locales', 'string'); 'i18n.root_directory', '${root_directory_path}/locales', 'string');
if (!is_null($root_path)) if (!is_null($root_path))
self :: $root_path = $root_path; self :: $root_path = $root_path;
if (is_null($default_locale)) if (is_null($default_locale))
self :: $default_locale = Config::get('i18n.default_locale', null, 'string'); self :: $default_locale = App::get('i18n.default_locale', null, 'string');
if (!is_null($default_locale)) if (!is_null($default_locale))
self :: $default_locale = $default_locale; self :: $default_locale = $default_locale;
@ -68,7 +68,10 @@ class I18n {
else { else {
$lang = Locale::lookup( $lang = Locale::lookup(
self :: get_available_langs(), self :: get_available_langs(),
Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']), Locale::acceptFromHttp(
isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])?
$_SERVER['HTTP_ACCEPT_LANGUAGE']:null
),
true, true,
Locale::getPrimaryLanguage(self :: $default_locale) Locale::getPrimaryLanguage(self :: $default_locale)
); );
@ -111,13 +114,13 @@ class I18n {
Log :: trace("Test: "._('Hello world !')); Log :: trace("Test: "._('Hello world !'));
// JS translation file // JS translation file
$js_translation_file = "translations/$lang.js";
if ( if (
php_sapi_name() != "cli" php_sapi_name() != "cli"
&& is_file(App :: root_directory_path()."/public_html/$js_translation_file")
&& Tpl :: initialized() && Tpl :: initialized()
) { ) {
Tpl :: add_js_file(array("lib/babel.js", "js/translation.js", $js_translation_file)); Tpl :: register_static_directory(self :: $root_path, null, 'locales/');
Tpl :: add_js_file("lib/babel.js", "js/translation.js");
Tpl :: add_js_file("locales/", "$lang.js");
} }
if (php_sapi_name() == 'cli') { if (php_sapi_name() == 'cli') {

View file

@ -72,19 +72,19 @@ class Log {
if ($filepath) if ($filepath)
self :: $filepath = $filepath; self :: $filepath = $filepath;
elseif (php_sapi_name() == 'cli') elseif (php_sapi_name() == 'cli')
self :: $filepath = Config::get( self :: $filepath = App::get(
'log.cli_logfile_path', Config::get('log.cli_file_path')); 'log.cli_logfile_path', App::get('log.cli_file_path'));
else else
self :: $filepath = Config::get('log.file_path'); self :: $filepath = App::get('log.file_path');
// Set log level // Set log level
self :: set_level($level?$level:Config::get('log.level')); self :: set_level($level?$level:App::get('log.level'));
// Log PHP errors // Log PHP errors
if (!is_null($php_errors_levels)) { if (!is_null($php_errors_levels)) {
self :: $php_errors_levels = $php_errors_levels; self :: $php_errors_levels = $php_errors_levels;
} }
elseif ($levels = Config::get('log.php_errors_levels', array(), 'array')) { elseif ($levels = App::get('log.php_errors_levels', array(), 'array')) {
$code = 'self :: $php_errors_levels = '; $code = 'self :: $php_errors_levels = ';
while($level = array_shift($levels)) { while($level = array_shift($levels)) {
if (!is_string($level)) continue; if (!is_string($level)) continue;

View file

@ -38,11 +38,11 @@ class SentryIntegration {
public static function init($dsn=null, $traces_sample_rate=null, public static function init($dsn=null, $traces_sample_rate=null,
$php_error_types=null) { $php_error_types=null) {
\Sentry\init([ \Sentry\init([
'dsn' => $dsn?$dsn:Config::get('sentry.dsn'), 'dsn' => $dsn?$dsn:App::get('sentry.dsn'),
'traces_sample_rate' => ( 'traces_sample_rate' => (
$traces_sample_rate? $traces_sample_rate?
$traces_sample_rate: $traces_sample_rate:
Config::get('sentry.traces_sample_rate', 0.2, 'float') App::get('sentry.traces_sample_rate', 0.2, 'float')
), ),
]); ]);
@ -57,7 +57,7 @@ class SentryIntegration {
}); });
if (!is_array($php_error_types)) if (!is_array($php_error_types))
$php_error_types = Config::get( $php_error_types = App::get(
'sentry.php_error_types', self :: $php_error_types, 'array' 'sentry.php_error_types', self :: $php_error_types, 'array'
); );
self :: $php_error_types = array(); self :: $php_error_types = array();

View file

@ -27,7 +27,7 @@ class Session {
// Define session max duration // Define session max duration
if (is_null($max_duration)) if (is_null($max_duration))
$max_duration = Config::get('session.max_duration', null, 'int'); $max_duration = App::get('session.max_duration', null, 'int');
if (is_int($max_duration)) if (is_int($max_duration))
self :: $max_duration = $max_duration; self :: $max_duration = $max_duration;
@ -44,7 +44,7 @@ class Session {
// Handle session timeout // Handle session timeout
if (is_null($timeout)) if (is_null($timeout))
$timeout = Config::get('session.timeout', null, 'int'); $timeout = App::get('session.timeout', null, 'int');
if (is_int($timeout) && $timeout) { if (is_int($timeout) && $timeout) {
if (!isset($_SESSION['session_last_access'])) { if (!isset($_SESSION['session_last_access'])) {
Log :: debug('Set initial session last access'); Log :: debug('Set initial session last access');

View file

@ -5,6 +5,7 @@ namespace EesyPHP;
use Exception; use Exception;
use Smarty; use Smarty;
use Smarty_Security; use Smarty_Security;
use League\MimeTypeDetection\ExtensionMimeTypeDetector;
class Tpl { class Tpl {
@ -38,6 +39,12 @@ class Tpl {
*/ */
public static bool $_debug_ajax; public static bool $_debug_ajax;
/**
* Static directories
* @var array
*/
private static array $static_directories = array();
/** /**
* CSS files to load in next displayed page * CSS files to load in next displayed page
* @var array<string> * @var array<string>
@ -50,6 +57,18 @@ class Tpl {
*/ */
private static array $js_files = 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 * Initialization
* @param string $templates_dir Smarty templates directory path * @param string $templates_dir Smarty templates directory path
@ -59,14 +78,18 @@ class Tpl {
* @param bool $debug_ajax Enable/disable AJAX returned data debugging in logs * @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, * (optional, default: from template.debug_ajax or debug_ajax config keys if set,
* false otherwise) * 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 * @return void
*/ */
public static function init($templates_dir=null, $templates_c_dir=null, $debug_ajax=null) { public static function init($templates_dir=null, $templates_c_dir=null, $debug_ajax=null,
$static_root_url=null) {
// Check templates/templates_c directories // Check templates/templates_c directories
if (is_null($templates_dir)) if (is_null($templates_dir))
$templates_dir = Config::get('template.directory', null, 'string'); $templates_dir = App::get('template.directory', null, 'string');
if (is_null($templates_c_dir)) if (is_null($templates_c_dir))
$templates_c_dir = Config::get('template.cache_directory', null, 'string'); $templates_c_dir = App::get('template.cache_directory', null, 'string');
if (!$templates_dir || !is_dir($templates_dir)) { if (!$templates_dir || !is_dir($templates_dir)) {
Log :: fatal( Log :: fatal(
"Template directory not found (%s)", "Template directory not found (%s)",
@ -83,9 +106,25 @@ class Tpl {
self :: $smarty->setTemplateDir($templates_dir); self :: $smarty->setTemplateDir($templates_dir);
self :: $smarty->setCompileDir($templates_c_dir); self :: $smarty->setCompileDir($templates_c_dir);
if (is_null($debug_ajax)) if (is_null($debug_ajax))
$debug_ajax = Config::get('template.debug_ajax', Config::get('debug_ajax')); $debug_ajax = App::get('template.debug_ajax', App::get('debug_ajax'));
self :: $_debug_ajax = boolval($debug_ajax); self :: $_debug_ajax = boolval($debug_ajax);
Log :: register_fatal_error_handler(array('\\EesyPHP\\Tpl', 'fatal_error')); 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);
}
} }
/** /**
@ -175,30 +214,50 @@ class Tpl {
/** /**
* Register CSS file(s) to load on next displayed page * Register CSS file(s) to load on next displayed page
* @param array<string|array<string>> $args CSS files to load * @param string|array<string> $args CSS files to load
* @return void * @return void
*/ */
public static function add_css_file(...$args) { 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) { foreach ($args as $files) {
if (!is_array($files)) $files = array($files); if (!is_array($files)) $files = array($files);
foreach ($files as $file) { foreach ($files as $file) {
if (!in_array($file, self :: $css_files)) $path = $root_url.$file;
self :: $css_files[] = $file; if (!in_array($path, self :: $css_files))
self :: $css_files[] = $path;
} }
} }
} }
/** /**
* Register JS file(s) to load on next displayed page * Register JS file(s) to load on next displayed page
* @param array<string|array<string>> $args JS files to load * @param string|array<string> $args JS files to load
* @return void * @return void
*/ */
public static function add_js_file(...$args) { 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) { foreach ($args as $files) {
if (!is_array($files)) $files = array($files); if (!is_array($files)) $files = array($files);
foreach ($files as $file) { foreach ($files as $file) {
if (!in_array($file, self :: $js_files)) $path = $root_url.$file;
self :: $js_files[] = $file; if (!in_array($path, self :: $js_files))
self :: $js_files[] = $path;
} }
} }
} }
@ -360,4 +419,128 @@ class Tpl {
public static function templates_directory() { public static function templates_directory() {
return isset(self :: $templates_directory)?self :: $templates_directory:null; 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>$root_url)(?P<path>.*)#";
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<string,mixed> $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;
}
} }

View file

@ -57,7 +57,7 @@ class Url {
*/ */
public static function init($public_root_url=null, $api_mode=false) { public static function init($public_root_url=null, $api_mode=false) {
if (is_null($public_root_url)) if (is_null($public_root_url))
$public_root_url = Config::get('public_root_url', null, 'string'); $public_root_url = App::get('public_root_url', null, 'string');
if (is_string($public_root_url) && $public_root_url) { if (is_string($public_root_url) && $public_root_url) {
// Check URL end // Check URL end
if (substr(self :: $public_root_url, -1) == '/') if (substr(self :: $public_root_url, -1) == '/')
@ -201,6 +201,15 @@ class Url {
return false; return false;
} }
/**
* Trigger a 404 HTTP error
* @param UrlRequest|null $request Current UrlRequest object (optional, default: null)
* @return void
*/
public static function trigger_error_404($request=null) {
call_user_func_array(self :: $error_404_handler, array($request));
}
/** /**
* Interprets the requested URL and return the corresponding UrlRequest object * Interprets the requested URL and return the corresponding UrlRequest object

View file

@ -2,6 +2,8 @@
namespace EesyPHP; namespace EesyPHP;
use League\MimeTypeDetection\ExtensionMimeTypeDetector;
/* /*
* Parser/formater values helpers * Parser/formater values helpers
*/ */
@ -120,12 +122,28 @@ function cast($value, $type, $split=false) {
/* /*
* Generic file/directory helpers * Generic file/directory helpers
*/ */
function dump_file($file_path, $max_age=3600) {
/**
* Dump file content to propose its download
* @param string $file_path The file path
* @param string|null $mime_type The file MIME type (optional, default: auto-detected)
* @param int|null $max_age Max age in second of this file in browser cache (optional,
* default: null=1h, set to False to disable Cache-Control header)
* @return void
*/
function dump_file($file_path, $mime_type=null, $max_age=null) {
if (is_file($file_path)) { if (is_file($file_path)) {
header('Content-Type: '.mime_content_type($file_path)); if (is_null($mime_type)) {
$detector = new ExtensionMimeTypeDetector();
$mime_type = $detector->detectMimeTypeFromFile($file_path);
Log::trace('MIME type detected for "%s" is "%s"', $file_path, $mime_type);
}
header("Content-Type: $mime_type");
$last_modified_time = filemtime($file_path); $last_modified_time = filemtime($file_path);
$etag = md5_file($file_path); $etag = md5_file($file_path);
header("Cache-Control: max-age=$max_age, must-revalidate"); $max_age = is_null($max_age)?3600:$max_age;
if ($max_age)
header("Cache-Control: max-age=$max_age, must-revalidate");
header("Last-Modified: ".gmdate("D, d M Y H:i:s", $last_modified_time)." GMT"); header("Last-Modified: ".gmdate("D, d M Y H:i:s", $last_modified_time)." GMT");
header("Etag: $etag"); header("Etag: $etag");
@ -147,8 +165,7 @@ function dump_file($file_path, $max_age=3600) {
readfile($file_path); readfile($file_path);
exit(); exit();
} }
header("HTTP/1.1 404 Not found"); Url :: trigger_error_404();
exit();
} }
function delete_directory($dir, $recursive=true) { function delete_directory($dir, $recursive=true) {

View file

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View file

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View file

Before

Width:  |  Height:  |  Size: 470 KiB

After

Width:  |  Height:  |  Size: 470 KiB

Some files were not shown because too many files have changed in this diff Show more