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.
This commit is contained in:
Benjamin Renard 2023-02-08 02:27:15 +01:00
parent 5496ff55c0
commit f2edf4910a
19 changed files with 725 additions and 292 deletions

View file

@ -23,7 +23,10 @@
"smarty-gettext/smarty-gettext": "^1.6",
"smarty-gettext/tsmarty2c": "^0.2.1",
"sepia/po-parser": "^6.0",
"sentry/sdk": "^3.3"
"sentry/sdk": "^3.3",
"ext-pdo": "^7.3",
"ext-json": "^7.3",
"ext-yaml": "^2.0"
},
"require-dev": {
"phpstan/phpstan": "^1.9"

10
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "372e3ce1ce40fd466379b8709b0f7c5a",
"content-hash": "bbb5f6bafa8928dce90eb5d63dbc0376",
"packages": [
{
"name": "brenard/php-unidecode",
@ -2856,7 +2856,11 @@
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform": {
"ext-pdo": "^7.4",
"ext-yaml": "^2.1",
"ext-json": "^7.4"
},
"platform-dev": [],
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.0.0"
}

2
includes/.gitignore vendored
View file

@ -1 +1 @@
config.local.php
config.local.yml

View file

@ -1,155 +0,0 @@
<?php
// Public root URL
$public_root_url = "http://127.0.0.1/eesyphp";
// Application root data directory
$data_dir = $root_dir_path."/data";
// Temporary files root directory
$tmp_root_dir = "$data_dir/tmp";
// Temporary uploading files directory
$upload_tmp_dir = "$tmp_root_dir/uploading";
// Main pagetitle
$main_pagetitle = "Eesyphp";
// Theme CSS file
$included_css_files = array();
// Log configuration
// Logs directory path
$logs_dir_path = "$data_dir/logs";
// Log file path
if (php_sapi_name() == "cli")
$log_file = "$logs_dir_path/cli.log";
else
$log_file = "$logs_dir_path/app.log";
// Log level (TRACE / DEBUG / INFO / WARNING / ERROR / FATAL)
$log_level = 'INFO';
// Debug Ajax request/response
$debug_ajax = false;
// Log PHP errors levels (as specified to set_error_handler())
// Default:
// - In TRACE or DEBUG: E_ALL & ~E_STRICT
// - Otherwise: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
// $log_php_errors_levels = E_ALL & ~E_STRICT;
/*
* Sentry configuration
*/
// Sentry DSN
$sentry_dsn = null;
// Log PHP errors in Sentry: list of errors types to logs
// Note: must also match with $log_php_errors_levels.
// See: https://www.php.net/manual/fr/errorfunc.constants.php
$sentry_php_error_types = array(
E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR,
E_RECOVERABLE_ERROR,E_DEPRECATED,
);
// Traces sample rate (between 0 and 1)
// Note: this parameter permit to determine how many transactions (=~ access) are traced and
// sent to Sentry, for instance, 0.2 meen that 20% of the transactions will be traced. In dev
// mode, set to 1 if you want all transactions are sent to Sentry.
// Default: 0.2
$sentry_traces_sample_rate = 0.2;
/*
* Smarty template configuration
*/
// Smarty directories
$smarty_templates_dir = "$root_dir_path/templates";
$smarty_templates_c_dir = "$tmp_root_dir/templates_c";
// Default locale (see locales directory for available languages list)
$default_locale = 'en_US.UTF8';
// Session
$session_timeout = 1800; // Session timeout dur to inactivity (in seconds)
$session_max_duration = 43200; // Session max duration (in seconds, default : 12h)
/**
* Database configuration
**/
// Sqlite
$db_dsn="sqlite:$root_dir_path/data/db.sqlite3";
$db_user=null;
$db_pwd=null;
$db_options=array();
// Date/Datetime format in database (strptime format)
$db_date_format = '%s';
$db_datetime_format = '%s';
/*
// Postgresql
$db_dsn="pgsql:host=localhost;port=5432;dbname=items";
$db_user="items";
$db_pwd="items";
$db_options=array();
// Date/Datetime format in database (strptime format)
$db_date_format = '%Y-%m-%d'; // Exemple : 2018-10-12
$db_datetime_format = '%Y-%m-%d %H:%M:%S'; // Exemple : 2018-10-12 18:06:59
*/
/*
// MariaDB / MySQL
$db_dsn="mysql:host=localhost;dbname=items";
$db_user="items";
$db_pwd="items";
$db_options=array();
// Date/Datetime format in database (strptime format)
$db_date_format = '%Y-%m-%d'; // Exemple : 2018-10-12
$db_datetime_format = '%Y-%m-%d %H:%M:%S'; // Exemple : 2018-10-12 18:06:59
*/
/*
* Config Mail
*/
// PHP PEAR Mail and Mail_Mine paths
$php_mail_path = 'Mail.php';
$php_mail_mime_path = 'Mail/mime.php';
/*
* Sending method :
* - mail : use PHP mail function
* - sendmail : use sendmail system command
* - smtp : use an SMTP server (PHP PEAR Net_SMTP required)
*/
$mail_send_method = 'smtp';
/*
* Sending parameters
* See : http://pear.php.net/manual/en/package.mail.mail.factory.php
*/
$mail_send_params = NULL;
// Headers add to all e-mails sent
$mail_headers = array();
// Email sender address (for all emails sent by the application)
$mail_sender = "noreply@example.org";
// Catch all e-mails sent to a configured e-mail address
$mail_catch_all = false;
/**
* Web Stats JS code
*/
$webstats_js_code = '';
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

170
includes/config.yml Normal file
View file

@ -0,0 +1,170 @@
# Public root URL
public_root_url: "http://127.0.0.1/eesyphp"
# Application root data directory
data_directory: "${root_directory_path}/data"
# Temporary files root directory
tmp_root_directory: "${data_directory}/tmp"
# Temporary uploading files directory
upload_tmp_directory: ${tmp_root_directory}/uploading"
# Main pagetitle
main_pagetitle: "Eesyphp"
# Theme CSS file
included_css_files:
#- css/custom.css
included_js_files:
#- js/custom.js
# Debug Ajax request/response
debug_ajax: false
#
# Log configuration
#
log:
# Logs directory path
directory_path: "${data_directory}/logs"
# CLI log file path
cli_file_path: "${log.directory_path}/cli.log"
# Log file path
file_path: "${log.directory_path}/app.log"
# Log level (TRACE / DEBUG / INFO / WARNING / ERROR / FATAL)
level: 'INFO'
# Log PHP errors levels (as specified to set_error_handler())
# Note: expected a list of PHP error level constants that will be resolved and combine
# with a bitwise operator. After the first value, the bitwise operator "|" (OR) or "^" (XOR)
# could be specified as contanst name prefix, otherwise the "&" (AND) operator will be used.
# The constant name could also be prefix with "~" or "!" (NOT) bitwise operator.
#
# Default:
# - In TRACE or DEBUG: E_ALL & ~E_STRICT
# - Otherwise: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
#log_php_errors_levels:
# - E_ALL
# - ~E_NOTICE
# - ~E_STRICT
# - ~E_DEPRECATED
#
# Sentry configuration
#
sentry:
# Sentry DSN
#dsn: null
# Log PHP errors in Sentry: list of errors types to logs
# See: https://www.php.net/manual/fr/errorfunc.constants.php
php_error_types:
- "E_ERROR"
- "E_PARSE"
- "E_CORE_ERROR"
- "E_COMPILE_ERROR"
- "E_USER_ERROR"
- "E_RECOVERABLE_ERROR"
- "E_DEPRECATED"
# Traces sample rate (between 0 and 1)
# Note: this parameter permit to determine how many transactions (=~ access) are traced and
# sent to Sentry, for instance, 0.2 meen that 20% of the transactions will be traced. In dev
# mode, set to 1 if you want all transactions are sent to Sentry.
# Default: 0.2
traces_sample_rate: 0.2
#
# Smarty template configuration
#
template:
# Smarty directories
directory: "${root_directory_path}/templates"
cache_directory: "${tmp_root_directory}/templates_c"
#
# Translations
#
i18n:
# Default locale (see locales directory for available languages list)
default_locale: 'en_US.UTF8'
#
# Session
#
session:
timeout: 1800 # Session timeout dur to inactivity (in seconds)
max_duration: 43200 # Session max duration (in seconds, default : 12h)
#
# Database configuration
#
db:
# Sqlite
dsn: "sqlite:${data_directory}/db.sqlite3"
options: null
# Date/Datetime format in database (strptime format)
date_format: '%s'
datetime_format: '%s'
# Postgresql
#dsn: "pgsql:host=localhost;port=5432;dbname=items"
#user: "items"
#pwd: "items"
#options: null
# Date/Datetime format in database (strptime format)
#date_format: '%Y-%m-%d' # Exemple : 2018-10-12
#datetime_format: '%Y-%m-%d %H:%M:%S' # Exemple : 2018-10-12 18:06:59
# MariaDB / MySQL
#dsn: "mysql:host=localhost;dbname=items"
#user: "items"
#pwd: "items"
#options: null
# Date/Datetime format in database (strptime format)
#date_format: '%Y-%m-%d' # Exemple : 2018-10-12
#datetime_format: '%Y-%m-%d %H:%M:%S' # Exemple : 2018-10-12 18:06:59
#
# Email configuration
#
email:
# PHP PEAR Mail and Mail_Mine paths
php_mail_path: 'Mail.php'
php_mail_mime_path: 'Mail/mime.php'
# Sending method :
# - mail : use PHP mail function
# - sendmail : use sendmail system command
# - smtp : use an SMTP server (PHP PEAR Net_SMTP required)
send_method: 'smtp'
# Sending parameters
# See : http:#pear.php.net/manual/en/package.mail.mail.factory.php
send_params: NULL
# Headers add to all e-mails sent
headers:
#- "CC: support@example.com"
# Email sender address (for all emails sent by the application)
sender: "noreply@example.org"
# Catch all e-mails sent to a configured e-mail address
catch_all: false
# Web Stats JS code
webstats_js_code: ''
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -1,13 +1,9 @@
<?php
use EesyPHP\Email;
use EesyPHP\App;
use EesyPHP\Config;
use EesyPHP\I18n;
use EesyPHP\Log;
use EesyPHP\SentryIntegration;
use EesyPHP\SentrySpan;
use EesyPHP\SentryTransaction;
use EesyPHP\Session;
use EesyPHP\Url;
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED);
@ -31,39 +27,20 @@ set_include_path($root_dir_path.'/includes' . PATH_SEPARATOR . get_include_path(
// Load composer autoload.php
require("$root_dir_path/vendor/autoload.php");
// Load configuration
require_once('config.inc.php');
// Load local configuration file is present
if (is_file("$root_dir_path/includes/config.local.php")) {
require "$root_dir_path/includes/config.local.php";
}
SentryIntegration :: init(
isset($sentry_dsn)?$sentry_dsn:null,
isset($sentry_traces_sample_rate)?$sentry_traces_sample_rate:null,
isset($sentry_php_error_types)?$sentry_php_error_types:null,
// Initialize EesyPHP application
App::init(
"$root_dir_path/includes/config.yml",
array(
'overwrite_config_files' => array(
"$root_dir_path/includes/config.local.yml",
),
),
$root_dir_path
);
$sentry_transaction = new SentryTransaction();
$sentry_span = new SentrySpan('core.init', 'Core initialization');
// Define upload_tmp_dir
if (isset($upload_tmp_dir))
ini_set('upload_tmp_dir', $upload_tmp_dir);
if (!isset($log_file))
die('Log file path not configured');
Log::init(
$log_file,
isset($log_level)?$log_level:null,
isset($log_php_errors_levels)?$log_php_errors_levels:null
);
require_once('functions.php');
Session :: init(
isset($session_max_duration)?$session_max_duration:null,
isset($session_timeout)?$session_timeout:null
);
// Nomenclatures
$status_list = array (
@ -72,29 +49,14 @@ $status_list = array (
'refused' => I18n :: ___('Refused'),
'archived' => I18n :: ___('Archived'),
);
foreach($status_list as $key => $value)
$status_list[$key] = _($value);
require_once('cli.php');
require_once('templates.php');
Url::init(isset($public_root_url)?$public_root_url:null);
require_once('url-helpers.php');
require_once('db.php');
Email :: init(
isset($mail_sender)?$mail_sender:null,
isset($mail_send_method)?$mail_send_method:null,
isset($mail_send_params)?$mail_send_params:null,
isset($mail_catch_all)?$mail_catch_all:null,
isset($mail_headers)?$mail_headers:null,
isset($php_mail_path)?$php_mail_path:null,
isset($php_mail_mime_path)?$php_mail_mime_path:null,
);
// Initialize translation
I18n::init(
"$root_dir_path/locales",
isset($default_locale)?$default_locale:null);
foreach($status_list as $key => $value)
$status_list[$key] = _($value);
$sentry_span->finish();
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

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

View file

@ -1,5 +1,6 @@
<?php
use EesyPHP\Config;
use EesyPHP\Date;
use EesyPHP\Hook;
use EesyPHP\Log;
@ -7,12 +8,6 @@ use EesyPHP\Tpl;
use function EesyPHP\format_size;
Tpl :: init(
isset($smarty_templates_dir)?$smarty_templates_dir:null,
isset($smarty_templates_c_dir)?$smarty_templates_c_dir:null,
isset($debug_ajax)?$debug_ajax:false
);
if (php_sapi_name() == "cli")
return true;
@ -28,18 +23,16 @@ Tpl :: enable_security_mode(
);
// Defined some global template variables
Tpl :: assign('public_root_url', isset($public_root_url)?$public_root_url:'/');
Tpl :: assign('main_pagetitle', isset($main_pagetitle)?$main_pagetitle:null);
Tpl :: assign('public_root_url', Config::get('public_root_url', '/', 'string'));
Tpl :: assign('main_pagetitle', Config::get('main_pagetitle', null, 'string'));
Tpl :: assign('session_key', $_SESSION['session_key']);
// Handle CSS & JS files included
if (isset($included_css_files) && is_array($included_css_files))
Tpl :: add_css_file($included_css_files);
if (isset($included_js_files) && is_array($included_js_files))
Tpl :: add_css_file($included_js_files);
Tpl :: add_css_file(Config::get('included_css_files', array(), 'array'));
Tpl :: add_js_file(Config::get('included_js_files', array(), 'array'));
function define_common_template_variables($event) {
global $status_list, $admin, $webstats_js_code;
global $status_list, $admin;
Tpl :: assign(
'status_list',
isset($status_list) && is_array($status_list)?
@ -48,7 +41,7 @@ function define_common_template_variables($event) {
Tpl :: assign('admin', isset($admin) && $admin);
Tpl :: assign(
'webstats_js_code',
isset($webstats_js_code)?$webstats_js_code:null);
Config::get('webstats_js_code', null, 'string'));
}
Hook :: register('before_displaying_template', 'define_common_template_variables');

View file

@ -20,10 +20,6 @@ parameters:
-
message: "#Call to static method isError\\(\\) on an unknown class PEAR\\.#"
path: src/Email.php
-
message: "#Variable \\$root_dir_path might not be defined\\.#"
paths:
- includes/config.inc.php
-
message: "#Variable \\$status_list might not be defined\\.#"
paths:

103
src/App.php Normal file
View file

@ -0,0 +1,103 @@
<?php
namespace EesyPHP;
class App {
/**
* Options
* @var array
*/
protected static $options = array();
/**
* Root directory path
* @var string|null
*/
protected static $root_directory_path = null;
/**
* Initialization
* @param string|null $config_file Application configuration file path
* @param array|null $options Application options (default: null)
* @param string|null $root_directory_path Application root directory path (default: null)
* @return void
*/
public static function init($config_file, $options=null, $root_directory_path=null) {
if (is_array($options)) self :: $options = $options;
if (is_null($root_directory_path)) {
$traces = debug_backtrace();
$root_directory_path = realpath(dirname($traces[0]['file']).'/../');
}
self :: $root_directory_path = $root_directory_path;
Config::register_extra_variable('root_directory_path', $root_directory_path);
$config_file = Config::replace_variables($config_file);
if (!Config::load($config_file)) {
Log::fatal('Fail to load configuration file (%s)', $config_file);
exit(1);
}
// Load overwrite configuration file
foreach (self :: get_option('overwrite_config_files', array(), 'array') as $file) {
$file = Config::replace_variables($file);
if (is_file($file)) Config::load($file, true);
}
if (self :: get_option('sentry.enabled', true, 'bool'))
SentryIntegration :: init();
$sentry_transaction = new SentryTransaction();
$sentry_span = new SentrySpan('app.init', 'Application initialization');
// Define upload_tmp_dir
if (is_string(Config::get('upload_tmp_directory')))
ini_set('upload_tmp_dir', Config::get('upload_tmp_directory'));
if (self :: get_option('log.enabled', true, 'bool'))
Log::init();
if (self :: get_option('session.enabled', true, 'bool'))
Session::init();
if (self :: get_option('template.enabled', true, 'bool'))
Tpl :: init();
if (self :: get_option('url.enabled', true, 'bool'))
Url::init();
if (self :: get_option('mail.enabled', true, 'bool'))
Email :: init();
if (self :: get_option('i18n.enabled', true, 'bool'))
I18n::init();
$sentry_span->finish();
}
/**
* Get a specific option value
*
* @param string $key The configuration variable key
* @param mixed $default The default value to return if configuration variable
* is not set (Default : null)
* @param string $cast The type of expected value. The configuration variable
* value will be cast as this type. Could be : bool, int,
* float or string. (Optional, default : raw value)
* @param bool $split If true, $cast is 'array' and value retreived from configuration
* is a string, split the value by comma (optional, default: true)
* @return mixed The configuration variable value
**/
public static function get_option($key, $default=null, $cast=null, $split=true) {
return Config::get(
$key,
Config::loaded()?Config::get($key, $default, $cast, $split):$default,
$cast,
$split,
self :: $options
);
}
/**
* Retreive application root directory path
* @return string|null
*/
public static function root_directory_path() {
return self :: $root_directory_path?self :: $root_directory_path:'.';
}
}

209
src/Config.php Normal file
View file

@ -0,0 +1,209 @@
<?php
Namespace EesyPHP;
Class Config {
/**
* Configuration file path
* @var string|null
*/
private static $filepath = null;
/**
* Loaded configuration data
* @var array|null
*/
private static $config = null;
/**
* Extra registered variables usable in configuration file
* @var array
*/
private static $extra_variables = array();
/**
* Load configuration file
* @param string $filepath The configuration file path
* @param bool $extra Load as an extra configuration file (default: false)
* @return bool
*/
public static function load($filepath, $extra=false) {
if (!file_exists($filepath)) {
Log :: error("Configuration file not found ($filepath)");
return false;
}
$config = yaml_parse_file($filepath);
if (!is_array($config) || empty($config)) {
Log :: error("Fail to load %sconfiguration file (%s)", $extra?'extra ':'', $filepath);
return false;
}
if (!$extra || !is_array(self :: $config))
self :: $config = $config;
else
self :: $config = array_replace_recursive(self :: $config, $config);
if (!$extra)
self :: $filepath = $filepath;
return true;
}
/**
* Check if configuration is loaded
* @return bool
*/
public static function loaded() {
return is_array(self :: $config);
}
/**
* Save configuration
* @param string|null $filepath The configuration file path
* @param bool $overwrite Overwrite existing file (optional, default: true)
* @return bool
*/
public static function save($filepath=null, $overwrite=true) {
if (!self :: loaded()) {
Log :: error("Configuration not loaded, can't save it.");
return false;
}
if (!$filepath) $filepath = self :: $filepath;
if (!$filepath) {
Log :: error("No configuration file set or pass as parameter");
return false;
}
if (file_exists($filepath)) {
if (!$overwrite) {
Log :: error("Configuration file already exist (%s)", $filepath);
return false;
}
if (!is_writable($filepath)) {
Log :: error(
"Configuration file already exist and is not writable (%s)",
$filepath);
return false;
}
}
if (!yaml_emit_file($filepath, self :: $config)) {
Log :: error("Fail to save configuration in '%s'", $filepath);
return false;
}
Log :: info("Configuration saved in '%s'", $filepath);
return true;
}
/**
* Register extra variable usable in configuration file
* @param string $key The variable key
* @param mixed $value The variable value
*/
public static function register_extra_variable($key, $value) {
self :: $extra_variables[$key] = $value;
}
/**
* Get a specific configuration variable value
*
* @param string $key The configuration variable key
* @param mixed $default The default value to return if configuration variable
* is not set (Default : null)
* @param string $cast The type of expected value. The configuration variable
* value will be cast as this type. Could be : bool, int,
* float or string. (Optional, default : raw value)
* @param bool $split If true, $cast=='array' and $value is a string, split
* the value by comma (optional, default: false)
* @param array|null $config Optional configuration to use instead of current loaded configuration
* @return mixed The configuration variable value
**/
public static function get($key, $default=null, $cast=null, $split=false, &$config=null) {
if (array_key_exists($key, self :: $extra_variables)) {
$value = self :: $extra_variables[$key];
}
else if (!is_array($config) && !self :: loaded()) {
Log :: fatal('Configuration not loaded (on getting %s)', $key);
exit(1);
}
else {
$exploded_key = explode('.', $key);
if (!is_array($exploded_key)) return self :: replace_variables($default);
$value = is_array($config)?$config:self :: $config;
foreach ($exploded_key as $k) {
if (!is_array($value) || !isset($value[$k]))
return self :: replace_variables($default);
$value = $value[$k];
}
}
return self :: replace_variables(cast($value, $cast, $split));
}
/**
* Replace variable in specified value
* @param mixed $value
* @return mixed
*/
public static function replace_variables($value) {
if (is_array($value)) {
foreach(array_keys($value) as $key) {
if (is_string($value[$key]) || is_array($value[$key]))
$value[$key] = self :: replace_variables($value[$key]);
}
}
else if (is_string($value)) {
$iteration = 0;
while (preg_match('/\$\{([^\}]+)\}/', $value, $m)) {
if ($iteration > 20) {
Log::fatal('Config::replace_variables(%s): max iteration reached');
return $value;
}
$value = str_replace($m[0], self :: get($m[1], '', 'string'), $value);
$iteration++;
}
}
return $value;
}
/**
* Get list of keys of a specific configuration variable
*
* @param string $key The configuration variable key
*
* @return array An array of the keys of a specific configuration variable
**/
public static function keys($key) {
$value = self :: get($key);
return (is_array($value)?array_keys($value):array());
}
/**
* Set a configuration variable
*
* @param string $key The configuration variable key
* @param mixed $value The configuration variable value
* @param array|null &$config Optional configuration to use instead of current loaded configuration
*
* @return boolean
**/
public static function set($key, $value, &$config=null) {
$exploded_key = explode('.', $key);
if (!is_array($exploded_key)) return false;
if (is_array($config)) {
$parent = &$config;
}
else {
if (!is_array(self :: $config))
self :: $config = array();
$parent = &self :: $config;
}
for ($i=0; $i < count($exploded_key) - 1; $i++) {
$k = $exploded_key[$i];
if (!array_key_exists($k, $config))
$config[$k] = array();
$config = &$config[$k];
}
$config[array_pop($exploded_key)] = $value;
return true;
}
}
# vim: tabstop=4 shiftwidth=4 softtabstop=4 expandtab

View file

@ -59,18 +59,40 @@ class Email {
/**
* Initialization
* @param string|null $php_mail_path PHP PEAR Mail lib path (optional, default: Mail.php)
* @param string|null $php_mail_mime_path PHP PEAR Mail lib path (optional, default: Mail/mime.php)
* @param string|null $php_mail_path PHP PEAR Mail lib path (optional, default: from
* email.php_mail_path config key if set, 'Mail.php' otherwise)
* @param string|null $php_mail_mime_path PHP PEAR Mail lib path (optional, default: from
* email.php_mail_mime_path config key if set, 'Mail/mime.php' otherwise)
* @return void
*/
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) {
if (is_null($sender))
$sender = Config::get('email.sender', null, 'string');
if ($sender) self :: $sender = $sender;
if (is_null($send_method))
$send_method = Config::get('email.send_method', null, 'string');
if ($send_method) self :: $send_method = $send_method;
if (is_null($send_params))
$send_params = Config::get('email.send_params', null, 'array');
if ($send_params) self :: $send_params = $send_params;
if (is_null($catch_all))
$catch_all = Config::get('email.catch_all');
if ($catch_all) self :: $catch_all = $catch_all;
if (is_null($headers))
$headers = Config::get('email.headers', null, 'array');
if ($headers) self :: $headers = $headers;
if (is_null($php_mail_path))
$php_mail_path = Config::get('email.php_mail_path', null, 'string');
if ($php_mail_path) self :: $php_mail_path = $php_mail_path;
if (is_null($php_mail_mime_path))
$php_mail_mime_path = Config::get('email.php_mail_mime_path', null, 'string');
if ($php_mail_mime_path) self :: $php_mail_mime_path = $php_mail_mime_path;
}

View file

@ -27,15 +27,22 @@ class I18n {
* system.
*
* @param string|null $root_path The root directory path of translation files
* (optional, default: ./locales)
* (optional, default: from i18n.root_directory config key if set,
* '${root_directory_path}/locales' otherwise)
* @param string|null $default_locale The default locale
* (optional, default: en_US.UTF8)
* (optional, default: from i18n.default_locale config key if set,
* 'en_US.UTF8' otherwise)
* @return void
*/
public static function init($root_path=null, $default_locale=null) {
global $root_dir_path;
if (is_null($root_path))
self :: $root_path = Config::get(
'i18n.root_directory', '${root_directory_path}/locales', 'string');
if (!is_null($root_path))
self :: $root_path = $root_path;
if (is_null($default_locale))
self :: $default_locale = Config::get('i18n.default_locale', null, 'string');
if (!is_null($default_locale))
self :: $default_locale = $default_locale;
@ -50,7 +57,11 @@ class I18n {
$lang = $_REQUEST['lang'];
Log :: trace("Select lang from request parameter: '$lang'");
}
elseif (isset($_SESSION['lang']) && in_array($_SESSION['lang'], $available_langs) && !isset($_REQUEST['reset_lang'])) {
elseif (
isset($_SESSION['lang'])
&& in_array($_SESSION['lang'], $available_langs)
&& !isset($_REQUEST['reset_lang'])
) {
$lang = $_SESSION['lang'];
Log :: trace("Restore lang from session: '$lang'");
}
@ -103,8 +114,7 @@ class I18n {
$js_translation_file = "translations/$lang.js";
if (
php_sapi_name() != "cli"
&& isset($root_dir_path) && $root_dir_path
&& is_file("$root_dir_path/public_html/$js_translation_file")
&& is_file(App :: root_directory_path()."/public_html/$js_translation_file")
&& Tpl :: initialized()
) {
Tpl :: add_js_file(array("lib/babel.js", "js/translation.js", $js_translation_file));
@ -250,14 +260,12 @@ class I18n {
* @return void
*/
public static function cli_extract_messages($command_args) {
global $root_dir_path;
// Store list of generated POT files
$pot_files = array();
// List PHP files to parse
$php_files = run_external_command(
array('find', escapeshellarg($root_dir_path), '-name', "'*.php'"),
array('find', escapeshellarg(App :: root_directory_path()), '-name', "'*.php'"),
null, // no STDIN data
false // do not escape command args (already done)
);
@ -284,7 +292,7 @@ class I18n {
// List JS files to parse
$js_files = run_external_command(
array('find', escapeshellarg("$root_dir_path/public_html/js"), '-name', "'*.js'"),
array('find', escapeshellarg(App :: root_directory_path()."/public_html/js"), '-name', "'*.js'"),
null, // no STDIN data
false // do not escape command args (already done)
);
@ -313,7 +321,7 @@ class I18n {
// Extract messages from templates files using tsmarty2c.php
$result = run_external_command(
array (
"$root_dir_path/vendor/bin/tsmarty2c.php",
App :: root_directory_path()."/vendor/bin/tsmarty2c.php",
"-o", self :: $root_path."/templates-messages.pot",
Tpl :: templates_directory(),
)
@ -359,8 +367,6 @@ class I18n {
* @return bool
*/
public static function cli_update_messages($command_args) {
global $root_dir_path;
$compendium_args = array();
foreach ($command_args as $path) {
if (!file_exists($path))
@ -435,7 +441,7 @@ class I18n {
return !$error;
}
Log :: fatal(_("Fail to open root lang directory (%s)."), $root_dir_path);
Log :: fatal(_("Fail to open root lang directory (%s)."), App :: root_directory_path());
return false;
}
@ -447,8 +453,6 @@ class I18n {
* @return bool
*/
public static function cli_compile_messages($command_args) {
global $root_dir_path;
if ($dh = opendir(self :: $root_path)) {
$error = False;
while (($file = readdir($dh)) !== false) {
@ -466,7 +470,7 @@ class I18n {
Log :: debug(_("Lang alias symlink found: %s -> %s"), $lang, $real_lang_dir);
// Create JSON catalog symlink (if not exists)
$js_link = "$root_dir_path/public_html/translations/$lang.js";
$js_link = App :: root_directory_path()."/public_html/translations/$lang.js";
$link_target = "$real_lang_dir.js";
if (!file_exists($js_link)) {
if (symlink($link_target, $js_link)) {
@ -528,7 +532,7 @@ class I18n {
// Compile messages from PO file to JSON catalog file
$json_catalog = self :: po2json($lang, $po_file);
$js_file = "$root_dir_path/public_html/translations/$lang.js";
$js_file = App :: root_directory_path()."/public_html/translations/$lang.js";
if(!$fd = fopen($js_file, 'w')) {
Log :: error(_("Fail to open %s JSON catalog file in write mode (%s)."),
$lang, $js_file);
@ -547,7 +551,7 @@ class I18n {
return !$error;
}
Log :: fatal(_("Fail to open root lang directory (%s)."), $root_dir_path);
Log :: fatal(_("Fail to open root lang directory (%s)."), App :: root_directory_path());
return false;
}

View file

@ -55,26 +55,69 @@ class Log {
// Custom fatal error handler
protected static $fatal_error_handler = null;
/*
/**
* Initialization
* @param string $filepath
* @param string|null $level
* @param int|null $php_errors_levels
* @param string $filepath The log file path
* (optional, default: from log.file_path or log.cli_file_path is set)
* @param string|null $level The log level
* (optional, default: from log.level config key if set, otherwise,
* see self :: $default_level)
* @param int|null $php_errors_levels PHP errors level as expected by set_error_handler()
* (optional, default: from log.php_errors_levels if set, E_ALL & ~E_STRICT
* if level is TRACE or DEBUG, and E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
* otherwise)
* @return void
*/
public static function init($filepath, $level=null, $php_errors_levels=null) {
self :: $filepath = $filepath;
public static function init($filepath=null, $level=null, $php_errors_levels=null) {
if ($filepath)
self :: $filepath = $filepath;
elseif (php_sapi_name() == 'cli')
self :: $filepath = Config::get(
'log.cli_logfile_path', Config::get('log.cli_file_path'));
else
self :: $filepath = Config::get('log.file_path');
// Set log level
self :: set_level($level);
self :: set_level($level?$level:Config::get('log.level'));
// Log PHP errors
if (!is_null($php_errors_levels))
if (!is_null($php_errors_levels)) {
self :: $php_errors_levels = $php_errors_levels;
elseif (in_array(self :: $level, array('DEBUG', 'TRACE')))
}
elseif ($levels = Config::get('log.php_errors_levels', array(), 'array')) {
$code = 'self :: $php_errors_levels = ';
while($level = array_shift($levels)) {
if (!is_string($level)) continue;
if (!defined($level) || !is_int(constant($level))) continue;
$code .= $level;
break;
}
foreach($levels as $level) {
if (!is_string($level)) continue;
$combine_operator = '&';
if (in_array($level[0], array('|', '^', '&'))) {
$combine_operator = $level[0];
$level = substr($level, 1);
}
$not = false;
if (in_array($level[0], array('!', '~'))) {
$not = $level[0];
$level = substr($level, 1);
}
if (!defined($level) || !is_int(constant($level))) continue;
$code .= " $combine_operator ";
if ($not) $code .= "$not";
$code .= $level;
}
$code .= ";";
eval($code);
}
elseif (in_array(self :: $level, array('DEBUG', 'TRACE'))) {
self :: $php_errors_levels = E_ALL & ~E_STRICT;
else
}
else {
self :: $php_errors_levels = E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED;
}
set_error_handler(array('EesyPHP\\Log', 'on_php_error'), self :: $php_errors_levels);
// Log uncatched exceptions
@ -93,7 +136,7 @@ class Log {
if (!array_key_exists($level, self :: $levels)) $level = self :: $default_level;
if (self :: $levels[$level] < self :: $levels[self :: $level]) return true;
if(is_null(self :: $file_fd)) {
if(self :: $filepath && is_null(self :: $file_fd)) {
self :: $file_fd = fopen(self :: $filepath, 'a');
}
@ -125,8 +168,8 @@ class Log {
$msg[] = $message;
$msg = implode(' - ', $msg)."\n";
}
fwrite(self :: $file_fd, $msg);
if (self :: $file_fd)
fwrite(self :: $file_fd, $msg);
if ($level == 'FATAL')
if (!is_null(self :: $fatal_error_handler))
@ -260,7 +303,7 @@ class Log {
self :: $level = self :: $default_level;
self :: warning(
"Invalid log level value found in configuration (%s). ".
"Set as default (%s).", $level, self :: $default_level);
"Set as default (%s).", vardump($level), self :: $default_level);
}
else {
self :: $level = $level;

View file

@ -26,22 +26,23 @@ class SentryIntegration {
/**
* Initialization
* @param string|null $dsn Sentry DSN
* (optional, default: from sentry.dsn config key if set, null otherwise)
* @param float|null $traces_sample_rate Sentry traces sample rate
* (optional, default: 0.2)
* (optional, default: from sentry.traces_sample_rate config key if set,
* 0.2 otherwise)
* @param array<int>|null $php_error_types Types of PHP error to log in Sentry
* (optional, default: see self::$php_error_types)
* (optional, default: from sentry.php_error_types config key if set,
* otherwise, see self::$php_error_types)
* @return void
*/
public static function init($dsn=null, $traces_sample_rate=null,
$php_error_types=null) {
// Init Sentry (if its DSN is configured)
if (!$dsn) return;
\Sentry\init([
'dsn' => $dsn,
'dsn' => $dsn?$dsn:Config::get('sentry.dsn'),
'traces_sample_rate' => (
$traces_sample_rate?
$traces_sample_rate:0.2
$traces_sample_rate:
Config::get('sentry.traces_sample_rate', 0.2, 'float')
),
]);
@ -55,8 +56,18 @@ class SentryIntegration {
]);
});
if (is_array($php_error_types))
self :: $php_error_types = $php_error_types;
if (!is_array($php_error_types))
$php_error_types = Config::get(
'sentry.php_error_types', self :: $php_error_types, 'array'
);
self :: $php_error_types = array();
foreach($php_error_types as $php_error_type) {
if (is_string($php_error_type) && defined($php_error_type))
$php_error_type = constant($php_error_type);
if (!is_int($php_error_type)) continue;
if (in_array($php_error_type, self :: $php_error_types)) continue;
self :: $php_error_types[] = $php_error_type;
}
set_error_handler(
array('EesyPHP\\SentryIntegration', 'on_php_error'),
E_ALL

View file

@ -16,9 +16,9 @@ class Session {
/**
* Initialization
* @param int|null $max_duration Session max duration in second
* (optional, default: 12h)
* (optional, default: from session.max_duration config key if set, 12h otherwise)
* @param int|null $timeout Session inactivity timeout in second
* (optional, default: no timeout)
* (optional, default: from session.timeout config key if set, no timeout otherwise)
* @return void
*/
public static function init($max_duration=null, $timeout=null) {
@ -26,6 +26,8 @@ class Session {
return;
// Define session max duration
if (is_null($max_duration))
$max_duration = Config::get('session.max_duration', null, 'int');
if (is_int($max_duration))
self :: $max_duration = $max_duration;
@ -41,7 +43,9 @@ class Session {
}
// Handle session timeout
if ($timeout) {
if (is_null($timeout))
$timeout = Config::get('session.timeout', null, 'int');
if (is_int($timeout) && $timeout) {
if (!isset($_SESSION['session_last_access'])) {
Log :: debug('Set initial session last access');
$_SESSION['session_last_access'] = time();

View file

@ -53,13 +53,20 @@ class Tpl {
/**
* 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: false)
* (optional, default: from template.debug_ajax or debug_ajax config keys if set,
* false otherwise)
* @return void
*/
public static function init($templates_dir, $templates_c_dir, $debug_ajax=false) {
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)",
@ -75,6 +82,8 @@ class Tpl {
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'));
}

View file

@ -51,11 +51,14 @@ class Url {
/**
* Initialization
* @param string|null $public_root_url The application public root URL
* (optional, default: from public_root_url config key)
* @param bool $api_mode Enable/disable API mode
* @return void
*/
public static function init($public_root_url = null, $api_mode=false) {
if ($public_root_url) {
public static function init($public_root_url=null, $api_mode=false) {
if (is_null($public_root_url))
$public_root_url = Config::get('public_root_url', null, 'string');
if (is_string($public_root_url) && $public_root_url) {
// Check URL end
if (substr(self :: $public_root_url, -1) == '/')
$public_root_url = substr($public_root_url, 0, -1);

View file

@ -49,8 +49,13 @@ function format_callable($callable) {
return vardump($callable);
}
function check_is_empty($val) {
switch(gettype($val)) {
/**
* Check if given value is empty
* @param mixed $value
* @return bool
*/
function check_is_empty($value) {
switch(gettype($value)) {
case "boolean":
case "integer":
case "double":
@ -59,11 +64,57 @@ function check_is_empty($val) {
return False;
case "array":
case "string":
if ($val == "0") return false;
return empty($val);
if ($value == "0") return false;
return empty($value);
case "NULL":
return True;
}
return empty($value);
}
/**
* Ensure the given value is an array and return an array with this value if not
* @param mixed $value
* @return array
*/
function ensure_is_array($value) {
if (is_array($value))
return $value;
if (check_is_empty($value))
return array();
return array($value);
}
/**
* Get a specific configuration variable value
*
* @param mixed $value The value to cast
* @param string $type The type of expected value. The configuration variable
* value will be cast as this type. Could be : bool, int,
* float or string.
* @param bool $split If true, $type=='array' and $value is a string, split
* the value by comma (optional, default: false)
* @return mixed The cast value
**/
function cast($value, $type, $split=false) {
switch($type) {
case 'bool':
case 'boolean':
return boolval($value);
case 'int':
case 'integer':
return intval($value);
case 'float':
return floatval($value);
case 'str':
case 'string':
return strval($value);
case 'array':
if ($split && is_string($value))
$value = preg_split('/ *, */', $value);
return ensure_is_array($value);
}
return $value;
}
/*