Benjamin Renard
6fdc5447f1
* 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
472 lines
12 KiB
PHP
472 lines
12 KiB
PHP
<?php
|
|
|
|
use Unidecode\Unidecode;
|
|
|
|
if (!isset($db_dsn)) {
|
|
logging('FATAL', 'Database DSN not configured');
|
|
exit(1);
|
|
}
|
|
|
|
try {
|
|
$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);
|
|
$fpdo = new FluentPDO($pdo);
|
|
|
|
$fpdo -> debug = function ($q) {
|
|
$time = sprintf('%0.3f', $q->getTime() * 1000) . ' ms';
|
|
$rows = ($q->getResult()) ? $q->getResult()->rowCount() : 0;
|
|
$query = $q->getQuery();
|
|
$msg = "# DB query ($time; rows = $rows) : $query";
|
|
|
|
$parameters = $q->getParameters();
|
|
if ($parameters) {
|
|
if (is_array($parameters)) {
|
|
$msg .= "\n# Parameters: '" . implode("', '", $parameters) . "'";
|
|
}
|
|
else {
|
|
$msg .= "\n# Parameters: '" . vardump($parameters) . "'";
|
|
}
|
|
}
|
|
|
|
logging('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.'));
|
|
}
|
|
|
|
/*
|
|
* Handle date/datetime format
|
|
*/
|
|
function db_date2time($date) {
|
|
global $db_date_format;
|
|
setlocale(LC_TIME, "fr_FR");
|
|
$pdate = strptime($date, $db_date_format);
|
|
return mktime(
|
|
$pdate['tm_hour'], $pdate['tm_min'], $pdate['tm_sec'],
|
|
$pdate['tm_mon'] + 1, $pdate['tm_mday'], $pdate['tm_year'] + 1900
|
|
);
|
|
}
|
|
|
|
function db_time2date($time) {
|
|
global $db_date_format;
|
|
setlocale(LC_TIME, "fr_FR");
|
|
return strftime($db_date_format, $time);
|
|
}
|
|
|
|
function db_datetime2time($date) {
|
|
global $db_datetime_format;
|
|
setlocale(LC_TIME, "fr_FR");
|
|
$pdate = strptime($date, $db_datetime_format);
|
|
return mktime(
|
|
$pdate['tm_hour'], $pdate['tm_min'], $pdate['tm_sec'],
|
|
$pdate['tm_mon'] + 1, $pdate['tm_mday'], $pdate['tm_year'] + 1900
|
|
);
|
|
}
|
|
|
|
function db_time2datetime($time) {
|
|
global $db_datetime_format;
|
|
setlocale(LC_TIME, "fr_FR");
|
|
return strftime($db_datetime_format, $time);
|
|
}
|
|
|
|
/*
|
|
* Helper to format row info from DB
|
|
*/
|
|
function _format_row_info($row, $datetime_fields) {
|
|
// Convert datetime fields
|
|
foreach($datetime_fields as $field)
|
|
if ($row[$field])
|
|
$row[$field] = db_datetime2time($row[$field]);
|
|
return $row;
|
|
}
|
|
|
|
/*
|
|
* Methods to handle items
|
|
*/
|
|
function get_items($orderby='id', $raw_values=false) {
|
|
global $fpdo;
|
|
try {
|
|
$query = $fpdo -> from('item')
|
|
-> orderBy($orderby);
|
|
|
|
$result = $query -> execute();
|
|
if ($result !== false) {
|
|
$info = $result -> fetchAll();
|
|
if ($info === false)
|
|
return null;
|
|
if ($raw_values)
|
|
return $info;
|
|
|
|
$items = array();
|
|
foreach ($info as $item)
|
|
$items[$item['id']] = _format_row_info($item, array('date'));
|
|
return $items;
|
|
}
|
|
}
|
|
catch (Exception $e) {
|
|
logging('ERROR', "Error retreiving items info from database : ".$e->getMessage());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function get_item($id, $raw_values=false) {
|
|
global $fpdo;
|
|
try {
|
|
$query = $fpdo -> from('item')
|
|
-> where('id', $id);
|
|
|
|
$result = $query -> execute();
|
|
if ($result !== false) {
|
|
$info = $result -> fetch();
|
|
if ($info === false)
|
|
return null;
|
|
if ($raw_values)
|
|
return $info;
|
|
|
|
return _format_row_info($info, array('date'));
|
|
}
|
|
}
|
|
catch (Exception $e) {
|
|
logging('ERROR', "Error retreiving item #$id info from database : ".$e->getMessage());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function add_item($values) {
|
|
global $fpdo;
|
|
$values['date'] = db_time2datetime(time());
|
|
try {
|
|
$result = $fpdo -> insertInto('item')
|
|
-> values($values)
|
|
-> execute();
|
|
|
|
if ($result !== false) {
|
|
$item = get_item($result);
|
|
logging('INFO', "New item #$result added");
|
|
trigger_hook('item_added', $item);
|
|
return $item;
|
|
}
|
|
}
|
|
catch (Exception $e) {
|
|
logging('ERROR', "Error creating item in database : ".$e->getMessage());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function update_item($id, $changes) {
|
|
global $fpdo;
|
|
|
|
if (!is_array($changes))
|
|
return false;
|
|
if (empty($changes))
|
|
return true;
|
|
$item = get_item($id, true);
|
|
if (!is_array($item)) return false;
|
|
|
|
if (isset($changes['date']) && !is_null($changes['date']))
|
|
$changes['date'] = db_time2datetime($changes['date']);
|
|
|
|
try {
|
|
$result = $fpdo -> update('item')
|
|
-> set($changes)
|
|
-> where('id', $id)
|
|
-> execute();
|
|
|
|
if ($result !== false) {
|
|
logging('INFO', "Item #$id updated");
|
|
trigger_hook('item_updated', $item);
|
|
return true;
|
|
}
|
|
}
|
|
catch (Exception $e) {
|
|
logging('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.");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function archive_item($id) {
|
|
return change_item_status($id, 'archived');
|
|
}
|
|
|
|
function delete_item($id) {
|
|
global $fpdo;
|
|
try {
|
|
$result = $fpdo -> deleteFrom('item')
|
|
-> where('id', $id)
|
|
-> execute();
|
|
|
|
if ($result !== false) {
|
|
logging('INFO', "Item #$id deleted");
|
|
return True;
|
|
}
|
|
}
|
|
catch (Exception $e) {
|
|
logging('ERROR', "Error deleting item #$id from database : ".$e->getMessage());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function search_items($params) {
|
|
global $fpdo, $db_dsn;
|
|
|
|
// Detect PgSQL backend
|
|
$is_pgsql = (strpos($db_dsn, "pgsql:") === 0);
|
|
|
|
$where = array();
|
|
if (isset($params['status']) && $params['status'] && $params['status'] != 'all')
|
|
$where['status'] = $params['status'];
|
|
|
|
$patterns_where = array();
|
|
if (isset($params['pattern']) && $params['pattern']) {
|
|
foreach(preg_split('/\s+/', trim($params['pattern'])) as $word) {
|
|
if (!$word) continue;
|
|
$patterns_word=array();
|
|
|
|
// If numeric pattern ?
|
|
if (preg_match('/^[0-9]+$/', $word)) {
|
|
// Numeric pattern
|
|
$word = intval($word);
|
|
$patterns_word['id = ?'] = $word;
|
|
}
|
|
else {
|
|
// Text pattern
|
|
foreach (array('name', 'description') as $field) {
|
|
if ($is_pgsql) {
|
|
$word = Unidecode::unidecode($word);
|
|
$patterns_word["unaccent($field) ILIKE ?"] = "%$word%";
|
|
}
|
|
else
|
|
$patterns_word["$field LIKE ?"] = "%$word%";
|
|
}
|
|
}
|
|
$patterns_where[] = $patterns_word;
|
|
}
|
|
}
|
|
|
|
$order='id';
|
|
$orders=array('id', 'name', 'date', 'status', 'description');
|
|
if (isset($params['order'])) {
|
|
if (!in_array($order, $orders))
|
|
return -1;
|
|
$order=$params['order'];
|
|
}
|
|
|
|
$order_direction='DESC';
|
|
if (isset($params['order_direction']) && $params['order_direction']) {
|
|
if (!in_array($params['order_direction'], array('ASC', 'DESC')))
|
|
return -1;
|
|
$order_direction=$params['order_direction'];
|
|
}
|
|
|
|
$orderby="$order $order_direction";
|
|
|
|
$limit = "";
|
|
$page = 1;
|
|
$nb_by_page = 10;
|
|
$offset = 0;
|
|
if (!isset($params['all'])) {
|
|
if (isset($params['page']) && $params['page']>0) {
|
|
if (isset($params['nb_by_page']) && $params['nb_by_page']>0) {
|
|
$nb_by_page = intval($params['nb_by_page']);
|
|
}
|
|
$page = intval($params['page']);
|
|
}
|
|
$offset = ($page-1)*$nb_by_page;
|
|
$limit = $nb_by_page;
|
|
}
|
|
|
|
try {
|
|
$query = $fpdo -> from('item');
|
|
if (!empty($where))
|
|
$query -> where($where);
|
|
foreach ($patterns_where as $patterns_word)
|
|
call_user_func_array(
|
|
array($query, 'where'),
|
|
array_merge(
|
|
array('('.implode(' OR ', array_keys($patterns_word)).')'),
|
|
array_values($patterns_word)
|
|
)
|
|
);
|
|
$result = $query -> orderBy($orderby)
|
|
-> limit($limit)
|
|
-> offset($offset)
|
|
-> execute();
|
|
|
|
if ($result === 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) {
|
|
log_exception(
|
|
$e, "An exception occured searching items with params %s infos from database : ",
|
|
preg_replace("/\n[ \t]*/", " ", print_r($params, true))
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function export_items($fd=null) {
|
|
if (!$fd) $fd = fopen('php://output', 'w');
|
|
fputcsv(
|
|
$fd,
|
|
array (
|
|
'id',
|
|
'name',
|
|
'date',
|
|
'status',
|
|
'description',
|
|
)
|
|
);
|
|
$items = get_items();
|
|
foreach($items as $item) {
|
|
fputcsv(
|
|
$fd,
|
|
array(
|
|
$item['id'],
|
|
$item['name'],
|
|
$item['date'],
|
|
$item['status'],
|
|
$item['description'],
|
|
)
|
|
);
|
|
}
|
|
return True;
|
|
}
|
|
|
|
function restore_items($fd=null) {
|
|
global $fpdo;
|
|
if (!$fd) $fd = fopen('php://stdin', 'r');
|
|
try {
|
|
$result = $fpdo -> deleteFrom('item')
|
|
-> execute();
|
|
if ($result === false) {
|
|
logging('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());
|
|
return false;
|
|
}
|
|
|
|
$first_row = false;
|
|
$line = 0;
|
|
$restored = 0;
|
|
$error = false;
|
|
$datetime_fields = array (
|
|
'date',
|
|
);
|
|
// Map fields to support hold CSV files format
|
|
$mapping = array (
|
|
'creation_date' => 'date',
|
|
'desc' => 'description',
|
|
);
|
|
|
|
while (($row = fgetcsv($fd)) !== FALSE) {
|
|
$line++;
|
|
if ($first_row === false) {
|
|
$first_row = $row;
|
|
continue;
|
|
}
|
|
try {
|
|
$values = array();
|
|
for ($i=0; $i < count($first_row); $i++) {
|
|
if (!$row[$i]) continue;
|
|
$field = (
|
|
array_key_exists($first_row[$i], $mapping)?
|
|
$mapping[$first_row[$i]]:
|
|
$first_row[$i]);
|
|
$value = (in_array($field, $datetime_fields)?db_time2datetime($row[$i]):$row[$i]);
|
|
$values[$field] = $value;
|
|
}
|
|
$result = $fpdo -> insertInto('item')
|
|
-> values($values)
|
|
-> execute();
|
|
|
|
if ($result !== false) {
|
|
$restored++;
|
|
}
|
|
else {
|
|
logging('ERROR', "Unkwown error occured restoring item from line #$line :\n".print_r($values, true));
|
|
$error = true;
|
|
}
|
|
}
|
|
catch (Exception $e) {
|
|
logging(
|
|
'ERROR',
|
|
"Error restoring item from line #$line : ".$e->getMessage()."\n".print_r($values, true));
|
|
$error = true;
|
|
}
|
|
}
|
|
logging('INFO', "$restored items restored");
|
|
|
|
// Trigger hooks
|
|
trigger_hook('items_restored');
|
|
|
|
return !$error;
|
|
}
|
|
|
|
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab
|