eesyphp/includes/db.php
Benjamin Renard 6fdc5447f1 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
2023-01-29 11:51:41 +01:00

473 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