Compare commits

...

6 commits

30 changed files with 2176 additions and 1577 deletions

View file

@ -5,4 +5,4 @@ require realpath(dirname(__FILE__).'/..')."/includes/core.php";
if (is_callable('handle_cli_args'))
handle_cli_args();
else
logging('FATAL', "An error occured initializing CLI : handle_cli_args() function not found.");
Log :: fatal("An error occured initializing CLI : handle_cli_args() function not found.");

View file

@ -7,6 +7,14 @@
"email": "info@easter-eggs.com"
}
],
"autoload": {
"psr-4": {
"EesyPHP\\": "src/"
},
"files": [
"src/functions.php"
]
},
"require": {
"envms/fluentpdo": "^1.1",
"pear/console_table": "^1.3",

View file

@ -1,16 +1,19 @@
<?php
use EesyPHP\Check;
use EesyPHP\Log;
$cli_commands=array();
function add_cli_command($command, $handler, $short_desc, $usage_args=false, $long_desc=false,
$override=false) {
global $cli_commands;
if (array_key_exists($command, $cli_commands) && !$override) {
logging('ERROR', _("The CLI command '%s' already exists."), $command);
Log :: error(_("The CLI command '%s' already exists."), $command);
return False;
}
if (!is_callable($handler)) {
logging('ERROR', _("The CLI command '%s' handler is not callable !"), $command);
Log :: error(_("The CLI command '%s' handler is not callable !"), $command);
return False;
}
@ -135,8 +138,8 @@ function handle_cli_args() {
if (!$cli_command)
usage();
logging(
'DEBUG', "Run %s command %s with argument(s) '%s'.",
Log :: debug(
"Run %s command %s with argument(s) '%s'.",
basename($argv[0]), $cli_command, implode("', '", $command_args)
);
@ -146,7 +149,7 @@ function handle_cli_args() {
exit($result?0:1);
}
catch(Exception $e) {
log_exception($e, _("An exception occured running command %s"), $cli_command);
Log :: exception($e, _("An exception occured running command %s"), $cli_command);
exit(1);
}
}
@ -208,7 +211,7 @@ function cli_list($command_args) {
$items = search_items($params);
if (!is_array($items)) {
logging("ERROR", "Invalid DB info return.\n");
Log :: error("Invalid DB info return.\n");
return False;
}
@ -257,14 +260,14 @@ add_cli_command(
);
function cli_show($command_args) {
if (count($command_args) != 1 || !check_id($command_args[0]))
if (count($command_args) != 1 || !Check :: id($command_args[0]))
usage(_('You must provide a valid ID.'));
$item_id = $command_args[0];
$item = get_item($item_id);
if (!$item)
logging('FATAL', _("Item #%s not found."), $item_id);
Log :: fatal(_("Item #%s not found."), $item_id);
print_item_info($item);
return True;
@ -281,14 +284,14 @@ function cli_delete($command_args) {
usage(_('You must provide item ID.'));
// Check URI
if (!check_id($command_args[0]))
logging('FATAL', _("Invalid item ID"));
if (!Check :: id($command_args[0]))
Log :: fatal(_("Invalid item ID"));
// Check exist
$item_id = $command_args[0];
$item = get_item($item_id);
if (!$item)
logging('FATAL', _("Item #%s not found."), $item_id);
Log :: fatal(_("Item #%s not found."), $item_id);
print_item_info($item);
@ -297,13 +300,13 @@ function cli_delete($command_args) {
$handle = fopen ("php://stdin","r");
$line = fgets($handle);
if(trim($line) != 'yes'){
logging('WARNING', _("User cancel"));
Log :: warning(_("User cancel"));
exit;
}
echo "\n";
if (!delete_item($item['id']))
logging('FATAL', _("An error occured deleting item #%d."), $item_id);
Log :: fatal(_("An error occured deleting item #%d."), $item_id);
return True;
}
@ -318,7 +321,7 @@ function cli_export($command_args) {
$fd = fopen((count($command_args) >= 1?$command_args[0]:'php://output'), 'w');
export_items($fd);
fclose($fd);
logging('INFO', "Items export to '".(count($command_args) >= 1?$command_args[0]:'STDOUT')."'.");
Log :: info("Items export to '".(count($command_args) >= 1?$command_args[0]:'STDOUT')."'.");
}
add_cli_command(
'export',
@ -331,8 +334,8 @@ function cli_restore($command_args) {
$fd = fopen((count($command_args) >= 1?$command_args[0]:'php://stdin'), 'r');
restore_items($fd);
fclose($fd);
logging(
'INFO', "Items restored from '%s'",
Log :: info(
"Items restored from '%s'",
(count($command_args) >= 1?$command_args[0]:'STDIN')
);
}
@ -354,7 +357,7 @@ function cli_cron($command_args) {
case '-m':
case '--max-age':
$i++;
if(!check_id($command_args[$i]))
if(!Check :: id($command_args[$i]))
usage('Invalid -m|--max-age clause');
$item_max_age = $command_args[$i];
break;
@ -368,37 +371,36 @@ function cli_cron($command_args) {
}
if (!is_int($item_max_age) || $item_max_age <= 0)
logging(
'FATAL',
Log :: fatal(
'Invalid $item_max_age value set in configuration: it\'s must be a positive integer.');
logging('DEBUG', "cli_cron(): item max age = $item_max_age day(s)");
Log :: debug("cli_cron(): item max age = $item_max_age day(s)");
$limit = time() - ($item_max_age * 86400);
logging('DEBUG', "Handle items expiration with creation date limit ".format_time($limit).".");
Log :: debug("Handle items expiration with creation date limit ".format_time($limit).".");
$items = search_items(array('all' => true));
$error = false;
foreach($items['items'] as $item) {
if ($item['date'] < $limit) {
if ($just_try) {
logging('DEBUG', 'Just-try mode: do not really delete item #%s (%s, creation date: %s)',
Log :: debug('Just-try mode: do not really delete item #%s (%s, creation date: %s)',
$item['id'], $item['name'], format_time($item['date'])
);
}
else if (delete_item($item['id'])) {
logging('INFO', 'Item #%s (%s) deleted (creation date: %s)',
Log :: info('Item #%s (%s) deleted (creation date: %s)',
$item['id'], $item['name'], format_time($item['date'])
);
}
else {
logging('ERROR', 'Fail to delete item "%s" (%s, creation date: %s)',
Log :: error('Fail to delete item "%s" (%s, creation date: %s)',
$item['id'], $item['name'], format_time($item['date'])
);
$error = true;
}
}
else {
logging('DEBUG', 'Item "%s" (%s) still valid (creation date: %s)',
Log :: debug('Item "%s" (%s) still valid (creation date: %s)',
$item['id'], $item['name'], format_time($item['date'])
);
}

View file

@ -1,5 +1,12 @@
<?php
use EesyPHP\Email;
use EesyPHP\Log;
use EesyPHP\SentryIntegration;
use EesyPHP\SentrySpan;
use EesyPHP\SentryTransaction;
use EesyPHP\Url;
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED);
// Root directory path
@ -22,9 +29,6 @@ set_include_path($root_dir_path.'/includes' . PATH_SEPARATOR . get_include_path(
// Load composer autoload.php
require("$root_dir_path/vendor/autoload.php");
// API mode
$api_mode = false;
// Load configuration
require_once('translation.php');
require_once('config.inc.php');
@ -34,7 +38,11 @@ if (is_file("$root_dir_path/includes/config.local.php")) {
require "$root_dir_path/includes/config.local.php";
}
require_once 'sentry.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,
);
$sentry_transaction = new SentryTransaction();
$sentry_span = new SentrySpan('core.init', 'Core initialization');
@ -43,7 +51,13 @@ $sentry_span = new SentrySpan('core.init', 'Core initialization');
if (isset($upload_tmp_dir))
ini_set('upload_tmp_dir', $upload_tmp_dir);
require_once('logging.php');
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');
require_once('session.php');
@ -55,14 +69,21 @@ $status_list = array (
'archived' => ___('Archived'),
);
require_once('hooks.php');
require_once('cli.php');
require_once('translation-cli.php');
require_once('smarty.php');
require_once('url.php');
Url::init(isset($public_root_url)?$public_root_url:null);
require_once('url-helpers.php');
require_once('db.php');
require_once('mail.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
init_translation();

View file

@ -1,9 +1,12 @@
<?php
use EesyPHP\Hook;
use EesyPHP\Log;
use Unidecode\Unidecode;
if (!isset($db_dsn)) {
logging('FATAL', 'Database DSN not configured');
Log :: fatal('Database DSN not configured');
exit(1);
}
@ -33,12 +36,12 @@ try {
}
}
logging('DEBUG',$msg);
Log :: debug($msg);
};
}
catch(Exception $e) {
logging('ERROR',"Fail to connect to DB (DSN : '$db_dsn') : ".$e->getMessage());
logging("FATAL", _('Unable to connect to the database.'));
Log :: error("Fail to connect to DB (DSN : '$db_dsn') : ".$e->getMessage());
Log :: fatal(_('Unable to connect to the database.'));
}
/*
@ -111,7 +114,7 @@ function get_items($orderby='id', $raw_values=false) {
}
}
catch (Exception $e) {
logging('ERROR', "Error retreiving items info from database : ".$e->getMessage());
Log :: error("Error retreiving items info from database : ".$e->getMessage());
}
return false;
}
@ -134,7 +137,7 @@ function get_item($id, $raw_values=false) {
}
}
catch (Exception $e) {
logging('ERROR', "Error retreiving item #$id info from database : ".$e->getMessage());
Log :: error("Error retreiving item #$id info from database : ".$e->getMessage());
}
return false;
}
@ -149,13 +152,13 @@ function add_item($values) {
if ($result !== false) {
$item = get_item($result);
logging('INFO', "New item #$result added");
trigger_hook('item_added', $item);
Log :: info("New item #$result added");
Hook :: trigger('item_added', $item);
return $item;
}
}
catch (Exception $e) {
logging('ERROR', "Error creating item in database : ".$e->getMessage());
Log :: error("Error creating item in database : ".$e->getMessage());
}
return false;
}
@ -180,20 +183,20 @@ function update_item($id, $changes) {
-> execute();
if ($result !== false) {
logging('INFO', "Item #$id updated");
trigger_hook('item_updated', $item);
Log :: info("Item #$id updated");
Hook :: trigger('item_updated', $item);
return true;
}
}
catch (Exception $e) {
logging('ERROR', "Error updating item #$id in database : ".$e->getMessage());
Log :: error("Error updating item #$id in database : ".$e->getMessage());
}
return false;
}
function change_item_status($id, $status) {
if (update_item($id, array('status' => $status))) {
logging('INFO', "Status of item #$id changed to $status.");
Log :: info("Status of item #$id changed to $status.");
return true;
}
return false;
@ -211,12 +214,12 @@ function delete_item($id) {
-> execute();
if ($result !== false) {
logging('INFO', "Item #$id deleted");
Log :: info("Item #$id deleted");
return True;
}
}
catch (Exception $e) {
logging('ERROR', "Error deleting item #$id from database : ".$e->getMessage());
Log :: error("Error deleting item #$id from database : ".$e->getMessage());
}
return false;
}
@ -308,7 +311,7 @@ function search_items($params) {
-> execute();
if ($result === false) {
logging('ERROR', 'search_items() : search in DB return false');
Log :: error('search_items() : search in DB return false');
return false;
}
@ -344,7 +347,7 @@ function search_items($params) {
$result_count = $query_count -> execute();
if ($result_count === false) {
logging('DEBUG', 'search_items() : search for count in DB return false');
Log :: debug('search_items() : search for count in DB return false');
return False;
}
$count = $result_count -> fetch();
@ -360,7 +363,7 @@ function search_items($params) {
);
}
catch (Exception $e) {
log_exception(
Log :: exception(
$e, "An exception occured searching items with params %s infos from database : ",
preg_replace("/\n[ \t]*/", " ", print_r($params, true))
);
@ -403,12 +406,12 @@ function restore_items($fd=null) {
$result = $fpdo -> deleteFrom('item')
-> execute();
if ($result === false) {
logging('ERROR', "An unknown error occured truncating item table in database.");
Log :: error("An unknown error occured truncating item table in database.");
return false;
}
}
catch (Exception $e) {
logging('ERROR', "Error truncating item table in database : ".$e->getMessage());
Log :: error("Error truncating item table in database : ".$e->getMessage());
return false;
}
@ -450,21 +453,20 @@ function restore_items($fd=null) {
$restored++;
}
else {
logging('ERROR', "Unkwown error occured restoring item from line #$line :\n".print_r($values, true));
Log :: error("Unkwown error occured restoring item from line #$line :\n".print_r($values, true));
$error = true;
}
}
catch (Exception $e) {
logging(
'ERROR',
Log :: error(
"Error restoring item from line #$line : ".$e->getMessage()."\n".print_r($values, true));
$error = true;
}
}
logging('INFO', "$restored items restored");
Log :: info("$restored items restored");
// Trigger hooks
trigger_hook('items_restored');
Hook :: trigger('items_restored');
return !$error;
}

View file

@ -1,85 +1,16 @@
<?php
use EesyPHP\Check;
use EesyPHP\Log;
/*
* Check values helpers
*/
function check_name($name) {
if (preg_match('/^[\w \-]{2,}$/iu',$name))
return true;
return false;
}
function check_id(&$id) {
if (is_int($id))
return true;
if (preg_match('/^[0-9]+$/', $id)) {
$id = intval($id);
return true;
}
return false;
}
function check_search_pattern($pattern) {
foreach(preg_split('/\s+/', trim($pattern)) as $word) {
if (!check_id($word) && !check_name($word))
return false;
}
return true;
}
function check_time(&$time) {
if (!is_int($time)) {
if (preg_match('/^[0-9]+$/', $time))
$time = intval($time);
else
return false;
}
return ($time >= 1577833200); // 2020-01-01 - date of birth of this soft
}
function check_status($status) {
global $status_list;
return array_key_exists($status, $status_list);
}
function check_description($comment) {
if (preg_match("/^[\p{L}0-9\p{P}\p{Zs}\p{Zl}\p{Sc}\=\+]+$/uim", $comment))
return true;
return false;
}
function check_email($value, $domain=NULL, $checkDns=true) {
$regex = '/^((\"[^\"\f\n\r\t\v\b]+\")|([\w\!\#\$\%\&\'\*\+\-\~\/\^\`\|\{\}]+(\.[\w\!\#\$\%\&\'\*\+\-\~\/\^\`\|\{\}]+)*))@((\[(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))\])|(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))|((([A-Za-z0-9\-])+\.)+[A-Za-z\-]+))$/';
if (!preg_match($regex, $value)) {
return false;
}
$nd = explode('@', $value);
$nd=$nd[1];
if ($domain) {
if(is_array($domain)) {
if (!in_array($nd,$domain)) {
return false;
}
}
else {
if($nd!=$domain) {
return false;
}
}
}
if ($checkDns && function_exists('checkdnsrr')) {
if (!(checkdnsrr($nd, 'MX') || checkdnsrr($nd, 'A'))) {
return false;
}
}
return true;
}
/*
* Handling item POST data
*/
@ -87,11 +18,11 @@ function handle_item_post_data(&$info, $enabled_fields=null, $required_fields=nu
&$changes=null) {
$field_errors=array();
if (isset($_POST['submit'])) {
logging('DEBUG', 'POST data : '.vardump($_POST));
Log :: debug('POST data : '.vardump($_POST));
// Name
if (!$enabled_fields || in_array('name', $enabled_fields)) {
if (isset($_POST['name'])) {
if (check_name($_POST['name'])) {
if (Check :: name($_POST['name'])) {
$info['name'] = $_POST['name'];
}
else {
@ -118,10 +49,10 @@ function handle_item_post_data(&$info, $enabled_fields=null, $required_fields=nu
isset($_POST['description']) &&
(!$enabled_fields || in_array('description', $enabled_fields))
) {
if (check_is_empty(trim($_POST['description']))) {
if (Check :: is_empty(trim($_POST['description']))) {
$info['description'] = null;
}
else if (check_description($_POST['description'])) {
else if (Check :: description($_POST['description'])) {
$info['description'] = $_POST['description'];
}
else {
@ -135,7 +66,7 @@ function handle_item_post_data(&$info, $enabled_fields=null, $required_fields=nu
foreach ($required_fields as $field) {
if (array_key_exists($field, $field_errors))
continue;
if (array_key_exists($field, $info) && !is_null($info[$field]) && !check_is_empty($info))
if (array_key_exists($field, $info) && !is_null($info[$field]) && !Check :: is_empty($info))
continue;
$field_errors[$field] = "Cette information est obligatoire.";
}
@ -251,23 +182,6 @@ function format_callable($callable) {
return vardump($callable);
}
function check_is_empty($val) {
switch(gettype($val)) {
case "boolean":
case "integer":
case "double":
case "object":
case "resource":
return False;
case "array":
case "string":
if ($val == "0") return false;
return empty($val);
case "NULL":
return True;
}
}
/*
* Generic file/directory helpers
*/
@ -308,18 +222,18 @@ function delete_directory($dir, $recursive=true) {
foreach ($files as $file) {
if (is_dir("$dir/$file")) {
if (!delete_directory("$dir/$file", true)) {
logging('ERROR', "delete_directory($dir) : Fail to delete sub-directory '$dir/$file'.");
Log :: error("delete_directory($dir) : Fail to delete sub-directory '$dir/$file'.");
return false;
}
}
else if (!unlink("$dir/$file")) {
logging('ERROR', "delete_directory($dir) : Fail to delete '$dir/$file'.");
Log :: error("delete_directory($dir) : Fail to delete '$dir/$file'.");
return false;
}
}
}
else if (!empty($files)) {
logging('ERROR', "delete_directory($dir) : Directory is not empty.");
Log :: error("delete_directory($dir) : Directory is not empty.");
return false;
}
return rmdir($dir);
@ -345,7 +259,7 @@ function run_external_command($command, $data_stdin=null, $escape_command_args=t
$command = implode(' ', $command);
if ($escape_command_args)
$command = escapeshellcmd($command);
logging('DEBUG', "Run external command: '$command'");
Log :: debug("Run external command: '$command'");
$descriptorspec = array(
0 => array("pipe", "r"), // stdin
1 => array("pipe", "w"), // stdout
@ -354,7 +268,7 @@ function run_external_command($command, $data_stdin=null, $escape_command_args=t
$process = proc_open($command, $descriptorspec, $pipes);
if (!is_resource($process)) {
logging('ERROR', "Fail to run external command: '$command'");
Log :: error("Fail to run external command: '$command'");
return false;
}
@ -372,7 +286,7 @@ function run_external_command($command, $data_stdin=null, $escape_command_args=t
$return_value = proc_close($process);
$error = (!empty($stderr) || $return_value != 0);
logging(
Log :: log(
($error?'ERROR':'DEBUG'),
"External command ".($error?"error":"result").":\n".
"\tCommand : $command\n".
@ -385,4 +299,25 @@ function run_external_command($command, $data_stdin=null, $escape_command_args=t
return array($return_value, $stdout, $stderr);
}
/**
* Check an AJAX request and trigger a fatal error on fail
*
* Check if session key is present and valid and set AJAX
* mode.
*
* @param string|null $session_key string The current session key (optional)
*
* @return void
**/
function check_ajax_request($session_key=null) {
global $ajax, $debug_ajax;
$ajax = true;
if (check_session_key($session_key))
fatal_error('Invalid request');
if ($debug_ajax)
Log :: debug("Ajax Request : ".vardump($_REQUEST));
}
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -1,105 +0,0 @@
<?php
$hooks = array();
/**
* Registered a hook on a specific event
*
* @param $event string The event name
* @param $callable callable The callable to run on event
* @param $param mixed Paremeter that will be pass to the callable
* Use an array if you have multiple parameters to pass
*
* @return void
*/
function register_hook($event, $callable, $param=NULL) {
global $hooks;
if (!array_key_exists($event, $hooks))
$hooks[$event] = array();
$hooks[$event][] = array (
'callable' => $callable,
'param' => $param,
);
}
/**
* Run triggered actions on specific event
*
* @param $event string HookEvent name
*
* @return boolean True if all triggered actions succefully runned, false otherwise
*/
function trigger_hook($event_name, $event_data=null) {
global $hooks;
$return = true;
if (isset($hooks[$event_name]) && is_array($hooks[$event_name])) {
if ($event_name == 'all')
$event = new HookEvent($event_data['event_name'], $event_data['event_data']);
else
$event = new HookEvent($event_name, $event_data);
foreach ($hooks[$event_name] as $e) {
if (is_callable($e['callable'])) {
try {
call_user_func_array($e['callable'],array($event, &$e['param']));
}
catch(Exception $e) {
log_exception(
$e, "An exception occured running hook ".format_callable($e['callable']).
" on event $event_name");
$return = false;
}
}
else {
logging(
'ERROR',
"The hook ".format_callable($e['callable'])." on event $event_name is not callable.");
$return = false;
}
}
}
else
logging('DEBUG', "No hook registered for event $event_name.");
// Handle 'all' event
if ($event_name != 'all') {
trigger_hook (
'all',
array (
'event_name' => $event_name,
'event_data' => $event_data,
)
);
}
return $return;
}
class HookEvent implements JsonSerializable {
private $name;
private $data;
function __construct($name, $data) {
$this -> name = $name;
$this -> data = $data;
}
function __get($key) {
if ($key == 'name')
return $this -> name;
elseif ($key == 'data')
return $this -> data;
elseif (is_array($this -> data) && array_key_exists($key, $this -> data))
return $this -> data[$key];
return null;
}
public function jsonSerialize() {
return array (
'name' => $this -> name,
'data' => $this -> data,
);
}
}
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -1,256 +0,0 @@
<?php
/*
* Configuration :
* // Log file
* $log_file='/path/to/app.log';
*
* // Log level (DEBUG / INFO / WARNING / ERROR / FATAL)
* $log_level='INFO';
*
* // Log PHP errors levels (as specified to set_error_handler())
* // $log_php_errors_levels = E_ALL & ~E_STRICT;
* // 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;
*/
// Log file descriptor (Do not change !!!)
$_log_file_fd = null;
// Log Levels
$_log_levels = array(
'TRACE' => 0,
'DEBUG' => 1,
'INFO' => 2,
'WARNING' => 3,
'ERROR' => 4,
'FATAL' => 5,
);
// Custom fatal error handler
$_fatal_error_handler = null;
/**
* Log a message
* @param string $level The message level (key of $_log_levels)
* @param string $message The message to log
* @return true
*/
function logging($level, $message) {
global $log_file, $_log_file_fd, $_log_levels, $log_level, $argv,
$_fatal_error_handler, $auth_user;
if (!array_key_exists($level, $_log_levels)) $level = 'INFO';
$level_id = $_log_levels[$level];
$log_level_id = $_log_levels[$log_level];
if ($level_id < $log_level_id) return true;
if(is_null($_log_file_fd)) {
$_log_file_fd = fopen($log_file, 'a');
}
// If more than 2 arguments passed, format message using sprintf
if (func_num_args() > 2) {
$message = call_user_func_array(
'sprintf',
array_merge(array($message), array_slice(func_get_args(), 2))
);
}
if (php_sapi_name() == "cli") {
$msg = implode(' - ', array(
date('Y/m/d H:i:s'),
basename($argv[0]),
$level,
$message
))."\n";
}
else {
$msg = array(
date('Y/m/d H:i:s'),
$_SERVER['REQUEST_URI'],
$_SERVER['REMOTE_ADDR'],
);
if (isset($auth_user))
$msg[] = ($auth_user['username']?$auth_user['username']:'anonymous');
$msg[] = $level;
$msg[] = $message;
$msg = implode(' - ', $msg)."\n";
}
fwrite($_log_file_fd, $msg);
if ($level == 'FATAL')
if (!is_null($_fatal_error_handler))
call_user_func($_fatal_error_handler, $message);
elseif (function_exists('fatal_error'))
fatal_error($message);
else
die("\n$message\n\n");
elseif (php_sapi_name() == "cli")
echo $msg;
return true;
}
// Set default log level (if not defined or invalid)
$default_log_level = 'WARNING';
if (!isset($log_level)) {
$log_level = $default_log_level;
}
elseif (!array_key_exists($log_level, $_log_levels)) {
$invalid_value = $log_level;
$log_level = $default_log_level;
logging(
$log_level, "Invalid log level value found in configuration (%s). ".
"Set as default (%s).", $invalid_value, $log_level);
}
/**
* Register a contextual fatal error handler
* @param null|callable $handler The fatal error handler (set as null to reset)
* @return void
*/
function register_fatal_error_handler($handler) {
// @phpstan-ignore-next-line
if ($handler && !is_callable($handler))
logging('FATAL', 'Fatal handler provided is not callable !');
global $_fatal_error_handler;
$_fatal_error_handler = ($handler?$handler:null);
}
/**
* Change of current log file
* @param string $file The new log file path
* @return bool
*/
function change_log_file($file) {
global $log_file, $_log_file_fd;
if ($file == $log_file) return True;
if ($_log_file_fd) {
fclose($_log_file_fd);
$_log_file_fd = false;
}
$log_file = $file;
return True;
}
/*
*******************************************************************************
* Handle exception logging
*******************************************************************************
*/
/**
* Get the current backtrace
* @param int $ignore_last The number of last levels to ignore
* @return string
*/
function get_debug_backtrace_context($ignore_last=0) {
$traces = debug_backtrace();
// Also ignore this function it self
$ignore_last++;
if (!is_array($traces) || count($traces) <= $ignore_last)
return "";
$msg = array();
for ($i=$ignore_last; $i < count($traces); $i++) {
$trace = array("#$i");
if (isset($traces[$i]['file']))
$trace[] = $traces[$i]['file'].(isset($traces[$i]['line'])?":".$traces[$i]['line']:"");
// @phpstan-ignore-next-line
if (isset($traces[$i]['class']) && isset($traces[$i]['function']))
$trace[] = implode(" ", array(
$traces[$i]['class'],
$traces[$i]['type'],
$traces[$i]['function']. "()"));
elseif (isset($traces[$i]['function']))
$trace[] = $traces[$i]['function']. "()";
$msg[] = implode(" - ", $trace);
}
return implode("\n", $msg);
}
/**
* Log an exception
* @param Throwable $exception
* @param string|null $prefix The prefix of the log message
* (optional, default: "An exception occured")
* @return void
*/
function log_exception($exception, $prefix=null) {
if (function_exists('log_in_sentry'))
log_in_sentry($exception);
// If more than 2 arguments passed, format prefix message using sprintf
if ($prefix && func_num_args() > 2) {
$prefix = call_user_func_array(
'sprintf',
array_merge(array($prefix), array_slice(func_get_args(), 2))
);
}
logging(
"ERROR", "%s:\n%s\n## %s:%d : %s",
($prefix?$prefix:"An exception occured"),
get_debug_backtrace_context(1),
$exception->getFile(), $exception->getLine(),
$exception->getMessage());
}
set_exception_handler('log_exception');
/*
*******************************************************************************
* Handle PHP error logging
*******************************************************************************
*/
/**
* Convert PHP error number to the corresponding label
* @param int $errno
* @return string
*/
function errno2type($errno) {
$constants = get_defined_constants();
if (is_array($constants))
foreach($constants as $label => $value)
if ($value == $errno && preg_match('/^E_(.*)$/', $label, $m))
return $m[1];
return 'UNKNOWN ERROR #'.$errno;
}
/**
* Log a PHP error
* Note: method design to be used as callable by set_error_handler()
* @param int $errno The error number
* @param string $errstr The error message
* @param string $errfile The filename that the error was raised in
* @param int $errline The line number where the error was raised
* @return false Return false to let the normal error handler continues.
*/
function log_php_error($errno, $errstr, $errfile, $errline) {
$msg = sprintf(
"A PHP error occured : [%s] %s\nFile : %s (line : %d)",
errno2type($errno), $errstr, $errfile, $errline
);
logging("ERROR", $msg);
if (function_exists('log_php_error_in_sentry'))
log_php_error_in_sentry($errno, $msg);
return False;
}
if (isset($log_php_errors_levels))
set_error_handler('log_php_error', $log_php_errors_levels);
elseif (in_array($log_level, array('DEBUG', 'TRACE')))
set_error_handler('log_php_error', E_ALL & ~E_STRICT);
else
set_error_handler('log_php_error', E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED);
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -1,135 +0,0 @@
<?php
// Load PHP PEAR Mail and Mail_mime libs
require_once(isset($php_mail_path)?$php_mail_path:"Mail.php");
require_once(isset($php_mail_mime_path)?$php_mail_mime_path:"Mail/mime.php");
/**
* Send an email
*
* @param string|null $from Email sender
* @param string|array<string> $to Email recipient(s)
* @param string $subject Email subject
* @param string $msg Email body
* @param boolean $html Set to true to send an HTML email (default: false)
* @param array<string,string>|null $attachments Email attachments as an array with
* filepath as key and filename as value
* @param array<string,string>|null $headers Email headers
* @param string|null $encoding Email encoding (default: utf8)
* @param string|null $eol End of line string (default : \n)
*
* @return boolean true If mail was sent, false otherwise
*/
function send_mail($from, $to, $subject, $msg, $html=false, $attachments=null, $headers=null,
$encoding=null, $eol=null) {
global $mail_send_method, $mail_headers, $mail_send_params, $mail_sender, $mail_catch_all;
$mail_obj = & Mail::factory($mail_send_method, $mail_send_params);
if (!$headers) $headers = array();
if(isset($mail_headers) && is_array($mail_headers)) {
$headers = array_merge($headers, $mail_headers);
}
logging(
'TRACE', 'Mail catch all: %s',
isset($mail_catch_all) && $mail_catch_all?
vardump($mail_catch_all):'not set'
);
if (isset($mail_catch_all) && $mail_catch_all) {
logging(
'DEBUG', 'Mail catch to %s',
is_array($mail_catch_all)?implode(',', $mail_catch_all):$mail_catch_all
);
$msg .= sprintf(
(
$html?
_("</hr><p><small>Mail initialy intended for %s.</small></p>"):
_("\n\n\nMail initialy intended for %s.")
),
(is_array($to)?implode(',', $to):$to));
$headers["X-Orig-To"] = $to;
$to = (
is_array($mail_catch_all)?
implode(',', $mail_catch_all):$mail_catch_all
);
}
if ($subject) {
$headers["Subject"] = $subject;
}
if (isset($headers['From'])) {
if (!$from)
$from = $headers['From'];
unset($headers['From']);
}
elseif (!$from) {
$from = $mail_sender;
}
$headers["To"] = $to;
$to = array (
'To' => $to
);
foreach(array_keys($headers) as $header) {
if(in_array(strtoupper($header), array('BCC', 'CC'))) {
if (isset($mail_catch_all) && $mail_catch_all) {
logging('DEBUG', "Mail catched: remove $header header");
$msg .= sprintf(
(
$html?
_("<p><small>%s: %s</small></p>"):
_("\n%s: %s")
),
strtoupper($header),
(is_array($headers[$header])?implode(',', $headers[$header]):$headers[$header]));
unset($headers[$header]);
continue;
}
$to[strtoupper($header)] = $headers[$header];
}
}
if (!$encoding) $encoding = "utf8";
$mime = new Mail_mime(
array(
'eol' => ($eol?$eol:"\n"),
($html?'html_charset':'text_charset') => $encoding,
'head_charset' => $encoding,
)
);
if ($from)
$mime->setFrom($from);
if ($subject)
$mime->setSubject($subject);
if ($html)
$mime->setHTMLBody($msg);
else
$mime->setTXTBody($msg);
if (is_array($attachments) && !empty($attachments)) {
$finfo = new finfo(FILEINFO_MIME_TYPE);
foreach ($attachments as $file => $filename) {
$mime->addAttachment($file, $finfo->file($file), $filename);
}
}
$body = $mime->get();
$headers = $mime->headers($headers);
$ret = $mail_obj -> send($to, $headers, $body);
if (PEAR::isError($ret)) {
$msg = "Error sending email: ".$ret -> getMessage();
logging('ERROR', $msg);
return false;
}
return true;
}
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -1,213 +0,0 @@
<?php
/*
* Configuration :
*
* // 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,
* );
*/
// Init Sentry (if its DSN is configured)
if (isset($sentry_dsn) && $sentry_dsn) {
\Sentry\init([
'dsn' => $sentry_dsn,
'traces_sample_rate' => (
isset($sentry_traces_sample_rate) ?
$sentry_traces_sample_rate : 0.2
),
]);
\Sentry\configureScope(function (\Sentry\State\Scope $scope): void {
global $auth_user;
$scope->setUser([
'id' => isset($auth_user) && $auth_user?$auth_user['uid']:null,
'email' => isset($auth_user) && $auth_user?$auth_user['mail']:null,
'segment' => isset($auth_user) && $auth_user?$auth_user['type']:null,
'ip_address' => $_SERVER['REMOTE_ADDR'],
]);
});
}
/**
* Log an exception or a message in Sentry
* @param string|Exception $msg
* @return void
*/
function log_in_sentry($msg) {
global $sentry_dsn;
if (!isset($sentry_dsn) || !$sentry_dsn) {
logging('TRACE', 'Sentry DSN not configured, do not log this error');
return;
}
if (is_string($msg)) {
logging('DEBUG', 'Error logged in Sentry');
\Sentry\captureMessage($msg);
}
elseif ($msg instanceof Exception) {
logging('DEBUG', 'Exception logged in Sentry');
\Sentry\captureException($msg);
}
}
/**
* Log a PHP error in Sentry
* @param int $errno The error number
* @param string $msg The error message
* @return void
*/
function log_php_error_in_sentry($errno, $msg) {
global $sentry_php_error_types;
if (
isset($sentry_php_error_types)
&& is_array($sentry_php_error_types)
&& in_array($errno, $sentry_php_error_types)
)
log_in_sentry($msg);
}
/*
* Performance monitoring
*/
class SentryTransaction {
/**
* The Sentry transaction object
* @var \Sentry\Tracing\Transaction
*/
private $transaction;
/**
* The Sentry transaction context object
* @var \Sentry\Tracing\TransactionContext
*/
private $context;
/**
* Constructor: start a Sentry transaction
* @param string|null $op The operation name
* @param string|null $name The transaction name
* @return void
*/
public function __construct($op=null, $name=null) {
// Setup context for the full transaction
$this->context = new \Sentry\Tracing\TransactionContext();
$this->context->setName(
$name?$name:
(php_sapi_name()=='cli'?'CLI execution':'HTTP request')
);
$this->context->setOp(
$op?$op:
(php_sapi_name()=='cli'?'cli.command':'http.request')
);
// Start the transaction
$this->transaction = \Sentry\startTransaction($this->context);
// Set the current transaction as the current span so we can retrieve it later
\Sentry\SentrySdk::getCurrentHub()->setSpan($this->transaction);
}
/**
* Destructor: Stop the current Sentry transaction
* @return void
*/
public function __destruct() {
SentrySpan :: finishAll();
$this->transaction->finish();
}
}
/**
* Internal Sentry Span object implementation
* This internal implementation principally permit to keep trace of new span parent
* and list of started spans.
*/
class SentrySpan {
/**
* Keep trace of started Sentry spans
* @var array<int,mixed>
*/
private static $_started_spans = array();
/**
* The unique ID of the Sentry span
* Note: internal ID used as key in self::$_started_spans
* @var int|null
*/
private $id = null;
/**
* The parent of the Sentry span
* @var mixed
*/
private $parent = null;
/**
* The context of the Sentry span
* @var null|\Sentry\Tracing\SpanContext
*/
private $context = null;
/**
* The Sentry span object
* @var mixed
*/
private $span = null;
/**
* Sentry span constructor
* @param string|null $op The operation name
* @param string|null $name The span name
* @return void
*/
public function __construct($op, $name) {
$this -> parent = \Sentry\SentrySdk::getCurrentHub()->getSpan();
// Check if we have a parent span (this is the case if we started a transaction earlier)
if (is_null($this -> parent)) return;
while (is_null($this -> id)) {
$this -> id = rand();
if (isset(self :: $_started_spans[$this -> id]))
$this -> id = null;
}
$this -> context = new \Sentry\Tracing\SpanContext();
$this -> context->setOp($op);
$this -> context->setDescription($name);
$this -> span = $this->parent->startChild($this -> context);
// Set the current span to the span we just started
\Sentry\SentrySdk::getCurrentHub()->setSpan($this -> span);
self :: $_started_spans[$this -> id] = $this;
}
/**
* Finish the span (if started)
* @return void
*/
public function finish() {
if (!$this -> span) return;
$this -> span -> finish();
unset(self::$_started_spans[$this -> id]);
\Sentry\SentrySdk::getCurrentHub()->setSpan($this -> parent);
}
/**
* Finish all started spans
* @see SentryTransaction::__destruct()
* @return void
*/
public static function finishAll() {
foreach (array_reverse(self :: $_started_spans) as $id => $span)
$span -> finish();
}
}
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -1,4 +1,7 @@
<?php
use EesyPHP\Log;
if (php_sapi_name() == "cli")
return true;
@ -19,18 +22,17 @@ if (!isset($_SESSION['session_key'])) {
// Handle session timeout
if (isset($session_timeout) && $session_timeout) {
if (!isset($_SESSION['session_last_access'])) {
logging('DEBUG', 'Set initial session last access');
Log :: debug('Set initial session last access');
$_SESSION['session_last_access'] = time();
}
elseif ($_SESSION['session_last_access'] > (time() - $session_timeout)) {
logging(
'DEBUG',
Log :: debug(
'Session timeout not expired, update session last access '.
'(Previous value : '.$_SESSION['session_last_access'].')');
$_SESSION['session_last_access'] = time();
}
else {
logging('INFO', 'Session destroyed due to inactivity');
Log :: info('Session destroyed due to inactivity');
session_destroy();
}
}

View file

@ -1,5 +1,10 @@
<?php
use EesyPHP\Log;
use EesyPHP\SentrySpan;
use EesyPHP\SentryTransaction;
use EesyPHP\Url;
if (php_sapi_name() == "cli")
return true;
@ -57,7 +62,7 @@ elseif (method_exists($smarty,'registerPlugin')) {
}
}
else {
logging('FATAL', _('Smarty version not supported.'));
Log :: fatal(_('Smarty version not supported.'));
}
// Configure templates/templates_c directories
@ -65,8 +70,8 @@ if (
!isset($smarty_templates_dir)
|| !is_dir($smarty_templates_dir)
)
logging(
'FATAL', "Template directory not found (%s)",
Log :: fatal(
"Template directory not found (%s)",
isset($smarty_templates_dir)?$smarty_templates_dir:'not set');
else
$smarty->setTemplateDir($smarty_templates_dir);
@ -76,8 +81,8 @@ if (
|| !is_dir($smarty_templates_c_dir)
|| !is_writable($smarty_templates_c_dir)
)
logging(
'FATAL', "Template cache directory not found or not writable (%s)",
Log :: fatal(
"Template cache directory not found or not writable (%s)",
isset($smarty_templates_c_dir)?$smarty_templates_c_dir:'not set');
else
$smarty->setCompileDir($smarty_templates_c_dir);
@ -179,15 +184,15 @@ function _defineCommonTemplateVariables($template, $pagetitle) {
function display_template($template, $pagetitle=false) {
if (!$template)
logging("FATAL", _("No template specified."));
Log :: fatal(_("No template specified."));
// If refresh parameter is present, remove it and redirect
if (isset($_GET['refresh'])) {
unset($_GET['refresh']);
$url = get_current_url();
$url = Url :: get_current_url();
if (!empty($_GET))
$url .= '?'.http_build_query($_GET);
redirect($url);
Url :: redirect($url);
return;
}
@ -208,9 +213,9 @@ function display_template($template, $pagetitle=false) {
unset($_SESSION['messages']);
}
catch (Exception $e) {
log_exception($e, "Smarty - An exception occured displaying template '$template'");
Log :: exception($e, "Smarty - An exception occured displaying template '$template'");
if ($template != 'fatal_error.tpl')
logging("FATAL", _("An error occurred while displaying this page."));
Log :: fatal(_("An error occurred while displaying this page."));
}
$sentry_span->finish();
@ -234,7 +239,7 @@ function display_ajax_return($data=null, $pretty=false) {
unset($_SESSION['errors']);
}
if ($debug_ajax)
logging('DEBUG',"Ajax Response : ".vardump($data));
Log :: debug("Ajax Response : ".vardump($data));
header('Content-Type: application/json');
echo json_encode($data, (($pretty||isset($_REQUEST['pretty']))?JSON_PRETTY_PRINT:0));
exit();
@ -242,7 +247,7 @@ function display_ajax_return($data=null, $pretty=false) {
$ajax=false;
function fatal_error($error) {
global $smarty, $ajax, $api_mode;
global $smarty, $ajax;
// If more than one arguments passed, format error message using sprintf
if (func_num_args() > 1) {
@ -258,7 +263,7 @@ function fatal_error($error) {
// Set HTTP reponse code to 500
http_response_code(500);
if ($ajax || $api_mode)
if ($ajax || Url :: api_mode())
display_ajax_return(array('success' => false, 'error' => $error));
$smarty->assign('fatal_error', $error);

View file

@ -1,5 +1,7 @@
<?php
use EesyPHP\Log;
/*
********************************************************************
* Translations CLI commands *
@ -56,7 +58,7 @@ function cli_extract_messages($command_args) {
false // do not escape command args (already done)
);
if (!is_array($php_files) || $php_files[0] != 0) {
logging('FATAL', _("Fail to list PHP files."));
Log :: fatal(_("Fail to list PHP files."));
}
// Extract messages from PHP files using xgettext
@ -73,7 +75,7 @@ function cli_extract_messages($command_args) {
$php_files[1] // Pass PHP files list via STDIN
);
if (!is_array($result) || $result[0] != 0)
logging('FATAL', _("Fail to extract messages from PHP files using xgettext."));
Log :: fatal(_("Fail to extract messages from PHP files using xgettext."));
// List JS files to parse
@ -83,7 +85,7 @@ function cli_extract_messages($command_args) {
false // do not escape command args (already done)
);
if (!is_array($js_files) || $js_files[0] != 0) {
logging('FATAL', _("Fail to list JS files."));
Log :: fatal(_("Fail to list JS files."));
}
// Extract messages from JS files using xgettext
@ -100,7 +102,7 @@ function cli_extract_messages($command_args) {
$js_files[1] // Pass JS files list via STDIN
);
if (!is_array($result) || $result[0] != 0)
logging('FATAL', _("Fail to extract messages from JS files using xgettext."));
Log :: fatal(_("Fail to extract messages from JS files using xgettext."));
// Extract messages from templates files using tsmarty2c.php
$result = run_external_command(
@ -111,8 +113,7 @@ function cli_extract_messages($command_args) {
)
);
if (!is_array($result) || $result[0] != 0)
logging(
'FATAL',
Log :: fatal(
_("Fail to extract messages from template files using tsmarty2c.php script."));
$fd = fopen("$root_lang_dir/headers.pot", 'w');
@ -139,7 +140,7 @@ function cli_extract_messages($command_args) {
"-o", "$root_lang_dir/messages.pot",
));
if (!is_array($result) || $result[0] != 0)
logging('FATAL', _("Fail to merge messages using msgcat."));
Log :: fatal(_("Fail to merge messages using msgcat."));
}
add_cli_command(
'extract_messages',
@ -162,14 +163,14 @@ function cli_update_messages($command_args) {
$compendium_args = array();
foreach ($command_args as $path) {
if (!file_exists($path))
logging('FATAL', _("Compendium file %s not found."), $path);
Log :: fatal(_("Compendium file %s not found."), $path);
$compendium_args[] = '-C';
$compendium_args[] = $path;
}
$pot_file = "$root_lang_dir/messages.pot";
if (!is_file($pot_file))
logging('FATAL', _("POT file not found (%s). Please run extract_messages first."), $pot_file);
Log :: fatal(_("POT file not found (%s). Please run extract_messages first."), $pot_file);
if ($dh = opendir($root_lang_dir)) {
$error = False;
@ -181,13 +182,13 @@ function cli_update_messages($command_args) {
)
continue;
logging('DEBUG', _("Lang directory '%s' found"), $file);
Log :: debug(_("Lang directory '%s' found"), $file);
// Check LC_MESSAGES directory exists
$lang = $file;
$lang_dir = $root_lang_dir . '/' . $file . '/LC_MESSAGES' ;
if (!is_dir($lang_dir)) {
logging('DEBUG', _("LC_MESSAGES directory not found in lang '%s' directory, ignore it."),
Log :: debug(_("LC_MESSAGES directory not found in lang '%s' directory, ignore it."),
$lang);
continue;
}
@ -202,7 +203,7 @@ function cli_update_messages($command_args) {
if (is_array($result) && $result[0] == 0) {
$created = true;
} else {
logging('ERROR', _("Fail to init messages in %s PO file using msginit (%s)."),
Log :: error(_("Fail to init messages in %s PO file using msginit (%s)."),
$lang, $po_file);
$error = True;
}
@ -220,20 +221,20 @@ function cli_update_messages($command_args) {
)
);
if (!is_array($result) || $result[0] != 0) {
logging('ERROR', _("Fail to update messages in %s PO file using msgmerge (%s)."),
Log :: error(_("Fail to update messages in %s PO file using msgmerge (%s)."),
$lang, $po_file);
$error = True;
}
}
elseif (!$created) {
logging('DEBUG', _("PO file not found in lang '%s' directory, ignore it."), $lang);
Log :: debug(_("PO file not found in lang '%s' directory, ignore it."), $lang);
}
}
closedir($dh);
return !$error;
}
logging('FATAL', _("Fail to open root lang directory (%s)."), $root_dir_path);
Log :: fatal(_("Fail to open root lang directory (%s)."), $root_dir_path);
return false;
}
add_cli_command(
@ -268,29 +269,28 @@ function cli_compile_messages($command_args) {
if (dirname($real_lang_dir) != '.' || !is_dir($root_lang_dir . '/' . $real_lang_dir))
continue;
$lang = $file;
logging('DEBUG', _("Lang alias symlink found: %s -> %s"), $lang, $real_lang_dir);
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";
$link_target = "$real_lang_dir.js";
if (!file_exists($js_link)) {
if (symlink($link_target, $js_link)) {
logging('INFO', _("JSON catalog symlink for %s -> %s created (%s)"),
Log :: info(_("JSON catalog symlink for %s -> %s created (%s)"),
$lang, $real_lang_dir, $js_link);
}
else {
logging('ERROR', _("Fail to create JSON catalog symlink for %s -> %s (%s)"),
Log :: error(_("Fail to create JSON catalog symlink for %s -> %s (%s)"),
$lang, $real_lang_dir, $js_link);
$error = True;
}
}
elseif (readlink($js_link) == $link_target) {
logging('DEBUG', _("JSON catalog symlink for %s -> %s already exist (%s)"),
Log :: debug(_("JSON catalog symlink for %s -> %s already exist (%s)"),
$lang, $real_lang_dir, $js_link);
}
else {
logging(
'WARNING',
Log :: warning(
_("JSON catalog file for %s already exist, but it's not a symlink to %s (%s)"),
$lang, $real_lang_dir, $js_link
);
@ -299,13 +299,13 @@ function cli_compile_messages($command_args) {
continue;
}
logging('DEBUG', _("Lang directory '%s' found"), $file);
Log :: debug(_("Lang directory '%s' found"), $file);
// Check LC_MESSAGES directory exists
$lang = $file;
$lang_dir = $root_lang_dir . '/' . $file . '/LC_MESSAGES' ;
if (!is_dir($lang_dir)) {
logging('DEBUG', _("LC_MESSAGES directory not found in lang '%s' directory, ignore it."),
Log :: debug(_("LC_MESSAGES directory not found in lang '%s' directory, ignore it."),
$lang);
continue;
}
@ -313,7 +313,7 @@ function cli_compile_messages($command_args) {
// Test .PO file is present
$po_file = $lang_dir . '/' . TEXT_DOMAIN . '.po';
if (!is_file($po_file)) {
logging('DEBUG', _("PO file not found in lang '%s' directory, ignore it."),
Log :: debug(_("PO file not found in lang '%s' directory, ignore it."),
$lang);
continue;
}
@ -325,8 +325,7 @@ function cli_compile_messages($command_args) {
array("msgfmt", "-o", $mo_file, $po_file)
);
if (!is_array($result) || $result[0] != 0) {
logging(
'ERROR',
Log :: error(
_("Fail to compile messages from %s PO file as MO file using msgfmt (%s)."),
$lang, $po_file
);
@ -337,24 +336,24 @@ function cli_compile_messages($command_args) {
$json_catalog = po2json($lang, $po_file);
$js_file = "$root_dir_path/public_html/translations/$lang.js";
if(!$fd = fopen($js_file, 'w')) {
logging('ERROR', _("Fail to open %s JSON catalog file in write mode (%s)."),
Log :: error(_("Fail to open %s JSON catalog file in write mode (%s)."),
$lang, $js_file);
$error = True;
}
elseif (fwrite($fd, sprintf("translations_data = %s;", $json_catalog)) === false) {
logging('ERROR', _("Fail to write %s JSON catalog in file (%s)."),
Log :: error(_("Fail to write %s JSON catalog in file (%s)."),
$lang, $js_file);
$error = True;
}
else {
logging('INFO', _("%s JSON catalog writed (%s)."), $lang, $js_file);
Log :: info(_("%s JSON catalog writed (%s)."), $lang, $js_file);
}
}
closedir($dh);
return !$error;
}
logging('FATAL', _("Fail to open root lang directory (%s)."), $root_dir_path);
Log :: fatal(_("Fail to open root lang directory (%s)."), $root_dir_path);
return false;
}
add_cli_command(

View file

@ -1,5 +1,7 @@
<?php
use EesyPHP\Log;
// Gettext text domain
define('TEXT_DOMAIN', 'DEFAULT');
@ -14,7 +16,7 @@ define('TEXT_DOMAIN', 'DEFAULT');
function get_available_langs($as_locales=false) {
global $root_lang_dir;
if (!is_dir($root_lang_dir))
logging('FATAL', "Root land directory not found ($root_lang_dir)");
Log :: fatal("Root land directory not found ($root_lang_dir)");
$langs = array(($as_locales?'en_US.UTF8':'en'));
if ($dh = opendir($root_lang_dir)) {
while (($file = readdir($dh)) !== false) {
@ -31,7 +33,7 @@ function get_available_langs($as_locales=false) {
closedir($dh);
}
$langs = array_unique($langs);
logging('TRACE', 'Available '.($as_locales?'locales':'languages').': '.implode(', ', $langs));
Log :: trace('Available '.($as_locales?'locales':'languages').': '.implode(', ', $langs));
return $langs;
}
@ -80,7 +82,7 @@ function init_translation() {
$root_lang_dir = "$root_dir_path/lang";
if (!class_exists('Locale')) {
logging('ERROR', 'Locale PHP class does not exist. May be php-intl is not installed?');
Log :: error('Locale PHP class does not exist. May be php-intl is not installed?');
return;
}
@ -88,11 +90,11 @@ function init_translation() {
if (php_sapi_name() != "cli") {
if (isset($_REQUEST['lang']) && in_array($_REQUEST['lang'], $available_langs)) {
$lang = $_REQUEST['lang'];
logging('TRACE', "Select lang from request parameter: '$lang'");
Log :: trace("Select lang from request parameter: '$lang'");
}
elseif (isset($_SESSION['lang']) && in_array($_SESSION['lang'], $available_langs) && !isset($_REQUEST['reset_lang'])) {
$lang = $_SESSION['lang'];
logging('TRACE', "Restore lang from session: '$lang'");
Log :: trace("Restore lang from session: '$lang'");
}
else {
$lang = Locale::lookup(
@ -101,7 +103,7 @@ function init_translation() {
true,
Locale::getPrimaryLanguage($default_locale)
);
logging('TRACE', "Best lang found is '$lang'");
Log :: trace("Best lang found is '$lang'");
}
}
else {
@ -112,32 +114,32 @@ function init_translation() {
if ($sys_current)
$lang = Locale::getPrimaryLanguage($sys_current);
if (is_null($lang)) {
logging('TRACE', 'No configured lang detected from CLI env, use default.');
Log :: trace('No configured lang detected from CLI env, use default.');
$lang = Locale::getPrimaryLanguage($default_locale);
}
else
logging('TRACE', "Lang detected from CLI env : '$lang'");
Log :: trace("Lang detected from CLI env : '$lang'");
}
// Keep selected lang in session
$_SESSION['lang'] = $lang;
$locale = lang2locale($lang);
logging('TRACE', "Matching locale found with language '$lang' is '$locale'");
Log :: trace("Matching locale found with language '$lang' is '$locale'");
// Gettext firstly look the LANGUAGE env variable, so set it
if (!putenv("LANGUAGE=$locale"))
logging('ERROR', "Fail to set LANGUAGE variable in environnement to '$locale'");
Log :: error("Fail to set LANGUAGE variable in environnement to '$locale'");
// Set the locale
if (setlocale(LC_ALL, $locale) === false)
logging('ERROR', "An error occured setting locale to '$locale'");
Log :: error("An error occured setting locale to '$locale'");
// Configure and set the text domain
$fullpath = bindtextdomain(TEXT_DOMAIN, $root_lang_dir);
logging('TRACE', "Text domain fullpath is '$fullpath'.");
logging('TRACE', "Text domain is '".textdomain(TEXT_DOMAIN)."'.");
logging('TRACE', "Test: "._('Hello world !'));
Log :: trace("Text domain fullpath is '$fullpath'.");
Log :: trace("Text domain is '".textdomain(TEXT_DOMAIN)."'.");
Log :: trace("Test: "._('Hello world !'));
// JS translation file
$js_translation_file = "translations/$lang.js";

View file

@ -1,14 +1,17 @@
<?php
use EesyPHP\Check;
use EesyPHP\Log;
function get_item_from_url($id, $fatal=false) {
if (!check_id($id))
logging('FATAL', _('Invalid element identifier.'));
if (!Check :: id($id))
Log :: fatal(_('Invalid element identifier.'));
$item = get_item($id);
if(!is_array($item)) {
$error = sprintf(_("Item #% s not found."), $id);
if ($fatal)
logging('FATAL', $error);
Log :: fatal($error);
add_error($error);
return false;
}

View file

@ -1,12 +1,18 @@
<?php
use EesyPHP\Check;
use EesyPHP\Log;
use EesyPHP\Url;
use function EesyPHP\vardump;
if (php_sapi_name() == "cli")
return true;
function handle_homepage($request) {
display_template("homepage.tpl", _("Hello world !"));
}
add_url_handler('#^$#', 'handle_homepage');
Url :: add_url_handler('#^$#', 'handle_homepage');
function handle_search($request) {
global $smarty, $status_list;
@ -24,9 +30,9 @@ function handle_search($request) {
'order_direction' => 'ASC',
);
if (isset($_REQUEST['clear']) && $_REQUEST['clear']=='true')
redirect($request -> current_url);
Url :: redirect($request -> current_url);
}
logging('DEBUG', 'Request params : '.vardump($_REQUEST));
Log :: debug('Request params : '.vardump($_REQUEST));
$status_list['all'] = _('Any');
if (isset($_REQUEST['status'])) {
@ -39,7 +45,7 @@ function handle_search($request) {
if (isset($_REQUEST['pattern'])) {
if (trim($_REQUEST['pattern']) == '')
$_SESSION['search']['pattern'] = false;
else if (check_search_pattern($_REQUEST['pattern']))
else if (Check :: search_pattern($_REQUEST['pattern']))
$_SESSION['search']['pattern'] = $_REQUEST['pattern'];
else
$smarty -> assign('pattern_error', true);
@ -82,7 +88,7 @@ function handle_search($request) {
$_SESSION['search']['nb_by_page']=$nbs_by_page[0];
}
logging('DEBUG', 'Search params : '.vardump($_SESSION['search']));
Log :: debug('Search params : '.vardump($_SESSION['search']));
$result = search_items($_SESSION['search']);
if (!is_array($result))
fatal_error(
@ -103,7 +109,7 @@ function handle_search($request) {
display_template("search.tpl", _("Search"));
}
add_url_handler('|^item/?$|', 'handle_search');
Url :: add_url_handler('|^item/?$|', 'handle_search');
/*
* One item pages
@ -114,7 +120,7 @@ function handle_show($request) {
$item = get_item_from_url($request -> id);
if (!$item)
error_404();
Url :: error_404();
$smarty->assign('item', $item);
@ -129,7 +135,7 @@ function handle_show($request) {
(is_array($item)?$item['name']:"#".$request -> id)
);
}
add_url_handler('|^item/(?P<id>[0-9]+)$|', 'handle_show');
Url :: add_url_handler('|^item/(?P<id>[0-9]+)$|', 'handle_show');
function handle_create($request) {
global $smarty, $status_list;
@ -140,14 +146,14 @@ function handle_create($request) {
$item = add_item($info);
if (is_array($item)) {
add_message(_("The element '% s' has been created."), $item['name']);
redirect('item/'.$item['id']);
Url :: redirect('item/'.$item['id']);
}
else {
add_error(_("An error occurred while saving this item."));
}
}
logging('DEBUG', 'Validated data : '.vardump($info));
logging('DEBUG', 'Fields errors : '.vardump($field_errors));
Log :: debug('Validated data : '.vardump($info));
Log :: debug('Fields errors : '.vardump($field_errors));
if (isset($_POST['submit']) && !empty($field_errors))
add_error(
_("There are errors preventing this item from being saved. ".
@ -159,7 +165,7 @@ function handle_create($request) {
display_template("form.tpl", _("New"));
}
add_url_handler('|^item/new$|', 'handle_create');
Url :: add_url_handler('|^item/new$|', 'handle_create');
function handle_modify($request) {
global $smarty, $status_list;
@ -168,7 +174,7 @@ function handle_modify($request) {
if(is_array($item)) {
if (!can_modify($item)) {
add_error(_('You cannot edit this item.'));
redirect('item/'.$item['id']);
Url :: redirect('item/'.$item['id']);
}
$info = array();
$field_errors = handle_item_post_data($info);
@ -178,21 +184,21 @@ function handle_modify($request) {
if ($value != $item[$key])
$changes[$key] = $value;
}
logging('DEBUG', 'Changes : '.vardump($changes));
Log :: debug('Changes : '.vardump($changes));
if (empty($changes)) {
add_message(_("You have not made any changes to element '% s'."), $item['name']);
redirect('item/'.$item['id']);
Url :: redirect('item/'.$item['id']);
}
else if (update_item($item['id'], $changes) === true) {
add_message(_("The element '% s' has been updated successfully."), $item['name']);
redirect('item/'.$item['id']);
Url :: redirect('item/'.$item['id']);
}
else {
add_error(_("An error occurred while updating this item."));
}
}
logging('DEBUG', 'Validated data : '.vardump($info));
logging('DEBUG', 'Fields errors : '.vardump($field_errors));
Log :: debug('Validated data : '.vardump($info));
Log :: debug('Fields errors : '.vardump($field_errors));
$smarty->assign('submited', isset($_POST['submit']));
if (isset($_POST['submit']) && !empty($field_errors))
add_error(
@ -204,7 +210,7 @@ function handle_modify($request) {
$smarty -> assign('status_list', $status_list);
}
else {
error_404();
Url :: error_404();
}
display_template(
@ -212,7 +218,7 @@ function handle_modify($request) {
(is_array($item)?$item['name']:"#".$request -> id)
);
}
add_url_handler('|^item/(?P<id>[0-9]+)/modify$|', 'handle_modify');
Url :: add_url_handler('|^item/(?P<id>[0-9]+)/modify$|', 'handle_modify');
function handle_archive($request) {
global $smarty;
@ -220,7 +226,7 @@ function handle_archive($request) {
$item = get_item_from_url($request -> id);
if(!is_array($item)) {
add_error(_("Item #% s not found."), $request -> id);
redirect('item');
Url :: redirect('item');
}
elseif ($item['status'] == 'archived') {
add_message(_("This item is already archived."));
@ -234,9 +240,9 @@ function handle_archive($request) {
else {
add_error(_('An error occurred while archiving this item.'));
}
redirect('item/'.$item['id']);
Url :: redirect('item/'.$item['id']);
}
add_url_handler('|^item/(?P<id>[0-9]+)/archive$|', 'handle_archive');
Url :: add_url_handler('|^item/(?P<id>[0-9]+)/archive$|', 'handle_archive');
function handle_delete($request) {
global $smarty;
@ -253,10 +259,10 @@ function handle_delete($request) {
}
else {
add_error(_('An error occurred while deleting this item.'));
redirect('item/'.$item['id']);
Url :: redirect('item/'.$item['id']);
}
redirect('item');
Url :: redirect('item');
}
add_url_handler('|^item/(?P<id>[0-9]+)/delete$|', 'handle_delete');
Url :: add_url_handler('|^item/(?P<id>[0-9]+)/delete$|', 'handle_delete');
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -1,613 +0,0 @@
<?php
/*
* Configured URL patterns :
*
* Example :
*
* array (
* '|get/(?P<name>[a-zA-Z0-9]+)$|' => array (
* 'handler' => 'get',
* 'authenticated' => true,
* 'api_mode' => false,
* 'methods' => array('GET'),
* ),
* '|get/all$|' => => array (
* 'handler' => 'get_all',
* 'authenticated' => true,
* 'api_mode' => false,
* 'methods' => array('GET', 'POST'),
* ),
* )
*
*/
$url_patterns =array();
/**
* Add an URL pattern
*
* @param string|array $pattern The URL pattern or an array of patterns (required)
* @param callable $handler The URL pattern handler (must be callable, required)
* @param boolean $authenticated Permit to define if this URL is accessible only for
* authenticated users (optional, default: true if the
* special force_authentication function is defined,
* false otherwise)
* @param boolean $override Allow override if a command already exists with the
* same name (optional, default: false)
* @param boolean $api_mode Enable API mode (optional, default: false)
* @param array|string|null $methods HTTP method (optional, default: array('GET', 'POST'))
**/
function add_url_handler($pattern, $handler=null, $authenticated=null, $override=true,
$api_mode=false, $methods=null) {
$authenticated = (
is_null($authenticated)?
function_exists('force_authentication'):
(bool)$authenticated
);
if (is_null($methods))
$methods = array('GET', 'POST');
elseif (!is_array($methods))
$methods = array($methods);
global $url_patterns;
if (is_array($pattern)) {
if (is_null($handler))
foreach($pattern as $p => $h)
add_url_handler($p, $h, $authenticated, $override, $api_mode, $methods);
else
foreach($pattern as $p)
add_url_handler($p, $handler, $authenticated, $override, $api_mode, $methods);
}
else {
if (!isset($url_patterns[$pattern])) {
$url_patterns[$pattern] = array(
'handler' => $handler,
'authenticated' => $authenticated,
'api_mode' => $api_mode,
'methods' => $methods,
);
}
elseif ($override) {
logging(
'DEBUG', "URL : override pattern '%s' with handler '%s' (old handler = '%s')".
$pattern, format_callable($handler), vardump($url_patterns[$pattern])
);
$url_patterns[$pattern] = array(
'handler' => $handler,
'authenticated' => $authenticated,
'api_mode' => $api_mode,
'methods' => $methods,
);
}
else {
logging('DEBUG', "URL : pattern '$pattern' already defined : do not override.");
}
}
}
/**
* Show error page
*
* @param $request UrlRequest|null The request (optional, default: null)
* @param $error_code int|null The HTTP error code (optional, default: 400)
*
* @return void
**/
function error_page($request=null, $error_code=null) {
global $smarty;
$http_errors = array(
400 => array(
'pagetitle' => _("Bad request"),
'message' => _("Invalid request."),
),
401 => array(
'pagetitle' => _("Authentication required"),
'message' => _("You have to be authenticated to access to this page."),
),
403 => array(
'pagetitle' => _("Access denied"),
'message' => _("You do not have access to this application. If you think this is an error, please contact support."),
),
404 => array(
'pagetitle' => _("Whoops ! Page not found"),
'message' => _("The requested page can not be found."),
),
);
$error_code = ($error_code?intval($error_code):400);
if (array_key_exists($error_code, $http_errors))
$error = $http_errors[intval($error_code)];
else
$error = array(
'pagetitle' => _('Error'),
'message' => _('An unknown error occurred. If problem persist, please contact support.'),
);
http_response_code($error_code);
$smarty -> assign('message', $error['message']);
display_template('error_page.tpl', $error['pagetitle']);
exit();
}
/*
* Error 404 page
*/
/**
* Error 404 handler
*
* @param UrlRequest|null $request The request (optional, default: null)
*
* @return void
**/
function error_404($request=null) {
error_page($request, 404);
}
$_404_url_handler = 'error_404';
function set_404_url_handler($handler=null) {
global $_404_url_handler;
$_404_url_handler = $handler;
}
/**
* Interprets the requested URL and return the corresponding UrlRequest object
*
* @param $default_url string|null The default URL if current one does not
* match with any configured pattern.
*
* @return UrlRequest The UrlRequest object corresponding to the the requested URL.
*/
function get_request($default_url=null) {
global $url_patterns, $_404_url_handler;
$current_url = get_current_url();
if ($current_url === false) {
logging(
'FATAL',
_('Unable to determine the requested page. '.
'If the problem persists, please contact support.')
);
exit();
}
if (!is_array($url_patterns)) {
logging('FATAL', 'URL : No URL patterns configured !');
exit();
}
logging('DEBUG', "URL : current url = '$current_url'");
logging(
'TRACE',
"URL : check current url with the following URL patterns :\n - ".
implode("\n - ", array_keys($url_patterns))
);
foreach ($url_patterns as $pattern => $handler_infos) {
$m = url_match($pattern, $current_url, $handler_infos['methods']);
if (is_array($m)) {
$request = new UrlRequest($current_url, $handler_infos, $m);
// Reset last redirect
if (isset($_SESSION['last_redirect']))
unset($_SESSION['last_redirect']);
logging('TRACE', "URL : result :\n".vardump($request));
return $request;
}
}
if ($default_url !== false) {
logging('DEBUG', "Current url match with no pattern. Redirect to default url ('$default_url')");
redirect($default_url);
exit();
}
// Error 404
$api_mode = (strpos($current_url, 'api/') === 0);
logging(
'DEBUG',
"Current URL match with no pattern. Use error 404 handler (API mode=$api_mode).");
return new UrlRequest(
$current_url,
array(
'handler' => $_404_url_handler,
'authenticated' => false,
'api_mode' => $api_mode,
)
);
}
/**
* Check if the current requested URL match with a specific pattern
*
* @param string $pattern The URL pattern
* @param string|false $current_url The current URL (optional)
* @param array|null $methods HTTP method (optional, default: no check)
*
* @return array|false The URL info if pattern matched, false otherwise.
**/
function url_match($pattern, $current_url=false, $methods=null) {
if ($methods && !in_array($_SERVER['REQUEST_METHOD'], $methods))
return false;
if ($current_url === false) {
$current_url = get_current_url();
if (!$current_url) return False;
}
if (preg_match($pattern, $current_url, $m)) {
logging(
'DEBUG',
"URL : Match found with pattern '$pattern' :\n\t".
str_replace("\n", "\n\t", print_r($m, true)));
return $m;
}
return False;
}
/**
* Retreive current requested URL and return it
*
* @return string|false The current request URL or false if fail
**/
function get_current_url() {
logging('TRACE', "URL : request URI = '".$_SERVER['REQUEST_URI']."'");
$base = get_rewrite_base();
logging('TRACE', "URL : rewrite base = '$base'");
if ($_SERVER['REQUEST_URI'] == $base)
return '';
if (substr($_SERVER['REQUEST_URI'], 0, strlen($base)) != $base) {
logging(
'ERROR',
"URL : request URI (".$_SERVER['REQUEST_URI'].") does not start with rewrite base ($base)");
return False;
}
$current_url = substr($_SERVER['REQUEST_URI'], strlen($base));
// URL contain params ?
$params_start = strpos($current_url, '?');
if ($params_start !== false)
// Params detected, remove it
// No url / currrent url start by '?' ?
if ($params_start == 0)
return '';
else
return substr($current_url, 0, $params_start);
return $current_url;
}
/**
* Try to detect rewrite base from public root URL
*
* @return string The detected rewrite base
**/
function get_rewrite_base() {
global $public_root_url;
if (preg_match('|^https?://[^/]+/(.*)$|', $public_root_url, $m))
return '/'.remove_trailing_slash($m[1]).'/';
elseif (preg_match('|^/(.*)$|', $public_root_url, $m))
return '/'.remove_trailing_slash($m[1]).'/';
return '/';
}
/**
* Trigger redirect to specified URL (or homepage if omited)
*
* @param string|false $go The destination URL
*
* @return void
**/
function redirect($go=false) {
global $public_root_url;
if ($go===false)
$go = "";
// If more than one argument passed, format URL using sprintf & urlencode parameters
elseif (func_num_args() > 1)
$go = call_user_func_array(
'sprintf',
array_merge(
array($go),
array_map('urlencode', array_slice(func_get_args(), 1))
)
);
if (is_absolute_url($go))
$url = $go;
elseif (isset($public_root_url) && $public_root_url) {
// Check $public_root_url end
if (substr($public_root_url, -1)=='/') {
$public_root_url=substr($public_root_url, 0, -1);
}
$url="$public_root_url/$go";
}
else
$url="/$go";
// Prevent loop
if (isset($_SESSION['last_redirect']) && $_SESSION['last_redirect'] == $url)
logging(
'FATAL',
_('Unable to determine the requested page (loop detected). '.
'If the problem persists, please contact support.'));
else
$_SESSION['last_redirect'] = $url;
logging('DEBUG',"redirect($go) => Redirect to : <$url>");
header("Location: $url");
exit();
}
/**
* Handle the current requested URL
*
* Note: if the route required that user is authenticated, this method will
* invoke the force_authentication() special function (or trigger a fatal error
* if it's not defined).
*
* @param string|null $default_url The default URL if current one does not
* match with any configured pattern.
*
* @return void
**/
function handle_request($default_url=null) {
global $smarty, $api_mode;
$sentry_span = new SentrySpan('http.handle_request', 'Handle the HTTP request');
$request = get_request($default_url);
if (!is_callable($request -> handler)) {
logging(
'ERROR', "URL handler function %s does not exists !",
format_callable($request -> handler));
logging('FATAL', _("This request cannot be processed."));
}
if ($request -> api_mode)
$api_mode = true;
if (isset($smarty) && $smarty)
$smarty -> assign('request', $request);
// Check authentication (if need)
if($request -> authenticated)
if (function_exists('force_authentication'))
force_authentication();
else
logging('FATAL', _("Authentication required but force_authentication function is not defined."));
try {
call_user_func($request -> handler, $request);
}
catch (Exception $e) {
log_exception(
$e, "An exception occured running URL handler function ".$request -> handler."()");
logging('FATAL', _("This request could not be processed correctly."));
}
$sentry_span->finish();
}
/**
* Remove trailing slash in specified URL
*
* @param string $url The URL
*
* @return string The specified URL without trailing slash
**/
function remove_trailing_slash($url) {
if ($url == '/')
return $url;
elseif (substr($url, -1) == '/')
return substr($url, 0, -1);
return $url;
}
/**
* Check an AJAX request and trigger a fatal error on fail
*
* Check if session key is present and valid and set AJAX
* mode.
*
* @param string|null $session_key string The current session key (optional)
*
* @return void
**/
function check_ajax_request($session_key=null) {
global $ajax, $debug_ajax;
$ajax = true;
if (check_session_key($session_key))
fatal_error('Invalid request');
if ($debug_ajax)
logging('DEBUG',"Ajax Request : ".vardump($_REQUEST));
}
/**
* Get the public absolute URL
*
* @param string|null $relative_url Relative URL to convert (Default: current URL)
*
* @return string The public absolute URL
**/
function get_absolute_url($relative_url=null) {
global $public_root_url;
if (!is_string($relative_url))
$relative_url = get_current_url();
if ($public_root_url[0] == '/') {
logging(
'DEBUG',
"URL :: get_absolute_url($relative_url): configured public root URL is relative ".
"($public_root_url) => try to detect it from current request infos.");
$public_root_url = (
'http'.(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'?'s':'').'://'.
$_SERVER['HTTP_HOST'].$public_root_url);
logging(
'DEBUG',
"URL :: get_absolute_url($relative_url): detected public_root_url: $public_root_url");
}
if (substr($relative_url, 0, 1) == '/')
$relative_url = substr($relative_url, 1);
$url = remove_trailing_slash($public_root_url)."/$relative_url";
logging('DEBUG', "URL :: get_absolute_url($relative_url): result = $url");
return $url;
}
/**
* Check if specified URL is absolute
*
* @param string $url The URL to check
*
* @return boolean True if specified URL is absolute, False otherwise
**/
function is_absolute_url($url) {
return boolval(preg_match('#^https?://#', $url));
}
/**
* Add parameter in specified URL
*
* @param string &$url The reference of the URL
* @param string $param The parameter name
* @param string $value The parameter value
* @param boolean $encode Set if parameter value must be URL encoded (optional, default: true)
*
* @return string The completed URL
*/
function add_url_parameter(&$url, $param, $value, $encode=true) {
if (strpos($url, '?') === false)
$url .= '?';
else
$url .= '&';
$url .= "$param=".($encode?urlencode($value):$value);
return $url;
}
/**
* URL request abstraction
*
* @author Benjamin Renard <brenard@easter-eggs.com>
*/
class UrlRequest {
// The URL requested handler
private $current_url = null;
// The URL requested handler
private $handler = null;
// Request need authentication ?
private $authenticated = true;
// API mode enabled ?
private $api_mode = false;
// Parameters detected on requested URL
private $url_params = array();
public function __construct($current_url, $handler_infos, $url_params=array()) {
$this -> current_url = $current_url;
$this -> handler = $handler_infos['handler'];
$this -> authenticated = (
isset($handler_infos['authenticated'])?
boolval($handler_infos['authenticated']):true);
$this -> api_mode = (
isset($handler_infos['api_mode'])?
boolval($handler_infos['api_mode']):false);
$this -> url_params = $url_params;
}
/**
* Get request info
*
* @param string $key The name of the info
*
* @return mixed The value
**/
public function __get($key) {
if ($key == 'current_url')
return $this -> current_url;
if ($key == 'handler')
return $this -> handler;
if ($key == 'authenticated')
return $this -> authenticated;
if ($key == 'api_mode')
return $this -> api_mode;
if ($key == 'referer')
return $this -> get_referer();
if ($key == 'http_method')
return $_SERVER['REQUEST_METHOD'];
if (array_key_exists($key, $this->url_params)) {
return urldecode($this->url_params[$key]);
}
// Unknown key, log warning
logging('WARNING', "__get($key): invalid property requested\n".get_debug_backtrace_context());
}
/**
* Set request info
*
* @param string $key The name of the info
* @param mixed $value The value of the info
*
* @return void
**/
public function __set($key, $value) {
if ($key == 'referer')
$_SERVER['HTTP_REFERER'] = $value;
elseif ($key == 'http_method')
$_SERVER['REQUEST_METHOD'] = $value;
else
$this->url_params[$key] = $value;
}
/**
* Check is request info is set
*
* @param string $key The name of the info
*
* @return bool True is info is set, False otherwise
**/
public function __isset($key) {
if (
in_array(
$key, array('current_url', 'handler', 'authenticated',
'api_mode', 'referer', 'http_method')
)
)
return True;
return array_key_exists($key, $this->url_params);
}
/**
* Get request parameter
*
* @param string $parameter The name of the parameter
* @param bool $decode If true, the parameter value will be urldecoded
* (optional, default: true)
*
* @return mixed The value or false if parameter does not exists
**/
public function get_param($parameter, $decode=true) {
if (array_key_exists($parameter, $this->url_params)) {
if ($decode)
return urldecode($this->url_params[$parameter]);
return $this->url_params[$parameter];
}
return false;
}
/**
* Get request referer (if known)
*
* @return string|null The request referer URL if known, null otherwise
*/
public function get_referer() {
if (isset($_SERVER['HTTP_REFERER']) && $_SERVER['HTTP_REFERER'])
return $_SERVER['HTTP_REFERER'];
return null;
}
}
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -1,6 +1,7 @@
parameters:
level: 5
paths:
- src
- includes
- public_html
- bin
@ -9,21 +10,20 @@ parameters:
ignoreErrors:
-
message: "#Instantiated class Mail_mime not found\\.#"
path: includes/mail.php
path: src/Email.php
-
message: "#Call to method (setFrom|setSubject|setHTMLBody|setTXTBody|get|headers|addAttachment)\\(\\) on an unknown class Mail_mime\\.#"
path: includes/mail.php
path: src/Email.php
-
message: "#Call to static method factory\\(\\) on an unknown class Mail\\.#"
path: includes/mail.php
path: src/Email.php
-
message: "#Call to static method isError\\(\\) on an unknown class PEAR\\.#"
path: includes/mail.php
path: src/Email.php
-
message: "#Variable \\$root_dir_path might not be defined\\.#"
paths:
- includes/config.inc.php
- "#Access to private property UrlRequest::\\$(handler|api_mode|authenticated)\\.#"
-
message: "#Variable \\$status_list might not be defined\\.#"
paths:

View file

@ -3,7 +3,9 @@
include '../includes/core.php';
include 'url-public.php';
use EesyPHP\Url;
$default_url='';
handle_request();
Url :: handle_request();
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

97
src/Check.php Normal file
View file

@ -0,0 +1,97 @@
<?php
namespace EesyPHP;
class Check {
public static function name($name) {
if (preg_match('/^[\w \-]{2,}$/iu',$name))
return true;
return false;
}
public static function id(&$id) {
if (is_int($id))
return true;
if (preg_match('/^[0-9]+$/', $id)) {
$id = intval($id);
return true;
}
return false;
}
public static function search_pattern($pattern) {
foreach(preg_split('/\s+/', trim($pattern)) as $word) {
if (!self :: id($word) && !self :: name($word))
return false;
}
return true;
}
public static function time(&$time) {
if (!is_int($time)) {
if (preg_match('/^[0-9]+$/', $time))
$time = intval($time);
else
return false;
}
return ($time >= 1577833200); // 2020-01-01 - date of birth of this soft
}
public static function description($comment) {
if (preg_match("/^[\p{L}0-9\p{P}\p{Zs}\p{Zl}\p{Sc}\=\+]+$/uim", $comment))
return true;
return false;
}
public static function email($value, $domain=NULL, $checkDns=true) {
$regex = '/^((\"[^\"\f\n\r\t\v\b]+\")|([\w\!\#\$\%\&\'\*\+\-\~\/\^\`\|\{\}]+(\.[\w\!\#\$\%\&\'\*\+\-\~\/\^\`\|\{\}]+)*))@((\[(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))\])|(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))|((([A-Za-z0-9\-])+\.)+[A-Za-z\-]+))$/';
if (!preg_match($regex, $value)) {
return false;
}
$nd = explode('@', $value);
$nd=$nd[1];
if ($domain) {
if(is_array($domain)) {
if (!in_array($nd,$domain)) {
return false;
}
}
else {
if($nd!=$domain) {
return false;
}
}
}
if ($checkDns && function_exists('checkdnsrr')) {
if (!(checkdnsrr($nd, 'MX') || checkdnsrr($nd, 'A'))) {
return false;
}
}
return true;
}
public static function is_empty($val) {
switch(gettype($val)) {
case "boolean":
case "integer":
case "double":
case "object":
case "resource":
return False;
case "array":
case "string":
if ($val == "0") return false;
return empty($val);
case "NULL":
return True;
}
}
}
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

211
src/Email.php Normal file
View file

@ -0,0 +1,211 @@
<?php
namespace EesyPHP;
use EesyPHP\Log;
use PEAR;
use Mail;
use Mail_mime;
use finfo;
class Email {
/**
* Default sender
* @var string|null;
*/
protected static $sender = null;
/**
* Sending method :
* - mail : use PHP mail function
* - sendmail : use sendmail system command
* - smtp : use an SMTP server (PHP PEAR Net_SMTP required)
* @var string
*/
protected static $send_method = 'mail';
/**
* Sending parameters
* @see http://pear.php.net/manual/en/package.mail.mail.factory.php
* @var array|null
*/
protected static $send_params = null;
/**
* Catch all sent email recipient
* @var string|array<string>|null
*/
protected static $catch_all = null;
/**
* Default headers to add on all sent emails
* @var array<string>
*/
protected static $headers = array();
/**
* PHP PEAR Mail lib path
* @var string
*/
protected static $php_mail_path = 'Mail.php';
/**
* PHP PEAR Mail lib path
* @var string
*/
protected static $php_mail_mime_path = 'Mail/mime.php';
/**
* 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)
* @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 ($sender) self :: $sender = $sender;
if ($send_method) self :: $send_method = $send_method;
if ($send_params) self :: $send_params = $send_params;
if ($catch_all) self :: $catch_all = $catch_all;
if ($headers) self :: $headers = $headers;
if ($php_mail_path) self :: $php_mail_path = $php_mail_path;
if ($php_mail_mime_path) self :: $php_mail_mime_path = $php_mail_mime_path;
}
/**
* Send an email
*
* @param string|null $from Email sender
* @param string|array<string> $to Email recipient(s)
* @param string $subject Email subject
* @param string $msg Email body
* @param boolean $html Set to true to send an HTML email (default: false)
* @param array<string,string>|null $attachments Email attachments as an array with
* filepath as key and filename as value
* @param array<string,string>|null $headers Email headers
* @param string|null $encoding Email encoding (default: utf8)
* @param string|null $eol End of line string (default : \n)
*
* @return boolean true If mail was sent, false otherwise
*/
public static function send($from, $to, $subject, $msg, $html=false, $attachments=null,
$headers=null, $encoding=null, $eol=null) {
if (!class_exists('Mail'))
require_once(self :: $php_mail_path);
if (!class_exists('Mail_mime'))
require_once(self :: $php_mail_mime_path);
$mail_obj = Mail::factory(self :: $send_method, self :: $send_params);
if (!$headers) $headers = array();
$headers = array_merge($headers, self :: $headers);
Log :: trace(
'Mail catch all: %s',
self :: $catch_all?
vardump(self :: $catch_all):'not set'
);
if (self :: $catch_all) {
Log :: debug(
'Mail catch to %s',
is_array(self :: $catch_all)?implode(',', self :: $catch_all):self :: $catch_all
);
$msg .= sprintf(
(
$html?
_("</hr><p><small>Mail initialy intended for %s.</small></p>"):
_("\n\n\nMail initialy intended for %s.")
),
(is_array($to)?implode(',', $to):$to));
$headers["X-Orig-To"] = $to;
$to = (
is_array(self :: $catch_all)?
implode(',', self :: $catch_all):self :: $catch_all
);
}
if ($subject) {
$headers["Subject"] = $subject;
}
if (isset($headers['From'])) {
if (!$from)
$from = $headers['From'];
unset($headers['From']);
}
elseif (!$from) {
$from = self :: $sender;
}
$headers["To"] = $to;
$to = array (
'To' => $to
);
foreach(array_keys($headers) as $header) {
if(in_array(strtoupper($header), array('BCC', 'CC'))) {
if (self :: $catch_all) {
Log :: debug("Mail catched: remove $header header");
$msg .= sprintf(
(
$html?
_("<p><small>%s: %s</small></p>"):
_("\n%s: %s")
),
strtoupper($header),
(is_array($headers[$header])?implode(',', $headers[$header]):$headers[$header]));
unset($headers[$header]);
continue;
}
$to[strtoupper($header)] = $headers[$header];
}
}
if (!$encoding) $encoding = "utf8";
$mime = new Mail_mime(
array(
'eol' => ($eol?$eol:"\n"),
($html?'html_charset':'text_charset') => $encoding,
'head_charset' => $encoding,
)
);
if ($from)
$mime->setFrom($from);
if ($subject)
$mime->setSubject($subject);
if ($html)
$mime->setHTMLBody($msg);
else
$mime->setTXTBody($msg);
if (is_array($attachments) && !empty($attachments)) {
$finfo = new finfo(FILEINFO_MIME_TYPE);
foreach ($attachments as $file => $filename) {
$mime->addAttachment($file, $finfo->file($file), $filename);
}
}
$body = $mime->get();
$headers = $mime->headers($headers);
$ret = $mail_obj -> send($to, $headers, $body);
if (PEAR::isError($ret)) {
$msg = "Error sending email: ".$ret -> getMessage();
Log :: error($msg);
return false;
}
return true;
}
}
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

89
src/Hook.php Normal file
View file

@ -0,0 +1,89 @@
<?php
namespace EesyPHP;
use EesyPHP\Log;
use Exception;
class Hook {
/**
* Registered hooks
* @var array
*/
protected static $hooks = array();
/**
* Registered a hook on a specific event
*
* @param $event string The event name
* @param $callable callable The callable to run on event
* @param $param mixed Paremeter that will be pass to the callable
* Use an array if you have multiple parameters to pass
*
* @return void
*/
public static function register($event, $callable, $param=NULL) {
if (!array_key_exists($event, self :: $hooks))
self :: $hooks[$event] = array();
self :: $hooks[$event][] = array (
'callable' => $callable,
'param' => $param,
);
}
/**
* Run triggered actions on specific event
*
* @param $event string Hook event name
* @param $event_data mixed Hook event data (optional, default: null)
*
* @return boolean True if all triggered actions succefully runned, false otherwise
*/
public static function trigger($event_name, $event_data=null) {
$return = true;
if (isset(self :: $hooks[$event_name]) && is_array(self :: $hooks[$event_name])) {
if ($event_name == 'all')
$event = new HookEvent($event_data['event_name'], $event_data['event_data']);
else
$event = new HookEvent($event_name, $event_data);
foreach (self :: $hooks[$event_name] as $e) {
if (is_callable($e['callable'])) {
try {
call_user_func_array($e['callable'],array($event, &$e['param']));
}
catch(Exception $e) {
Log :: exception(
$e, "An exception occured running hook ".format_callable($e['callable']).
" on event $event_name");
$return = false;
}
}
else {
Log :: error(
"The hook ".format_callable($e['callable'])." on event $event_name is not callable.");
$return = false;
}
}
}
else
Log :: debug("No hook registered for event $event_name.");
// Handle 'all' event
if ($event_name != 'all') {
self :: trigger (
'all',
array (
'event_name' => $event_name,
'event_data' => $event_data,
)
);
}
return $return;
}
}
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

34
src/HookEvent.php Normal file
View file

@ -0,0 +1,34 @@
<?php
namespace EesyPHP;
use JsonSerializable;
class HookEvent implements JsonSerializable {
private $name;
private $data;
function __construct($name, $data) {
$this -> name = $name;
$this -> data = $data;
}
function __get($key) {
if ($key == 'name')
return $this -> name;
elseif ($key == 'data')
return $this -> data;
elseif (is_array($this -> data) && array_key_exists($key, $this -> data))
return $this -> data[$key];
return null;
}
public function jsonSerialize() {
return array (
'name' => $this -> name,
'data' => $this -> data,
);
}
}
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

363
src/Log.php Normal file
View file

@ -0,0 +1,363 @@
<?php
namespace EesyPHP;
use EesyPHP\SentryIntegration;
use Throwable;
class Log {
/*
* Log file path
* @var string|null
*/
protected static $filepath = null;
/*
* Log file descriptor
* @var resource|null
*/
protected static $file_fd = null;
/*
* Log Levels
* @var array(string,int)
*/
protected static $levels = array(
'TRACE' => 0,
'DEBUG' => 1,
'INFO' => 2,
'WARNING' => 3,
'ERROR' => 4,
'FATAL' => 5,
);
/*
* Default log level
* @var string
*/
protected static string $default_level = 'WARNING';
/*
* Current log level
* @var string|null
*/
protected static $level = null;
/*
* Log PHP errors levels (as specified to set_error_handler())
* Default (set in self::init() method):
* - In TRACE or DEBUG: E_ALL & ~E_STRICT
* - Otherwise: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
*/
protected static $php_errors_levels = null;
// Custom fatal error handler
protected static $fatal_error_handler = null;
/*
* Initialization
* @param string $filepath
* @param string|null $level
* @param int|null $php_errors_levels
* @return void
*/
public static function init($filepath, $level=null, $php_errors_levels=null) {
self :: $filepath = $filepath;
// Set default log level (if not defined or invalid)
if (is_null($level)) {
self :: $level = self :: $default_level;
}
elseif (!array_key_exists($level, self :: $levels)) {
self :: $level = self :: $default_level;
self :: warning(
"Invalid log level value found in configuration (%s). ".
"Set as default (%s).", $level, self :: $default_level);
}
else {
self :: $level = $level;
}
// Log PHP errors
if (!is_null($php_errors_levels))
self :: $php_errors_levels = $php_errors_levels;
elseif (in_array(self :: $level, array('DEBUG', 'TRACE')))
self :: $php_errors_levels = E_ALL & ~E_STRICT;
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
set_exception_handler(array('EesyPHP\\Log', 'exception'));
}
/**
* Log a message
* @param string $level The message level (key of self :: $levels)
* @param string $message The message to log
* @param array $extra_args Extra arguments to use to compute message using sprintf
* @return true
*/
public static function log($level, $message, ...$extra_args) {
global $auth_user, $argv;
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)) {
self :: $file_fd = fopen(self :: $filepath, 'a');
}
// Extra arguments passed, format message using sprintf
if ($extra_args) {
$message = call_user_func_array(
'sprintf',
array_merge(array($message), $extra_args)
);
}
if (php_sapi_name() == "cli") {
$msg = implode(' - ', array(
date('Y/m/d H:i:s'),
basename($argv[0]),
$level,
$message
))."\n";
}
else {
$msg = array(
date('Y/m/d H:i:s'),
$_SERVER['REQUEST_URI'],
$_SERVER['REMOTE_ADDR'],
);
if (isset($auth_user))
$msg[] = ($auth_user['username']?$auth_user['username']:'anonymous');
$msg[] = $level;
$msg[] = $message;
$msg = implode(' - ', $msg)."\n";
}
fwrite(self :: $file_fd, $msg);
if ($level == 'FATAL')
if (!is_null(self :: $fatal_error_handler))
call_user_func(self :: $fatal_error_handler, $message);
elseif (function_exists('fatal_error'))
fatal_error($message);
else
die("\n$message\n\n");
elseif (php_sapi_name() == "cli")
echo $msg;
return true;
}
/**
* Log a trace message
* @param string $message The message to log
* @param array $extra_args Extra arguments to use to compute message using sprintf
* @return true
*/
public static function trace($message, ...$extra_args) {
return call_user_func_array(
array('EesyPHP\\Log', 'log'),
array_merge(array('TRACE', $message), $extra_args)
);
}
/**
* Log a debug message
* @param string $message The message to log
* @param array $extra_args Extra arguments to use to compute message using sprintf
* @return true
*/
public static function debug($message, ...$extra_args) {
return call_user_func_array(
array('EesyPHP\\Log', 'log'),
array_merge(array('DEBUG', $message), $extra_args)
);
}
/**
* Log an info message
* @param string $message The message to log
* @param array $extra_args Extra arguments to use to compute message using sprintf
* @return true
*/
public static function info($message, ...$extra_args) {
return call_user_func_array(
array('EesyPHP\\Log', 'log'),
array_merge(array('INFO', $message), $extra_args)
);
}
/**
* Log an warning message
* @param string $message The message to log
* @param array $extra_args Extra arguments to use to compute message using sprintf
* @return true
*/
public static function warning($message, ...$extra_args) {
return call_user_func_array(
array('EesyPHP\\Log', 'log'),
array_merge(array('WARNING', $message), $extra_args)
);
}
/**
* Log an error message
* @param string $message The message to log
* @param array $extra_args Extra arguments to use to compute message using sprintf
* @return true
*/
public static function error($message, ...$extra_args) {
return call_user_func_array(
array('EesyPHP\\Log', 'log'),
array_merge(array('ERROR', $message), $extra_args)
);
}
/**
* Log an fatal message
* @param string $message The message to log
* @param array $extra_args Extra arguments to use to compute message using sprintf
* @return true
*/
public static function fatal($message, ...$extra_args) {
return call_user_func_array(
array('EesyPHP\\Log', 'log'),
array_merge(array('FATAL', $message), $extra_args)
);
}
/**
* Register a contextual fatal error handler
* @param null|callable $handler The fatal error handler (set as null to reset)
* @return void
*/
public static function register_fatal_error_handler($handler) {
// @phpstan-ignore-next-line
if ($handler && !is_callable($handler))
self :: fatal('Invalid fatal error handler provided: it is not callable !');
self :: $fatal_error_handler = ($handler?$handler:null);
}
/**
* Change of current log file
* @param string $file The new log file path
* @return bool
*/
public static function change_filepath($file) {
if ($file == self :: $filepath) return True;
if (self :: $file_fd) {
fclose(self :: $file_fd);
self :: $file_fd = null;
}
self :: $filepath = $file;
return True;
}
/*
*******************************************************************************
* Handle exception logging
*******************************************************************************
*/
/**
* Get the current backtrace
* @param int $ignore_last The number of last levels to ignore
* @return string
*/
public static function get_debug_backtrace_context($ignore_last=0) {
$traces = debug_backtrace();
// Also ignore this function it self
$ignore_last++;
if (!is_array($traces) || count($traces) <= $ignore_last)
return "";
$msg = array();
for ($i=$ignore_last; $i < count($traces); $i++) {
$trace = array("#$i");
if (isset($traces[$i]['file']))
$trace[] = $traces[$i]['file'].(isset($traces[$i]['line'])?":".$traces[$i]['line']:"");
// @phpstan-ignore-next-line
if (isset($traces[$i]['class']) && isset($traces[$i]['function']))
$trace[] = implode(" ", array(
$traces[$i]['class'],
$traces[$i]['type'],
$traces[$i]['function']. "()"));
elseif (isset($traces[$i]['function']))
$trace[] = $traces[$i]['function']. "()";
$msg[] = implode(" - ", $trace);
}
return implode("\n", $msg);
}
/**
* Log an exception
* @param Throwable $exception
* @param string|null $prefix The prefix of the log message
* (optional, default: "An exception occured")
* @param array $extra_args Extra arguments to use to compute prefix using sprintf
* @return void
*/
public static function exception($exception, $prefix=null, ...$extra_args) {
SentryIntegration :: log($exception);
// If extra arguments passed, format prefix message using sprintf
if ($prefix && $extra_args) {
$prefix = call_user_func_array(
'sprintf',
array_merge(array($prefix), $extra_args)
);
}
self :: error(
"%s:\n%s\n## %s:%d : %s",
($prefix?$prefix:"An exception occured"),
self::get_debug_backtrace_context(1),
$exception->getFile(), $exception->getLine(),
$exception->getMessage());
}
/*
*******************************************************************************
* Handle PHP error logging
*******************************************************************************
*/
/**
* Convert PHP error number to the corresponding label
* @param int $errno
* @return string
*/
public static function errno2type($errno) {
$constants = get_defined_constants();
if (is_array($constants))
foreach($constants as $label => $value)
if ($value == $errno && preg_match('/^E_(.*)$/', $label, $m))
return $m[1];
return 'UNKNOWN ERROR #'.$errno;
}
/**
* Log a PHP error
* Note: method design to be used as callable by set_error_handler()
* @param int $errno The error number
* @param string $errstr The error message
* @param string $errfile The filename that the error was raised in
* @param int $errline The line number where the error was raised
* @return false Return false to let the normal error handler continues.
*/
public static function on_php_error($errno, $errstr, $errfile, $errline) {
self :: error(
"A PHP error occured : [%s] %s\nFile : %s (line : %d)",
self :: errno2type($errno), $errstr, $errfile, $errline
);
return False;
}
}
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

114
src/SentryIntegration.php Normal file
View file

@ -0,0 +1,114 @@
<?php
namespace EesyPHP;
use EesyPHP\Log;
use Throwable;
use Exception;
class SentryIntegration {
/**
* Sentry DSN
* @var string|null
*/
protected static $dsn = null;
/**
* Types of PHP error to log in Sentry
* @see https://www.php.net/manual/fr/errorfunc.constants.php
* @var array<int>
*/
protected static array $php_error_types = array(
E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR,
E_RECOVERABLE_ERROR,E_DEPRECATED,
);
/**
* Initialization
* @param string|null $dsn Sentry DSN
* @param float|null $traces_sample_rate Sentry traces sample rate
* (optional, default: 0.2)
* @param array<int>|null $php_error_types Types of PHP error to log in Sentry
* (optional, default: 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,
'traces_sample_rate' => (
$traces_sample_rate?
$traces_sample_rate:0.2
),
]);
\Sentry\configureScope(function (\Sentry\State\Scope $scope): void {
global $auth_user;
$scope->setUser([
'id' => isset($auth_user) && $auth_user?$auth_user['uid']:null,
'email' => isset($auth_user) && $auth_user?$auth_user['mail']:null,
'segment' => isset($auth_user) && $auth_user?$auth_user['type']:null,
'ip_address' => $_SERVER['REMOTE_ADDR'],
]);
});
if (is_array($php_error_types))
self :: $php_error_types = $php_error_types;
set_error_handler(
array('EesyPHP\\SentryIntegration', 'on_php_error'),
E_ALL
);
}
/**
* Log an exception or a message in Sentry
* @param string|Throwable $message
* @param array $extra_args Extra arguments to use to compute message using sprintf
* @return void
*/
public static function log($message, ...$extra_args) {
if (!self :: $dsn) {
Log :: trace('Sentry DSN not configured, do not log this error');
return;
}
if (is_string($message)) {
// Extra arguments passed, format message using sprintf
if ($extra_args) {
$message = call_user_func_array(
'sprintf',
array_merge(array($message), $extra_args)
);
}
Log :: debug('Error logged in Sentry');
\Sentry\captureMessage($message);
}
elseif ($message instanceof Exception) {
Log :: debug('Exception logged in Sentry');
\Sentry\captureException($message);
}
}
/**
* Log a PHP error in Sentry
* Note: method design to be used as callable by set_error_handler()
* @param int $errno The error number
* @param string $errstr The error message
* @param string $errfile The filename that the error was raised in
* @param int $errline The line number where the error was raised
* @return false Return false to let the normal error handler continues.
*/
public static function on_php_error($errno, $errstr, $errfile, $errline) {
if (in_array($errno, self :: $php_error_types))
self :: log(
"A PHP error occured : [%s] %s\nFile : %s (line : %d)",
Log :: errno2type($errno), $errstr, $errfile, $errline
);
return false;
}
}
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

92
src/SentrySpan.php Normal file
View file

@ -0,0 +1,92 @@
<?php
namespace EesyPHP;
/**
* Internal Sentry Span object implementation
* This internal implementation principally permit to keep trace of new span parent
* and list of started spans.
*/
class SentrySpan {
/**
* Keep trace of started Sentry spans
* @var array<int,mixed>
*/
private static $_started_spans = array();
/**
* The unique ID of the Sentry span
* Note: internal ID used as key in self::$_started_spans
* @var int|null
*/
private $id = null;
/**
* The parent of the Sentry span
* @var mixed
*/
private $parent = null;
/**
* The context of the Sentry span
* @var null|\Sentry\Tracing\SpanContext
*/
private $context = null;
/**
* The Sentry span object
* @var mixed
*/
private $span = null;
/**
* Sentry span constructor
* @param string|null $op The operation name
* @param string|null $name The span name
* @return void
*/
public function __construct($op, $name) {
$this -> parent = \Sentry\SentrySdk::getCurrentHub()->getSpan();
// Check if we have a parent span (this is the case if we started a transaction earlier)
if (is_null($this -> parent)) return;
while (is_null($this -> id)) {
$this -> id = rand();
if (isset(self :: $_started_spans[$this -> id]))
$this -> id = null;
}
$this -> context = new \Sentry\Tracing\SpanContext();
$this -> context->setOp($op);
$this -> context->setDescription($name);
$this -> span = $this->parent->startChild($this -> context);
// Set the current span to the span we just started
\Sentry\SentrySdk::getCurrentHub()->setSpan($this -> span);
self :: $_started_spans[$this -> id] = $this;
}
/**
* Finish the span (if started)
* @return void
*/
public function finish() {
if (!$this -> span) return;
$this -> span -> finish();
unset(self::$_started_spans[$this -> id]);
\Sentry\SentrySdk::getCurrentHub()->setSpan($this -> parent);
}
/**
* Finish all started spans
* @see SentryTransaction::__destruct()
* @return void
*/
public static function finishAll() {
foreach (array_reverse(self :: $_started_spans) as $id => $span)
$span -> finish();
}
}
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

58
src/SentryTransaction.php Normal file
View file

@ -0,0 +1,58 @@
<?php
namespace EesyPHP;
/*
* Performance monitoring
*/
class SentryTransaction {
/**
* The Sentry transaction object
* @var \Sentry\Tracing\Transaction
*/
private $transaction;
/**
* The Sentry transaction context object
* @var \Sentry\Tracing\TransactionContext
*/
private $context;
/**
* Constructor: start a Sentry transaction
* @param string|null $op The operation name
* @param string|null $name The transaction name
* @return void
*/
public function __construct($op=null, $name=null) {
// Setup context for the full transaction
$this->context = new \Sentry\Tracing\TransactionContext();
$this->context->setName(
$name?$name:
(php_sapi_name()=='cli'?'CLI execution':'HTTP request')
);
$this->context->setOp(
$op?$op:
(php_sapi_name()=='cli'?'cli.command':'http.request')
);
// Start the transaction
$this->transaction = \Sentry\startTransaction($this->context);
// Set the current transaction as the current span so we can retrieve it later
\Sentry\SentrySdk::getCurrentHub()->setSpan($this->transaction);
}
/**
* Destructor: Stop the current Sentry transaction
* @return void
*/
public function __destruct() {
SentrySpan :: finishAll();
$this->transaction->finish();
}
}
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

517
src/Url.php Normal file
View file

@ -0,0 +1,517 @@
<?php
namespace EesyPHP;
use EesyPHP\Log;
use EesyPHP\SentrySpan;
use EesyPHP\SentryTransaction;
use EesyPHP\UrlRequest;
use function EesyPHP\format_callable;
use function EesyPHP\vardump;
use Exception;
class Url {
/**
* Configured URL patterns :
*
* Example :
*
* array (
* '|get/(?P<name>[a-zA-Z0-9]+)$|' => array (
* 'handler' => 'get',
* 'authenticated' => true,
* 'api_mode' => false,
* 'methods' => array('GET'),
* ),
* '|get/all$|' => => array (
* 'handler' => 'get_all',
* 'authenticated' => true,
* 'api_mode' => false,
* 'methods' => array('GET', 'POST'),
* ),
* )
* @var array
*/
protected static $patterns = array();
/**
* Error 404 handler
* @var callable
*/
protected static $error_404_handler = array('\\EesyPHP\\Url', 'error_404');
/**
* Public root URL
* @var string|null
*/
protected static $public_root_url = null;
/**
* Enable/disable API mode
* @see self :: initialization()
* @var bool
*/
protected static bool $_api_mode;
/**
* Initialization
* @param string|null $public_root_url The application public root URL
* @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) {
// Check URL end
if (substr(self :: $public_root_url, -1) == '/')
$public_root_url = substr($public_root_url, 0, -1);
self :: $public_root_url = $public_root_url?$public_root_url:null;
}
self :: $_api_mode = boolval($api_mode);
}
/**
* Add an URL pattern
*
* @param string|array $pattern The URL pattern or an array of patterns (required)
* @param callable $handler The URL pattern handler (must be callable, required)
* @param boolean $authenticated Permit to define if this URL is accessible only for
* authenticated users (optional, default: true if the
* special force_authentication function is defined,
* false otherwise)
* @param boolean $override Allow override if a command already exists with the
* same name (optional, default: false)
* @param boolean $api_mode Enable API mode (optional, default: false)
* @param array|string|null $methods HTTP method (optional, default: array('GET', 'POST'))
**/
public static function add_url_handler($pattern, $handler=null, $authenticated=null, $override=true,
$api_mode=false, $methods=null) {
$authenticated = (
is_null($authenticated)?
function_exists('force_authentication'):
(bool)$authenticated
);
if (is_null($methods))
$methods = array('GET', 'POST');
elseif (!is_array($methods))
$methods = array($methods);
if (is_array($pattern)) {
if (is_null($handler))
foreach($pattern as $p => $h)
self :: add_url_handler($p, $h, $authenticated, $override, $api_mode, $methods);
else
foreach($pattern as $p)
self :: add_url_handler($p, $handler, $authenticated, $override, $api_mode, $methods);
}
else {
if (!isset(self :: $patterns[$pattern])) {
self :: $patterns[$pattern] = array(
'handler' => $handler,
'authenticated' => $authenticated,
'api_mode' => $api_mode,
'methods' => $methods,
);
}
elseif ($override) {
Log :: debug(
"URL : override pattern '%s' with handler '%s' (old handler = '%s')".
$pattern, format_callable($handler), vardump(self :: $patterns[$pattern])
);
self :: $patterns[$pattern] = array(
'handler' => $handler,
'authenticated' => $authenticated,
'api_mode' => $api_mode,
'methods' => $methods,
);
}
else {
Log :: debug("URL : pattern '$pattern' already defined : do not override.");
}
}
}
/**
* Show error page
*
* @param $request UrlRequest|null The request (optional, default: null)
* @param $error_code int|null The HTTP error code (optional, default: 400)
*
* @return void
**/
public static function error_page($request=null, $error_code=null) {
global $smarty;
$http_errors = array(
400 => array(
'pagetitle' => _("Bad request"),
'message' => _("Invalid request."),
),
401 => array(
'pagetitle' => _("Authentication required"),
'message' => _("You have to be authenticated to access to this page."),
),
403 => array(
'pagetitle' => _("Access denied"),
'message' => _("You do not have access to this application. If you think this is an error, please contact support."),
),
404 => array(
'pagetitle' => _("Whoops ! Page not found"),
'message' => _("The requested page can not be found."),
),
);
$error_code = ($error_code?intval($error_code):400);
if (array_key_exists($error_code, $http_errors))
$error = $http_errors[intval($error_code)];
else
$error = array(
'pagetitle' => _('Error'),
'message' => _('An unknown error occurred. If problem persist, please contact support.'),
);
http_response_code($error_code);
if (isset($smarty) && $smarty)
$smarty -> assign('message', $error['message']);
display_template('error_page.tpl', $error['pagetitle']);
exit();
}
/**
* Error 404 handler
*
* @param UrlRequest|null $request The request (optional, default: null)
*
* @return void
**/
public static function error_404($request=null) {
self :: error_page($request, 404);
}
/**
* Set unknown URL handler
* @param callable|null $handler
* @return bool
*/
public static function set_unknown_url_handler($handler=null) {
// @phpstan-ignore-next-line
if (is_callable($handler) || is_null($handler)) {
self :: $error_404_handler = $handler;
return true;
}
// @phpstan-ignore-next-line
Log :: warning(
'Url::set_unknown_url_handler(): Invalid URL handler provided: %s',
vardump($handler));
return false;
}
/**
* Interprets the requested URL and return the corresponding UrlRequest object
*
* @param $default_url string|null The default URL if current one does not
* match with any configured pattern.
*
* @return UrlRequest The UrlRequest object corresponding to the the requested URL.
*/
protected static function get_request($default_url=null) {
$current_url = self :: get_current_url();
if ($current_url === false) {
Log :: fatal(
_('Unable to determine the requested page. '.
'If the problem persists, please contact support.')
);
exit();
}
if (!is_array(self :: $patterns)) {
Log :: fatal('URL : No URL patterns configured !');
exit();
}
Log :: debug("URL : current url = '$current_url'");
Log :: trace(
"URL : check current url with the following URL patterns :\n - ".
implode("\n - ", array_keys(self :: $patterns))
);
foreach (self :: $patterns as $pattern => $handler_infos) {
$m = self :: url_match($pattern, $current_url, $handler_infos['methods']);
if (is_array($m)) {
$request = new UrlRequest($current_url, $handler_infos, $m);
// Reset last redirect
if (isset($_SESSION['last_redirect']))
unset($_SESSION['last_redirect']);
Log :: trace("URL : result :\n".vardump($request));
return $request;
}
}
if ($default_url !== false) {
Log :: debug("Current url match with no pattern. Redirect to default url ('$default_url')");
self :: redirect($default_url);
exit();
}
// Error 404
$api_mode = self :: $_api_mode || (strpos($current_url, 'api/') === 0);
Log :: debug(
"Current URL match with no pattern. Use error 404 handler (API mode=$api_mode).");
return new UrlRequest(
$current_url,
array(
'handler' => self :: $error_404_handler,
'authenticated' => false,
'api_mode' => $api_mode,
)
);
}
/**
* Check if the current requested URL match with a specific pattern
*
* @param string $pattern The URL pattern
* @param string|false $current_url The current URL (optional)
* @param array|null $methods HTTP method (optional, default: no check)
*
* @return array|false The URL info if pattern matched, false otherwise.
**/
public static function url_match($pattern, $current_url=false, $methods=null) {
if ($methods && !in_array($_SERVER['REQUEST_METHOD'], $methods))
return false;
if ($current_url === false) {
$current_url = self :: get_current_url();
if (!$current_url) return False;
}
if (preg_match($pattern, $current_url, $m)) {
Log :: debug(
"URL : Match found with pattern '$pattern' :\n\t".
str_replace("\n", "\n\t", print_r($m, true)));
return $m;
}
return False;
}
/**
* Retreive current requested URL and return it
*
* @return string|false The current request URL or false if fail
**/
public static function get_current_url() {
Log :: trace("URL : request URI = '".$_SERVER['REQUEST_URI']."'");
$base = self :: get_rewrite_base();
Log :: trace("URL : rewrite base = '$base'");
if ($_SERVER['REQUEST_URI'] == $base)
return '';
if (substr($_SERVER['REQUEST_URI'], 0, strlen($base)) != $base) {
Log :: error(
"URL : request URI (".$_SERVER['REQUEST_URI'].") does not start with rewrite base ($base)");
return False;
}
$current_url = substr($_SERVER['REQUEST_URI'], strlen($base));
// URL contain params ?
$params_start = strpos($current_url, '?');
if ($params_start !== false)
// Params detected, remove it
// No url / currrent url start by '?' ?
if ($params_start == 0)
return '';
else
return substr($current_url, 0, $params_start);
return $current_url;
}
/**
* Try to detect rewrite base from public root URL
*
* @return string The detected rewrite base
**/
public static function get_rewrite_base() {
if (!self :: $public_root_url) return '/';
if (preg_match('|^https?://[^/]+/(.*)$|', self :: $public_root_url, $m))
return '/'.self :: remove_trailing_slash($m[1]).'/';
if (preg_match('|^/(.*)$|', self :: $public_root_url, $m))
return '/'.self :: remove_trailing_slash($m[1]).'/';
return '/';
}
/**
* Trigger redirect to specified URL (or homepage if omited)
*
* @param string|false $go The destination URL
*
* @return void
**/
public static function redirect($go=false) {
if ($go===false)
$go = "";
// If more than one argument passed, format URL using sprintf & urlencode parameters
elseif (func_num_args() > 1)
$go = call_user_func_array(
'sprintf',
array_merge(
array($go),
array_map('urlencode', array_slice(func_get_args(), 1))
)
);
if (self :: is_absolute_url($go))
$url = $go;
elseif (self :: $public_root_url)
$url = self :: $public_root_url."/$go";
else
$url = "/$go";
// Prevent loop
if (isset($_SESSION['last_redirect']) && $_SESSION['last_redirect'] == $url)
Log :: fatal(
_('Unable to determine the requested page (loop detected). '.
'If the problem persists, please contact support.'));
else
$_SESSION['last_redirect'] = $url;
Log :: debug("redirect($go) => Redirect to : <$url>");
header("Location: $url");
exit();
}
/**
* Handle the current requested URL
*
* Note: if the route required that user is authenticated, this method will
* invoke the force_authentication() special function (or trigger a fatal error
* if it's not defined).
*
* @param string|null $default_url The default URL if current one does not
* match with any configured pattern.
*
* @return void
**/
public static function handle_request($default_url=null) {
global $smarty;
$sentry_span = new SentrySpan('http.handle_request', 'Handle the HTTP request');
$request = self :: get_request($default_url);
if (!is_callable($request -> handler)) {
Log :: error(
"URL handler function %s does not exists !",
format_callable($request -> handler));
Log :: fatal(_("This request cannot be processed."));
}
if ($request -> api_mode)
self :: $_api_mode = true;
if (isset($smarty) && $smarty)
$smarty -> assign('request', $request);
// Check authentication (if need)
if($request -> authenticated)
if (function_exists('force_authentication'))
force_authentication();
else
Log :: fatal(_("Authentication required but force_authentication function is not defined."));
try {
call_user_func($request -> handler, $request);
}
catch (Exception $e) {
Log :: exception(
$e, "An exception occured running URL handler function %s()",
format_callable($request -> handler));
Log :: fatal(_("This request could not be processed correctly."));
}
$sentry_span->finish();
}
/**
* Remove trailing slash in specified URL
*
* @param string $url The URL
*
* @return string The specified URL without trailing slash
**/
public static function remove_trailing_slash($url) {
if ($url == '/')
return $url;
elseif (substr($url, -1) == '/')
return substr($url, 0, -1);
return $url;
}
/**
* Get the public absolute URL
*
* @param string|null $relative_url Relative URL to convert (Default: current URL)
*
* @return string The public absolute URL
**/
public static function get_absolute_url($relative_url=null) {
if (!is_string($relative_url))
$relative_url = self :: get_current_url();
if (self :: $public_root_url[0] == '/') {
Log :: debug(
"URL :: get_absolute_url($relative_url): configured public root URL is relative ".
"(%s) => try to detect it from current request infos.",
self :: $public_root_url);
self :: $public_root_url = (
'http'.(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'?'s':'').'://'.
$_SERVER['HTTP_HOST'].self :: $public_root_url);
Log :: debug(
"URL :: get_absolute_url(%s): detected public_root_url: %s",
$relative_url, self :: $public_root_url);
}
if (substr($relative_url, 0, 1) == '/')
$relative_url = substr($relative_url, 1);
$url = self :: remove_trailing_slash(self :: $public_root_url)."/$relative_url";
Log :: debug("URL :: get_absolute_url($relative_url): result = $url");
return $url;
}
/**
* Check if specified URL is absolute
*
* @param string $url The URL to check
*
* @return boolean True if specified URL is absolute, False otherwise
**/
public static function is_absolute_url($url) {
return boolval(preg_match('#^https?://#', $url));
}
/**
* Add parameter in specified URL
*
* @param string &$url The reference of the URL
* @param string $param The parameter name
* @param string $value The parameter value
* @param boolean $encode Set if parameter value must be URL encoded (optional, default: true)
*
* @return string The completed URL
*/
public static function add_url_parameter(&$url, $param, $value, $encode=true) {
if (strpos($url, '?') === false)
$url .= '?';
else
$url .= '&';
$url .= "$param=".($encode?urlencode($value):$value);
return $url;
}
/**
* Get/set API mode
* @param bool|null $value If boolean, the current API mode will be changed
* @return bool Current API mode
*/
public static function api_mode($value=null) {
if (is_bool($value)) self :: $_api_mode = $value;
return self :: $_api_mode;
}
}
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

143
src/UrlRequest.php Normal file
View file

@ -0,0 +1,143 @@
<?php
namespace EesyPHP;
use EesyPHP\Log;
/**
* URL request abstraction
*
* @author Benjamin Renard <brenard@easter-eggs.com>
* @property-read string|null $current_url;
* @property-read array|null $handler;
* @property-read bool $authenticated;
* @property-read bool $api_mode;
*/
class UrlRequest {
// The URL requested handler
private $current_url = null;
// The URL requested handler
private $handler = null;
// Request need authentication ?
private $authenticated = true;
// API mode enabled ?
private $api_mode = false;
// Parameters detected on requested URL
private $url_params = array();
public function __construct($current_url, $handler_infos, $url_params=array()) {
$this -> current_url = $current_url;
$this -> handler = $handler_infos['handler'];
$this -> authenticated = (
isset($handler_infos['authenticated'])?
boolval($handler_infos['authenticated']):true);
$this -> api_mode = (
isset($handler_infos['api_mode'])?
boolval($handler_infos['api_mode']):false);
$this -> url_params = $url_params;
}
/**
* Get request info
*
* @param string $key The name of the info
*
* @return mixed The value
**/
public function __get($key) {
if ($key == 'current_url')
return $this -> current_url;
if ($key == 'handler')
return $this -> handler;
if ($key == 'authenticated')
return $this -> authenticated;
if ($key == 'api_mode')
return $this -> api_mode;
if ($key == 'referer')
return $this -> get_referer();
if ($key == 'http_method')
return $_SERVER['REQUEST_METHOD'];
if (array_key_exists($key, $this->url_params)) {
return urldecode($this->url_params[$key]);
}
// Unknown key, log warning
Log :: warning(
"__get($key): invalid property requested\n%s",
Log :: get_debug_backtrace_context());
}
/**
* Set request info
*
* @param string $key The name of the info
* @param mixed $value The value of the info
*
* @return void
**/
public function __set($key, $value) {
if ($key == 'referer')
$_SERVER['HTTP_REFERER'] = $value;
elseif ($key == 'http_method')
$_SERVER['REQUEST_METHOD'] = $value;
else
$this->url_params[$key] = $value;
}
/**
* Check is request info is set
*
* @param string $key The name of the info
*
* @return bool True is info is set, False otherwise
**/
public function __isset($key) {
if (
in_array(
$key, array('current_url', 'handler', 'authenticated',
'api_mode', 'referer', 'http_method')
)
)
return True;
return array_key_exists($key, $this->url_params);
}
/**
* Get request parameter
*
* @param string $parameter The name of the parameter
* @param bool $decode If true, the parameter value will be urldecoded
* (optional, default: true)
*
* @return mixed The value or false if parameter does not exists
**/
public function get_param($parameter, $decode=true) {
if (array_key_exists($parameter, $this->url_params)) {
if ($decode)
return urldecode($this->url_params[$parameter]);
return $this->url_params[$parameter];
}
return false;
}
/**
* Get request referer (if known)
*
* @return string|null The request referer URL if known, null otherwise
*/
public function get_referer() {
if (isset($_SERVER['HTTP_REFERER']) && $_SERVER['HTTP_REFERER'])
return $_SERVER['HTTP_REFERER'];
return null;
}
}
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

216
src/functions.php Normal file
View file

@ -0,0 +1,216 @@
<?php
namespace EesyPHP;
use EesyPHP\Log;
/*
* Parser/formater values helpers
*/
$_date_format = "%d/%m/%Y";
$_date_time_format = "%d/%m/%Y %H:%M:%S";
function format_time($time, $with_time=true) {
global $_date_format, $_date_time_format;
if ($with_time)
return strftime($_date_time_format, $time);
return strftime($_date_format, $time);
}
function parse_date($date, $with_time=true) {
global $_date_format, $_date_time_format;
if ($with_time)
$ptime = strptime($date, $_date_time_format);
else
$ptime = strptime($date, $_date_format);
if(is_array($ptime)) {
return mktime(
$ptime['tm_hour'],
$ptime['tm_min'],
$ptime['tm_sec'],
$ptime['tm_mon']+1,
$ptime['tm_mday'],
$ptime['tm_year']+1900
);
}
return false;
}
function format_size($size, $digit=False) {
if (!$digit && $digit!==0) $digit=2;
if ($size>=1099511627776)
return number_format($size/1099511627776,$digit)."To";
elseif ($size>=1073741824)
return number_format($size/1073741824,$digit)."Go";
else if ($size>=1048576)
return number_format($size/1048576,$digit)."Mo";
else if ($size>=1024)
return number_format($size/1024,$digit)."Ko";
else
return $size."o";
}
/*
* Generic Data/value helpers
*/
function vardump($data) {
ob_start();
var_dump($data);
$data = ob_get_contents();
ob_end_clean();
return $data;
}
/**
* Format a callable object for logging
* @param string|array $callable The callable object
* @return string The callable object string representation
*/
function format_callable($callable) {
if (is_string($callable))
return $callable."()";
if (is_array($callable))
if (is_string($callable[0]))
return $callable[0]."::".$callable[1]."()";
elseif (is_object($callable[0]))
return get_class($callable[0])."->".$callable[1]."()";
else
return "Unkown->".$callable[1]."()";
// @phpstan-ignore-next-line
return vardump($callable);
}
function check_is_empty($val) {
switch(gettype($val)) {
case "boolean":
case "integer":
case "double":
case "object":
case "resource":
return False;
case "array":
case "string":
if ($val == "0") return false;
return empty($val);
case "NULL":
return True;
}
}
/*
* Generic file/directory helpers
*/
function dump_file($file_path, $max_age=3600) {
if (is_file($file_path)) {
header('Content-Type: '.mime_content_type($file_path));
$last_modified_time = filemtime($file_path);
$etag = md5_file($file_path);
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("Etag: $etag");
if (
(
isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
@strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified_time
) || (
isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag
)
) {
header("HTTP/1.1 304 Not Modified");
exit();
}
header('Pragma: public');
header('Content-Length: ' . filesize($file_path));
readfile($file_path);
exit();
}
header("HTTP/1.1 404 Not found");
exit();
}
function delete_directory($dir, $recursive=true) {
$files = array_diff(scandir($dir), array('.','..'));
if ($recursive) {
foreach ($files as $file) {
if (is_dir("$dir/$file")) {
if (!delete_directory("$dir/$file", true)) {
Log :: error("delete_directory($dir) : Fail to delete sub-directory '$dir/$file'.");
return false;
}
}
else if (!unlink("$dir/$file")) {
Log :: error("delete_directory($dir) : Fail to delete '$dir/$file'.");
return false;
}
}
}
else if (!empty($files)) {
Log :: error("delete_directory($dir) : Directory is not empty.");
return false;
}
return rmdir($dir);
}
/*
* Run external command helper
*/
/**
* Run external command
*
* @param $command string|array The command. It's could be an array of the command with its
* arguments.
* @param $data_stdin string|null The command arguments (optional, default: null)
* @param $escape_command_args boolean If true, the command will be escaped
* (optional, default: true)
*
* @return false|array An array of return code, stdout and stderr result or False in case of fatal
* error
**/
function run_external_command($command, $data_stdin=null, $escape_command_args=true) {
if (is_array($command))
$command = implode(' ', $command);
if ($escape_command_args)
$command = escapeshellcmd($command);
Log :: debug("Run external command: '$command'");
$descriptorspec = array(
0 => array("pipe", "r"), // stdin
1 => array("pipe", "w"), // stdout
2 => array("pipe", "w"), // stderr
);
$process = proc_open($command, $descriptorspec, $pipes);
if (!is_resource($process)) {
Log :: error("Fail to run external command: '$command'");
return false;
}
if (!is_null($data_stdin)) {
fwrite($pipes[0], $data_stdin);
}
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[2]);
$return_value = proc_close($process);
$error = (!empty($stderr) || $return_value != 0);
Log :: log(
($error?'ERROR':'DEBUG'),
"External command ".($error?"error":"result").":\n".
"\tCommand : $command\n".
"\tReturn code: $return_value\n".
"\tOutput:\n".
"\t\t- Stdout :\n$stdout\n\n".
"\t\t- Stderr :\n$stderr"
);
return array($return_value, $stdout, $stderr);
}
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab