Some improvments from recent works on apps based on its "framework"

* Code cleaning and fix some small errors using Phpstan
* Configure pre-commit to run Phpstan before each commit
* Some little improvments and logging, mail, smarty & URL libs
* Add Sentry integration
* Add Webstat JS code inclusion
* Install Smarty dependency using composer

Breaking changes:
* Rename Event class as HookEvent to avoid conflict with PECL event
* URL with refresh GET parameter now automatically trigger redirection without it
 after page loading to avoid to keep it in URL
This commit is contained in:
Benjamin Renard 2023-01-29 11:51:41 +01:00
parent 01759fb4c2
commit 6fdc5447f1
24 changed files with 2363 additions and 510 deletions

9
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,9 @@
# Pre-commit hooks to run tests and ensure code is cleaned.
# See https://pre-commit.com for more information
repos:
- repo: https://github.com/digitalpulp/pre-commit-php.git
rev: 1.4.0
hooks:
- id: php-stan
files: \.(php)$
args: ['--configuration=phpstan.neon']

View file

@ -1,13 +1,23 @@
{ {
"name": "ee/eesyphp",
"description": "Easter-eggs easyphp PHP framework for simple web apps",
"authors": [
{
"name": "Easter-eggs",
"email": "info@easter-eggs.com"
}
],
"require": { "require": {
"envms/fluentpdo": "^1.1", "envms/fluentpdo": "^1.1",
"pear/console_table": "^1.3", "pear/console_table": "^1.3",
"brenard/php-unidecode": "dev-master", "brenard/php-unidecode": "dev-master",
"smarty/smarty": "3.1.34",
"smarty-gettext/smarty-gettext": "^1.6", "smarty-gettext/smarty-gettext": "^1.6",
"smarty-gettext/tsmarty2c": "^0.2.1", "smarty-gettext/tsmarty2c": "^0.2.1",
"sepia/po-parser": "^6.0" "sepia/po-parser": "^6.0",
"sentry/sdk": "^3.3"
}, },
"require-dev": { "require-dev": {
"overtrue/phplint": "^3.0" "phpstan/phpstan": "^1.9"
} }
} }

1833
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -49,3 +49,11 @@ Some other variables exist to manage the email sending by the application :
- `$mail_headers` : an PHP array of default headers add to mail sent by the application - `$mail_headers` : an PHP array of default headers add to mail sent by the application
- `$mail_sender` : the email address of the sender of all emails sent by the application - `$mail_sender` : the email address of the sender of all emails sent by the application
- `$mail_catch_all` : for debugging purpose, you can using this variable to specify an email address that will received all email sent by the application in place of the original recipient - `$mail_catch_all` : for debugging purpose, you can using this variable to specify an email address that will received all email sent by the application in place of the original recipient
## Web stats JS code inclusion
If you use a Web stats tool that need to include a piece of JS code in all page like [Matomo](https://matomo.org/), you could defined the `$webstats_js_code` variable.
## Sentry integration
If you want to enable the [Sentry](https://sentry.io) integration, you have to define the `$sentry_dsn`. This integration permit to report PHP errors (see `$sentry_php_error_types`) and exception as issues and to monitor performance of the application (see `$sentry_traces_sample_rate`).

View file

@ -5,7 +5,7 @@
Some Debian packages have to be installed : Some Debian packages have to be installed :
```bash ```bash
apt install git composer php-cli php-mail php-mail-mine php-net-smtp php-auth-sasl php-json php-mbstring php-intl smarty3 apt install git composer php-cli php-mail php-mail-mine php-net-smtp php-auth-sasl php-json php-mbstring php-intl
# for PostgreSQL DB backend # for PostgreSQL DB backend
apt install php-pgsql apt install php-pgsql
# for MySQL/MariaDB DB backend # for MySQL/MariaDB DB backend

View file

@ -5,7 +5,7 @@ function add_cli_command($command, $handler, $short_desc, $usage_args=false, $lo
$override=false) { $override=false) {
global $cli_commands; global $cli_commands;
if (array_key_exists($command, $cli_commands) && !$override) { if (array_key_exists($command, $cli_commands) && !$override) {
logging('ERROR', _("The CLI command '%s' already exists.", $command)); logging('ERROR', _("The CLI command '%s' already exists."), $command);
return False; return False;
} }
@ -331,10 +331,10 @@ function cli_restore($command_args) {
$fd = fopen((count($command_args) >= 1?$command_args[0]:'php://stdin'), 'r'); $fd = fopen((count($command_args) >= 1?$command_args[0]:'php://stdin'), 'r');
restore_items($fd); restore_items($fd);
fclose($fd); fclose($fd);
logging('INFO', sprint( logging(
"Items restored from '%s'", 'INFO', "Items restored from '%s'",
(count($command_args) >= 1?$command_args[0]:'STDIN') (count($command_args) >= 1?$command_args[0]:'STDIN')
)); );
} }
add_cli_command( add_cli_command(
'restore', 'restore',
@ -389,7 +389,6 @@ function cli_cron($command_args) {
logging('INFO', 'Item #%s (%s) deleted (creation date: %s)', logging('INFO', 'Item #%s (%s) deleted (creation date: %s)',
$item['id'], $item['name'], format_time($item['date']) $item['id'], $item['name'], format_time($item['date'])
); );
remove_item_attachments($item['id']);
} }
else { else {
logging('ERROR', 'Fail to delete item "%s" (%s, creation date: %s)', logging('ERROR', 'Fail to delete item "%s" (%s, creation date: %s)',
@ -416,3 +415,5 @@ add_cli_command(
___("-m/--max-age Item expiration limit (in days, optional)"), ___("-m/--max-age Item expiration limit (in days, optional)"),
) )
); );
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -35,8 +35,39 @@ $log_level = 'INFO';
// Debug Ajax request/response // Debug Ajax request/response
$debug_ajax = false; $debug_ajax = false;
// Smarty class path // Log PHP errors levels (as specified to set_error_handler())
$smarty_path = 'smarty3/Smarty.class.php'; // Default:
// - In TRACE or DEBUG: E_ALL & ~E_STRICT
// - Otherwise: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
// $log_php_errors_levels = E_ALL & ~E_STRICT;
/*
* Sentry configuration
*/
// Sentry DSN
$sentry_dsn = null;
// Log PHP errors in Sentry: list of errors types to logs
// Note: must also match with $log_php_errors_levels.
// See: https://www.php.net/manual/fr/errorfunc.constants.php
$sentry_php_error_types = array(
E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR,
E_RECOVERABLE_ERROR,E_DEPRECATED,
);
// Traces sample rate (between 0 and 1)
// Note: this parameter permit to determine how many transactions (=~ access) are traced and
// sent to Sentry, for instance, 0.2 meen that 20% of the transactions will be traced. In dev
// mode, set to 1 if you want all transactions are sent to Sentry.
// Default: 0.2
$sentry_traces_sample_rate = 0.2;
/*
* Smarty template configuration
*/
// Smarty directories
$smarty_templates_dir = "$root_dir_path/templates"; $smarty_templates_dir = "$root_dir_path/templates";
$smarty_templates_c_dir = "$tmp_root_dir/templates_c"; $smarty_templates_c_dir = "$tmp_root_dir/templates_c";
@ -116,7 +147,9 @@ $mail_sender = "noreply@example.org";
// Catch all e-mails sent to a configured e-mail address // Catch all e-mails sent to a configured e-mail address
$mail_catch_all = false; $mail_catch_all = false;
// Load local configuration file is present /**
if (is_file("$root_dir_path/includes/config.local.php")) { * Web Stats JS code
require "$root_dir_path/includes/config.local.php"; */
} $webstats_js_code = '';
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -3,7 +3,8 @@
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED); error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED);
// Root directory path // Root directory path
if (__FILE__ != "") { $script = null;
if (defined('__FILE__') && constant('__FILE__')) {
$script = __FILE__; $script = __FILE__;
} }
else { else {
@ -12,7 +13,8 @@ else {
if (basename($script) == 'core.php') if (basename($script) == 'core.php')
break; break;
} }
$root_dir_path=realpath(dirname($script).'/../'); if (!$script) die('Fail to detect root directory path');
$root_dir_path = realpath(dirname($script).'/../');
// Include App's includes and vendor directories to PHP include paths // Include App's includes and vendor directories to PHP include paths
set_include_path($root_dir_path.'/includes' . PATH_SEPARATOR . get_include_path()); set_include_path($root_dir_path.'/includes' . PATH_SEPARATOR . get_include_path());
@ -27,13 +29,19 @@ $api_mode = false;
require_once('translation.php'); require_once('translation.php');
require_once('config.inc.php'); require_once('config.inc.php');
// Check $public_root_url end // Load local configuration file is present
if (substr($public_root_url, -1)=='/') { if (is_file("$root_dir_path/includes/config.local.php")) {
$public_root_url=substr($public_root_url, 0, -1); require "$root_dir_path/includes/config.local.php";
} }
require_once 'sentry.php';
$sentry_transaction = new SentryTransaction();
$sentry_span = new SentrySpan('core.init', 'Core initialization');
// Define upload_tmp_dir // Define upload_tmp_dir
ini_set('upload_tmp_dir',$upload_tmp_dir); if (isset($upload_tmp_dir))
ini_set('upload_tmp_dir', $upload_tmp_dir);
require_once('logging.php'); require_once('logging.php');
require_once('functions.php'); require_once('functions.php');
@ -60,3 +68,6 @@ require_once('mail.php');
init_translation(); init_translation();
foreach($status_list as $key => $value) foreach($status_list as $key => $value)
$status_list[$key] = _($value); $status_list[$key] = _($value);
$sentry_span->finish();
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -2,8 +2,18 @@
use Unidecode\Unidecode; use Unidecode\Unidecode;
if (!isset($db_dsn)) {
logging('FATAL', 'Database DSN not configured');
exit(1);
}
try { try {
$pdo = new PDO($db_dsn, $db_user, $db_pwd, $db_options); $pdo = new PDO(
$db_dsn,
isset($db_user)?$db_user:null,
isset($db_pwd)?$db_pwd:null,
isset($db_options)?$db_options:null
);
$pdo -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$fpdo = new FluentPDO($pdo); $fpdo = new FluentPDO($pdo);
@ -266,18 +276,18 @@ function search_items($params) {
$orderby="$order $order_direction"; $orderby="$order $order_direction";
$limit = ""; $limit = "";
$page = 1;
$nb_by_page = 10;
$offset = 0; $offset = 0;
if (!isset($params['all'])) { if (!isset($params['all'])) {
$page=1;
$nb_by_page=10;
if (isset($params['page']) && $params['page']>0) { if (isset($params['page']) && $params['page']>0) {
if (isset($params['nb_by_page']) && $params['nb_by_page']>0) { if (isset($params['nb_by_page']) && $params['nb_by_page']>0) {
$nb_by_page=intval($params['nb_by_page']); $nb_by_page = intval($params['nb_by_page']);
} }
$page=intval($params['page']); $page = intval($params['page']);
} }
$offset=($page-1)*$nb_by_page; $offset = ($page-1)*$nb_by_page;
$limit=$nb_by_page; $limit = $nb_by_page;
} }
try { try {
@ -297,60 +307,57 @@ function search_items($params) {
-> offset($offset) -> offset($offset)
-> execute(); -> execute();
if ($result !== false) { if ($result === false) {
$rows = $result -> fetchAll();
$items = array();
foreach ($rows as $row) {
$items[] = _format_row_info($row, array('date'));
}
if (is_array($items)) {
if (empty($limit)) {
return array(
'count' => count($items),
'first' => 1,
'last' => count($items),
'nb_pages' => 1,
'page' => 1,
'items' => $items
);
}
else {
$query_count = $fpdo -> from('item')
-> select(null)
-> select('count(*) as count');
if (!empty($where))
$query_count -> where($where);
foreach ($patterns_where as $patterns_word)
call_user_func_array(
array($query_count, 'where'),
array_merge(
array('('.implode(' OR ', array_keys($patterns_word)).')'),
array_values($patterns_word)
)
);
$result_count = $query_count -> execute();
if ($result_count === false) {
logging('DEBUG', 'search_items() : search for count in DB return false');
return False;
}
$count = $result_count -> fetch();
return array(
'count' => $count['count'],
'first' => $offset+1,
'last' => (
$offset+$nb_by_page<$count['count']?
$offset+$nb_by_page:$count['count']),
'nb_pages' => ceil($count['count']/$nb_by_page),
'page' => $page,
'items' => $items,
);
}
}
}
else
logging('ERROR', 'search_items() : search in DB return false'); logging('ERROR', 'search_items() : search in DB return false');
return false;
}
$rows = $result -> fetchAll();
$items = array();
foreach ($rows as $row) {
$items[] = _format_row_info($row, array('date'));
}
if (isset($params['all'])) {
return array(
'count' => count($items),
'first' => 1,
'last' => count($items),
'nb_pages' => 1,
'page' => 1,
'items' => $items
);
}
$query_count = $fpdo -> from('item')
-> select(null)
-> select('count(*) as count');
if (!empty($where))
$query_count -> where($where);
foreach ($patterns_where as $patterns_word)
call_user_func_array(
array($query_count, 'where'),
array_merge(
array('('.implode(' OR ', array_keys($patterns_word)).')'),
array_values($patterns_word)
)
);
$result_count = $query_count -> execute();
if ($result_count === false) {
logging('DEBUG', 'search_items() : search for count in DB return false');
return False;
}
$count = $result_count -> fetch();
return array(
'count' => $count['count'],
'first' => $offset+1,
'last' => (
$offset+$nb_by_page<$count['count']?
$offset+$nb_by_page:$count['count']),
'nb_pages' => ceil($count['count']/$nb_by_page),
'page' => $page,
'items' => $items,
);
} }
catch (Exception $e) { catch (Exception $e) {
log_exception( log_exception(
@ -461,3 +468,5 @@ function restore_items($fd=null) {
return !$error; return !$error;
} }
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -232,6 +232,25 @@ function vardump($data) {
return $data; 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) { function check_is_empty($val) {
switch(gettype($val)) { switch(gettype($val)) {
case "boolean": case "boolean":
@ -322,7 +341,7 @@ function delete_directory($dir, $recursive=true) {
* error * error
**/ **/
function run_external_command($command, $data_stdin=null, $escape_command_args=true) { function run_external_command($command, $data_stdin=null, $escape_command_args=true) {
if (array($command)) if (is_array($command))
$command = implode(' ', $command); $command = implode(' ', $command);
if ($escape_command_args) if ($escape_command_args)
$command = escapeshellcmd($command); $command = escapeshellcmd($command);
@ -365,3 +384,5 @@ function run_external_command($command, $data_stdin=null, $escape_command_args=t
return array($return_value, $stdout, $stderr); return array($return_value, $stdout, $stderr);
} }
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -25,7 +25,7 @@ function register_hook($event, $callable, $param=NULL) {
/** /**
* Run triggered actions on specific event * Run triggered actions on specific event
* *
* @param $event string Event name * @param $event string HookEvent name
* *
* @return boolean True if all triggered actions succefully runned, false otherwise * @return boolean True if all triggered actions succefully runned, false otherwise
*/ */
@ -35,16 +35,16 @@ function trigger_hook($event_name, $event_data=null) {
if (isset($hooks[$event_name]) && is_array($hooks[$event_name])) { if (isset($hooks[$event_name]) && is_array($hooks[$event_name])) {
if ($event_name == 'all') if ($event_name == 'all')
$event = new Event($event_data['event_name'], $event_data['event_data']); $event = new HookEvent($event_data['event_name'], $event_data['event_data']);
else else
$event = new Event($event_name, $event_data); $event = new HookEvent($event_name, $event_data);
foreach ($hooks[$event_name] as $e) { foreach ($hooks[$event_name] as $e) {
if (is_callable($e['callable'])) { if (is_callable($e['callable'])) {
try { try {
call_user_func_array($e['callable'],array($event, &$e['param'])); call_user_func_array($e['callable'],array($event, &$e['param']));
} }
catch(Exception $e) { catch(Exception $e) {
logException( log_exception(
$e, "An exception occured running hook ".format_callable($e['callable']). $e, "An exception occured running hook ".format_callable($e['callable']).
" on event $event_name"); " on event $event_name");
$return = false; $return = false;
@ -75,7 +75,7 @@ function trigger_hook($event_name, $event_data=null) {
return $return; return $return;
} }
class Event implements JsonSerializable { class HookEvent implements JsonSerializable {
private $name; private $name;
private $data; private $data;
@ -101,3 +101,5 @@ class Event implements JsonSerializable {
); );
} }
} }
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -7,14 +7,23 @@
* *
* // Log level (DEBUG / INFO / WARNING / ERROR / FATAL) * // Log level (DEBUG / INFO / WARNING / ERROR / FATAL)
* $log_level='INFO'; * $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 descriptor (Do not change !!!)
$_log_file_fd=null; $_log_file_fd = null;
// Log Levels // Log Levels
$_log_levels=array( $_log_levels = array(
'TRACE' => 0, 'TRACE' => 0,
'DEBUG' => 1, 'DEBUG' => 1,
'INFO' => 2, 'INFO' => 2,
@ -23,13 +32,22 @@ $_log_levels=array(
'FATAL' => 5, '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) { function logging($level, $message) {
global $log_file, $_log_file_fd, $_log_levels, $log_level, $argv, $auth_user; 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'; if (!array_key_exists($level, $_log_levels)) $level = 'INFO';
$level_id = $_log_levels[$level]; $level_id = $_log_levels[$level];
if (!array_key_exists($log_level, $_log_levels)) $log_level = 'INFO';
$log_level_id = $_log_levels[$log_level]; $log_level_id = $_log_levels[$log_level];
if ($level_id < $log_level_id) return true; if ($level_id < $log_level_id) return true;
@ -66,10 +84,12 @@ function logging($level, $message) {
$msg = implode(' - ', $msg)."\n"; $msg = implode(' - ', $msg)."\n";
} }
fwrite($_log_file_fd , $msg); fwrite($_log_file_fd, $msg);
if ($level == 'FATAL') if ($level == 'FATAL')
if (function_exists('fatal_error')) if (!is_null($_fatal_error_handler))
call_user_func($_fatal_error_handler, $message);
elseif (function_exists('fatal_error'))
fatal_error($message); fatal_error($message);
else else
die("\n$message\n\n"); die("\n$message\n\n");
@ -79,6 +99,38 @@ function logging($level, $message) {
return true; 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) { function change_log_file($file) {
global $log_file, $_log_file_fd; global $log_file, $_log_file_fd;
if ($file == $log_file) return True; if ($file == $log_file) return True;
@ -90,7 +142,17 @@ function change_log_file($file) {
return True; return True;
} }
// Handle exception logging /*
*******************************************************************************
* 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) { function get_debug_backtrace_context($ignore_last=0) {
$traces = debug_backtrace(); $traces = debug_backtrace();
@ -105,6 +167,7 @@ function get_debug_backtrace_context($ignore_last=0) {
$trace = array("#$i"); $trace = array("#$i");
if (isset($traces[$i]['file'])) if (isset($traces[$i]['file']))
$trace[] = $traces[$i]['file'].(isset($traces[$i]['line'])?":".$traces[$i]['line']:""); $trace[] = $traces[$i]['file'].(isset($traces[$i]['line'])?":".$traces[$i]['line']:"");
// @phpstan-ignore-next-line
if (isset($traces[$i]['class']) && isset($traces[$i]['function'])) if (isset($traces[$i]['class']) && isset($traces[$i]['function']))
$trace[] = implode(" ", array( $trace[] = implode(" ", array(
$traces[$i]['class'], $traces[$i]['class'],
@ -118,7 +181,16 @@ function get_debug_backtrace_context($ignore_last=0) {
return implode("\n", $msg); 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) { 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 more than 2 arguments passed, format prefix message using sprintf
if ($prefix && func_num_args() > 2) { if ($prefix && func_num_args() > 2) {
$prefix = call_user_func_array( $prefix = call_user_func_array(
@ -135,13 +207,50 @@ function log_exception($exception, $prefix=null) {
} }
set_exception_handler('log_exception'); set_exception_handler('log_exception');
// Handle PHP error logging /*
function log_php_eror($errno, $errstr, $errfile, $errline) { *******************************************************************************
logging("ERROR", "A PHP error occured : [%d] %s\nFile : %s (line : %d)", * Handle PHP error logging
$errno, $errstr, $errfile, $errline); *******************************************************************************
*/
/**
* 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; return False;
} }
if ($log_level == 'DEBUG') if (isset($log_php_errors_levels))
set_error_handler('log_php_eror', E_ALL & ~E_STRICT); 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 else
set_error_handler('log_php_eror', E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED); 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,24 +1,72 @@
<?php <?php
// Load PHP PEAR Mail and Mail_mime libs // Load PHP PEAR Mail and Mail_mime libs
require_once($php_mail_path); require_once(isset($php_mail_path)?$php_mail_path:"Mail.php");
require_once($php_mail_mime_path); require_once(isset($php_mail_mime_path)?$php_mail_mime_path:"Mail/mime.php");
function send_mail($from, $to, $subject, $msg, $headers=array(), $attachments=array(), /**
$crlf="\r\n") { * Send an email
global $mail_send_method, $mail_headers, $mail_send_params, $mail_catch_all, $mail_sender; *
$mail_obj = Mail::factory($mail_send_method, $mail_send_params); * @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 ($mail_catch_all) { if (!$headers) $headers = array();
$msg .= sprintf( if(isset($mail_headers) && is_array($mail_headers)) {
_("\n\n\nMail initialy intended for %s."), $headers = array_merge($headers, $mail_headers);
(is_array($to)?implode(',', $to):$to));
$to = $mail_catch_all;
} }
if(is_array($mail_headers)) { logging(
$headers = array_merge($headers,$mail_headers); '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; $headers["To"] = $to;
$to = array ( $to = array (
@ -26,33 +74,43 @@ function send_mail($from, $to, $subject, $msg, $headers=array(), $attachments=ar
); );
foreach(array_keys($headers) as $header) { foreach(array_keys($headers) as $header) {
if(strtoupper($header) == 'BCC') { if(in_array(strtoupper($header), array('BCC', 'CC'))) {
$to['BCC'] = $headers[$header]; if (isset($mail_catch_all) && $mail_catch_all) {
} logging('DEBUG', "Mail catched: remove $header header");
elseif(strtoupper($header) == 'CC') { $msg .= sprintf(
$to['CC'] = $headers[$header]; (
$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( $mime = new Mail_mime(
array( array(
'eol' => $crlf, 'eol' => ($eol?$eol:"\n"),
'text_charset' => 'utf8', ($html?'html_charset':'text_charset') => $encoding,
'head_charset' => 'utf8', 'head_charset' => $encoding,
) )
); );
if ($from) {
$mime->setFrom($from);
}
elseif ($mail_sender) {
$mime->setFrom($mail_sender);
}
if ($subject) { if ($from)
$mime->setFrom($from);
if ($subject)
$mime->setSubject($subject); $mime->setSubject($subject);
}
$mime->setTXTBody($msg);
if ($html)
$mime->setHTMLBody($msg);
else
$mime->setTXTBody($msg);
if (is_array($attachments) && !empty($attachments)) { if (is_array($attachments) && !empty($attachments)) {
$finfo = new finfo(FILEINFO_MIME_TYPE); $finfo = new finfo(FILEINFO_MIME_TYPE);
@ -64,11 +122,14 @@ function send_mail($from, $to, $subject, $msg, $headers=array(), $attachments=ar
$body = $mime->get(); $body = $mime->get();
$headers = $mime->headers($headers); $headers = $mime->headers($headers);
$ret = $mail_obj -> send($to,$headers,$body); $ret = $mail_obj -> send($to, $headers, $body);
if ($ret instanceof PEAR_Error) { if (PEAR::isError($ret)) {
logging('ERROR',"Error sending email to $to : ".$ret -> getMessage()); $msg = "Error sending email: ".$ret -> getMessage();
return False; logging('ERROR', $msg);
return false;
} }
return true; return true;
} }
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

213
includes/sentry.php Normal file
View file

@ -0,0 +1,213 @@
<?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

@ -17,7 +17,7 @@ if (!isset($_SESSION['session_key'])) {
} }
// Handle session timeout // Handle session timeout
if ($session_timeout) { if (isset($session_timeout) && $session_timeout) {
if (!isset($_SESSION['session_last_access'])) { if (!isset($_SESSION['session_last_access'])) {
logging('DEBUG', 'Set initial session last access'); logging('DEBUG', 'Set initial session last access');
$_SESSION['session_last_access'] = time(); $_SESSION['session_last_access'] = time();
@ -40,3 +40,5 @@ function check_session_key($value=null) {
$value = $_REQUEST['session_key']; $value = $_REQUEST['session_key'];
return ($value && $_SESSION['session_key'] == $value); return ($value && $_SESSION['session_key'] == $value);
} }
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -3,7 +3,6 @@
if (php_sapi_name() == "cli") if (php_sapi_name() == "cli")
return true; return true;
require_once($smarty_path);
$smarty = new Smarty(); $smarty = new Smarty();
/* /*
@ -62,8 +61,26 @@ else {
} }
// Configure templates/templates_c directories // Configure templates/templates_c directories
$smarty->setTemplateDir($smarty_templates_dir); if (
$smarty->setCompileDir($smarty_templates_c_dir); !isset($smarty_templates_dir)
|| !is_dir($smarty_templates_dir)
)
logging(
'FATAL', "Template directory not found (%s)",
isset($smarty_templates_dir)?$smarty_templates_dir:'not set');
else
$smarty->setTemplateDir($smarty_templates_dir);
if (
!isset($smarty_templates_c_dir)
|| !is_dir($smarty_templates_c_dir)
|| !is_writable($smarty_templates_c_dir)
)
logging(
'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);
// Enable Smarty security // Enable Smarty security
smarty_enable_security_mode( smarty_enable_security_mode(
@ -77,13 +94,13 @@ smarty_enable_security_mode(
); );
// Defined some global template variables // Defined some global template variables
$smarty->assign('public_root_url', $public_root_url); $smarty->assign('public_root_url', isset($public_root_url)?$public_root_url:'/');
$smarty->assign('main_pagetitle', $main_pagetitle); $smarty->assign('main_pagetitle', isset($main_pagetitle)?$main_pagetitle:null);
$smarty->assign('session_key', $_SESSION['session_key']); $smarty->assign('session_key', $_SESSION['session_key']);
// Handle in-page errors & messages // Handle in-page errors & messages
if (!isset($_SESSION['errors'])) if (!isset($_SESSION['errors']))
$_SESSION['errors']=array(); $_SESSION['errors'] = array();
function add_error($error) { function add_error($error) {
// If more than one arguments passed, format error message using sprintf // If more than one arguments passed, format error message using sprintf
if (func_num_args() > 1) { if (func_num_args() > 1) {
@ -96,7 +113,7 @@ function add_error($error) {
} }
if (!isset($_SESSION['messages'])) if (!isset($_SESSION['messages']))
$_SESSION['messages']=array(); $_SESSION['messages'] = array();
function add_message($message) { function add_message($message) {
// If more than one arguments passed, format message using sprintf // If more than one arguments passed, format message using sprintf
if (func_num_args() > 1) { if (func_num_args() > 1) {
@ -113,42 +130,72 @@ if (isset($included_css_files) && is_array($included_css_files)) {
$_css = $included_css_files; $_css = $included_css_files;
} }
else { else {
$_css=array(); $_css = array();
} }
function add_css_file($files) { function add_css_file() {
global $_css; global $_css;
if (!is_array($files)) $files=array($files); foreach (func_get_args() as $files) {
foreach ($files as $file) if (!is_array($files)) $files=array($files);
if (!in_array($file, $_css)) foreach ($files as $file) {
$_css[]=$file; if (!in_array($file, $_css))
$_css[]=$file;
}
}
} }
$_js=array(); $_js = array();
function add_js_file($files) { function add_js_file() {
global $_js; global $_js;
if (!is_array($files)) $files=array($files); foreach (func_get_args() as $files) {
foreach ($files as $file) if (!is_array($files)) $files = array($files);
if (!in_array($file, $_js)) foreach ($files as $file) {
$_js[]=$file; if (!in_array($file, $_js))
$_js[] = $file;
}
}
} }
function _defineCommonTemplateVariables($template, $pagetitle) { function _defineCommonTemplateVariables($template, $pagetitle) {
global $smarty, $_css, $_js, $status_list, $auth_user, $admin; global $smarty, $_css, $_js, $status_list, $auth_user, $admin, $webstats_js_code;
$smarty->assign('pagetitle', $pagetitle); $smarty->assign('pagetitle', $pagetitle);
// Messages
$smarty -> assign('errors', (isset($_SESSION['errors'])?$_SESSION['errors']:array()));
$smarty -> assign('messages', (isset($_SESSION['messages'])?$_SESSION['messages']:array()));
// Files inclusions
$smarty -> assign('css', $_css);
$smarty -> assign('js', $_js);
// Authenticated user info
if (isset($auth_user)) if (isset($auth_user))
$smarty->assign('auth_user', $auth_user); $smarty->assign('auth_user', $auth_user);
$smarty->assign('errors', $_SESSION['errors']);
$smarty->assign('messages', $_SESSION['messages']); // Webstats JS code
$smarty->assign('css', $_css); $smarty->assign(
$smarty->assign('js', $_js); 'webstats_js_code',
isset($webstats_js_code)?$webstats_js_code:null);
} }
function display_template($template, $pagetitle=false) { function display_template($template, $pagetitle=false) {
if (!$template) if (!$template)
logging("FATAL", _("No template specified.")); logging("FATAL", _("No template specified."));
// If refresh parameter is present, remove it and redirect
if (isset($_GET['refresh'])) {
unset($_GET['refresh']);
$url = get_current_url();
if (!empty($_GET))
$url .= '?'.http_build_query($_GET);
redirect($url);
return;
}
$sentry_span = new SentrySpan('smarty.display_template', "Display Smarty template");
global $smarty; global $smarty;
// If more than 2 arguments passed, format pagetitle using sprintf // If more than 2 arguments passed, format pagetitle using sprintf
if ($pagetitle & func_num_args() > 2) { if ($pagetitle && func_num_args() > 2) {
$pagetitle = call_user_func_array( $pagetitle = call_user_func_array(
'sprintf', 'sprintf',
array_merge(array($pagetitle), array_slice(func_get_args(), 2)) array_merge(array($pagetitle), array_slice(func_get_args(), 2))
@ -163,8 +210,11 @@ function display_template($template, $pagetitle=false) {
catch (Exception $e) { 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') if ($template != 'fatal_error.tpl')
logging("FATAL", _("An error occurred while viewing this page.")); logging("FATAL", _("An error occurred while displaying this page."));
} }
$sentry_span->finish();
} }
function display_ajax_return($data=null, $pretty=false) { function display_ajax_return($data=null, $pretty=false) {
@ -269,3 +319,5 @@ function smarty_encodeJsonBase64($params, $smarty) {
echo base64_encode(json_encode($params['data'])); echo base64_encode(json_encode($params['data']));
} }
smarty_register_function('encodeJsonBase64','smarty_encodeJsonBase64'); smarty_register_function('encodeJsonBase64','smarty_encodeJsonBase64');
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -154,7 +154,7 @@ add_cli_command(
* all PO file in lang/[lang]/LC_MESSAGES. * all PO file in lang/[lang]/LC_MESSAGES.
* *
* @param array $command_args The command arguments * @param array $command_args The command arguments
* @return void * @return bool
*/ */
function cli_update_messages($command_args) { function cli_update_messages($command_args) {
global $root_dir_path, $root_lang_dir, $smarty_templates_dir; global $root_dir_path, $root_lang_dir, $smarty_templates_dir;
@ -234,6 +234,7 @@ function cli_update_messages($command_args) {
} }
logging('FATAL', _("Fail to open root lang directory (%s)."), $root_dir_path); logging('FATAL', _("Fail to open root lang directory (%s)."), $root_dir_path);
return false;
} }
add_cli_command( add_cli_command(
'update_messages', 'update_messages',
@ -248,7 +249,7 @@ add_cli_command(
* to corresponding MO files and as JSON catalog (for translation in JS). * to corresponding MO files and as JSON catalog (for translation in JS).
* *
* @param array $command_args The command arguments * @param array $command_args The command arguments
* @return void * @return bool
*/ */
function cli_compile_messages($command_args) { function cli_compile_messages($command_args) {
global $root_dir_path, $root_lang_dir, $smarty_templates_dir; global $root_dir_path, $root_lang_dir, $smarty_templates_dir;
@ -354,6 +355,7 @@ function cli_compile_messages($command_args) {
return !$error; return !$error;
} }
logging('FATAL', _("Fail to open root lang directory (%s)."), $root_dir_path); logging('FATAL', _("Fail to open root lang directory (%s)."), $root_dir_path);
return false;
} }
add_cli_command( add_cli_command(
'compile_messages', 'compile_messages',
@ -368,3 +370,5 @@ add_cli_command(
"directories to MO files and as JSON catalogs in public_html/translations." "directories to MO files and as JSON catalogs in public_html/translations."
) )
); );
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -145,3 +145,5 @@ function init_translation() {
add_js_file(array("lib/babel.js", "js/translation.js", $js_translation_file)); add_js_file(array("lib/babel.js", "js/translation.js", $js_translation_file));
} }
} }
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -14,3 +14,5 @@ function get_item_from_url($id, $fatal=false) {
} }
return $item; return $item;
} }
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -258,3 +258,5 @@ function handle_delete($request) {
redirect('item'); redirect('item');
} }
add_url_handler('|^item/(?P<id>[0-9]+)/delete$|', 'handle_delete'); add_url_handler('|^item/(?P<id>[0-9]+)/delete$|', 'handle_delete');
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -26,15 +26,16 @@ $url_patterns =array();
/** /**
* Add an URL pattern * Add an URL pattern
* *
* @param $pattern string The URL pattern (required) * @param string|array $pattern The URL pattern or an array of patterns (required)
* @param $handler callable The URL pattern handler (must be callable, required) * @param callable $handler The URL pattern handler (must be callable, required)
* @param $authenticated boolean Permit to define if this URL is accessible only for * @param boolean $authenticated Permit to define if this URL is accessible only for
* authenticated users (optional, default: true if the special * authenticated users (optional, default: true if the
* force_authentication function is defined, false otherwise) * special force_authentication function is defined,
* @param $override boolean Allow override if a command already exists with the * false otherwise)
* same name (optional, default: false) * @param boolean $override Allow override if a command already exists with the
* @param $api_mode boolean Enable API mode (optional, default: false) * same name (optional, default: false)
* @param $methods array|null HTTP method (optional, default: array('GET', 'POST')) * @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, function add_url_handler($pattern, $handler=null, $authenticated=null, $override=true,
$api_mode=false, $methods=null) { $api_mode=false, $methods=null) {
@ -67,9 +68,9 @@ function add_url_handler($pattern, $handler=null, $authenticated=null, $override
} }
elseif ($override) { elseif ($override) {
logging( logging(
'DEBUG', 'DEBUG', "URL : override pattern '%s' with handler '%s' (old handler = '%s')".
"URL : override pattern '$pattern' with handler '$handler' ". $pattern, format_callable($handler), vardump($url_patterns[$pattern])
"(old handler = '".$url_patterns[$pattern]."')"); );
$url_patterns[$pattern] = array( $url_patterns[$pattern] = array(
'handler' => $handler, 'handler' => $handler,
'authenticated' => $authenticated, 'authenticated' => $authenticated,
@ -133,9 +134,9 @@ function error_page($request=null, $error_code=null) {
/** /**
* Error 404 handler * Error 404 handler
* *
* @param[in] $request UrlRequest|null The request (optional, default: null) * @param UrlRequest|null $request The request (optional, default: null)
* *
* @retval void * @return void
**/ **/
function error_404($request=null) { function error_404($request=null) {
error_page($request, 404); error_page($request, 404);
@ -174,7 +175,7 @@ function get_request($default_url=null) {
logging('DEBUG', "URL : current url = '$current_url'"); logging('DEBUG', "URL : current url = '$current_url'");
logging( logging(
'DEBUG', 'TRACE',
"URL : check current url with the following URL patterns :\n - ". "URL : check current url with the following URL patterns :\n - ".
implode("\n - ", array_keys($url_patterns)) implode("\n - ", array_keys($url_patterns))
); );
@ -185,7 +186,7 @@ function get_request($default_url=null) {
// Reset last redirect // Reset last redirect
if (isset($_SESSION['last_redirect'])) if (isset($_SESSION['last_redirect']))
unset($_SESSION['last_redirect']); unset($_SESSION['last_redirect']);
logging('DEBUG', "URL : result :\n".varDump($request, 1)); logging('TRACE', "URL : result :\n".vardump($request));
return $request; return $request;
} }
} }
@ -212,9 +213,9 @@ function get_request($default_url=null) {
/** /**
* Check if the current requested URL match with a specific pattern * Check if the current requested URL match with a specific pattern
* *
* @param $pattern string The URL pattern * @param string $pattern The URL pattern
* @param $current_url string|false The current URL (optional) * @param string|false $current_url The current URL (optional)
* @param $methods array|null HTTP method (optional, default: no check) * @param array|null $methods HTTP method (optional, default: no check)
* *
* @return array|false The URL info if pattern matched, false otherwise. * @return array|false The URL info if pattern matched, false otherwise.
**/ **/
@ -229,7 +230,7 @@ function url_match($pattern, $current_url=false, $methods=null) {
logging( logging(
'DEBUG', 'DEBUG',
"URL : Match found with pattern '$pattern' :\n\t". "URL : Match found with pattern '$pattern' :\n\t".
str_replace("\n", "\n\t", print_r($m, 1))); str_replace("\n", "\n\t", print_r($m, true)));
return $m; return $m;
} }
return False; return False;
@ -241,10 +242,10 @@ function url_match($pattern, $current_url=false, $methods=null) {
* @return string|false The current request URL or false if fail * @return string|false The current request URL or false if fail
**/ **/
function get_current_url() { function get_current_url() {
logging('DEBUG', "URL : request URI = '".$_SERVER['REQUEST_URI']."'"); logging('TRACE', "URL : request URI = '".$_SERVER['REQUEST_URI']."'");
$base = get_rewrite_base(); $base = get_rewrite_base();
logging('DEBUG', "URL : rewrite base = '$base'"); logging('TRACE', "URL : rewrite base = '$base'");
if ($_SERVER['REQUEST_URI'] == $base) if ($_SERVER['REQUEST_URI'] == $base)
return ''; return '';
@ -289,7 +290,7 @@ function get_rewrite_base() {
/** /**
* Trigger redirect to specified URL (or homepage if omited) * Trigger redirect to specified URL (or homepage if omited)
* *
* @param $go string|false The destination URL * @param string|false $go The destination URL
* *
* @return void * @return void
**/ **/
@ -298,6 +299,15 @@ function redirect($go=false) {
if ($go===false) if ($go===false)
$go = ""; $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)) if (is_absolute_url($go))
$url = $go; $url = $go;
@ -332,7 +342,7 @@ function redirect($go=false) {
* invoke the force_authentication() special function (or trigger a fatal error * invoke the force_authentication() special function (or trigger a fatal error
* if it's not defined). * if it's not defined).
* *
* @param $default_url string|null The default URL if current one does not * @param string|null $default_url The default URL if current one does not
* match with any configured pattern. * match with any configured pattern.
* *
* @return void * @return void
@ -340,10 +350,14 @@ function redirect($go=false) {
function handle_request($default_url=null) { function handle_request($default_url=null) {
global $smarty, $api_mode; global $smarty, $api_mode;
$sentry_span = new SentrySpan('http.handle_request', 'Handle the HTTP request');
$request = get_request($default_url); $request = get_request($default_url);
if (!is_callable($request -> handler)) { if (!is_callable($request -> handler)) {
logging('ERROR', "URL handler function ".$request -> handler."() does not exists !"); logging(
'ERROR', "URL handler function %s does not exists !",
format_callable($request -> handler));
logging('FATAL', _("This request cannot be processed.")); logging('FATAL', _("This request cannot be processed."));
} }
@ -360,19 +374,20 @@ function handle_request($default_url=null) {
logging('FATAL', _("Authentication required but force_authentication function is not defined.")); logging('FATAL', _("Authentication required but force_authentication function is not defined."));
try { try {
return call_user_func($request -> handler, $request); call_user_func($request -> handler, $request);
} }
catch (Exception $e) { catch (Exception $e) {
log_exception( log_exception(
$e, "An exception occured running URL handler function ".$request -> handler."()"); $e, "An exception occured running URL handler function ".$request -> handler."()");
logging('FATAL', _("This request could not be processed correctly.")); logging('FATAL', _("This request could not be processed correctly."));
} }
$sentry_span->finish();
} }
/** /**
* Remove trailing slash in specified URL * Remove trailing slash in specified URL
* *
* @param $url string The URL * @param string $url The URL
* *
* @return string The specified URL without trailing slash * @return string The specified URL without trailing slash
**/ **/
@ -390,13 +405,13 @@ function remove_trailing_slash($url) {
* Check if session key is present and valid and set AJAX * Check if session key is present and valid and set AJAX
* mode. * mode.
* *
* @param $session_key string The current session key (optional) * @param string|null $session_key string The current session key (optional)
* *
* @return void * @return void
**/ **/
function check_ajax_request($session_key=null) { function check_ajax_request($session_key=null) {
global $ajax, $debug_ajax; global $ajax, $debug_ajax;
$ajax=true; $ajax = true;
if (check_session_key($session_key)) if (check_session_key($session_key))
fatal_error('Invalid request'); fatal_error('Invalid request');
@ -408,7 +423,7 @@ function check_ajax_request($session_key=null) {
/** /**
* Get the public absolute URL * Get the public absolute URL
* *
* @param $relative_url string|null Relative URL to convert (Default: current URL) * @param string|null $relative_url Relative URL to convert (Default: current URL)
* *
* @return string The public absolute URL * @return string The public absolute URL
**/ **/
@ -430,7 +445,7 @@ function get_absolute_url($relative_url=null) {
} }
if (substr($relative_url, 0, 1) == '/') if (substr($relative_url, 0, 1) == '/')
$relative_url = substr($url, 1); $relative_url = substr($relative_url, 1);
$url = remove_trailing_slash($public_root_url)."/$relative_url"; $url = remove_trailing_slash($public_root_url)."/$relative_url";
logging('DEBUG', "URL :: get_absolute_url($relative_url): result = $url"); logging('DEBUG', "URL :: get_absolute_url($relative_url): result = $url");
return $url; return $url;
@ -439,7 +454,7 @@ function get_absolute_url($relative_url=null) {
/** /**
* Check if specified URL is absolute * Check if specified URL is absolute
* *
* @param $url string The URL to check * @param string $url The URL to check
* *
* @return boolean True if specified URL is absolute, False otherwise * @return boolean True if specified URL is absolute, False otherwise
**/ **/
@ -450,12 +465,12 @@ function is_absolute_url($url) {
/** /**
* Add parameter in specified URL * Add parameter in specified URL
* *
* @param &$url string The reference of the URL * @param string &$url The reference of the URL
* @param $param string The parameter name * @param string $param The parameter name
* @param $value string The parameter value * @param string $value The parameter value
* @param $encode boolean Set if parameter value must be URL encoded (optional, default: true) * @param boolean $encode Set if parameter value must be URL encoded (optional, default: true)
* *
* @return string|null The completed URL * @return string The completed URL
*/ */
function add_url_parameter(&$url, $param, $value, $encode=true) { function add_url_parameter(&$url, $param, $value, $encode=true) {
if (strpos($url, '?') === false) if (strpos($url, '?') === false)
@ -503,7 +518,7 @@ class UrlRequest {
/** /**
* Get request info * Get request info
* *
* @param $key string The name of the info * @param string $key The name of the info
* *
* @return mixed The value * @return mixed The value
**/ **/
@ -527,15 +542,39 @@ class UrlRequest {
logging('WARNING', "__get($key): invalid property requested\n".get_debug_backtrace_context()); 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 * Check is request info is set
* *
* @param $key string The name of the info * @param string $key The name of the info
* *
* @return boolval True is info is set, False otherwise * @return bool True is info is set, False otherwise
**/ **/
public function __isset($key) { public function __isset($key) {
if (in_array($key, array('current_url', 'handler', 'authenticated'))) if (
in_array(
$key, array('current_url', 'handler', 'authenticated',
'api_mode', 'referer', 'http_method')
)
)
return True; return True;
return array_key_exists($key, $this->url_params); return array_key_exists($key, $this->url_params);
} }
@ -543,9 +582,9 @@ class UrlRequest {
/** /**
* Get request parameter * Get request parameter
* *
* @param $parameter string The name of the parameter * @param string $parameter The name of the parameter
* @param $decode string If true, the parameter value will be urldecoded * @param bool $decode If true, the parameter value will be urldecoded
* (optional, default: true) * (optional, default: true)
* *
* @return mixed The value or false if parameter does not exists * @return mixed The value or false if parameter does not exists
**/ **/
@ -570,3 +609,5 @@ class UrlRequest {
} }
} }
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

31
phpstan.neon Normal file
View file

@ -0,0 +1,31 @@
parameters:
level: 5
paths:
- includes
- public_html
- bin
excludePaths:
- includes/config.local.php
ignoreErrors:
-
message: "#Instantiated class Mail_mime not found\\.#"
path: includes/mail.php
-
message: "#Call to method (setFrom|setSubject|setHTMLBody|setTXTBody|get|headers|addAttachment)\\(\\) on an unknown class Mail_mime\\.#"
path: includes/mail.php
-
message: "#Call to static method factory\\(\\) on an unknown class Mail\\.#"
path: includes/mail.php
-
message: "#Call to static method isError\\(\\) on an unknown class PEAR\\.#"
path: includes/mail.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:
- includes/cli.php

View file

@ -5,3 +5,5 @@ include 'url-public.php';
$default_url=''; $default_url='';
handle_request(); handle_request();
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -97,5 +97,6 @@
<script language="javascript" src="{$file}"></script> <script language="javascript" src="{$file}"></script>
{/foreach} {/foreach}
{if $webstats_js_code}{$webstats_js_code}{/if}
</body> </body>
</html> </html>