Adjust example to use DBObject and CSV export

This commit is contained in:
Benjamin Renard 2024-02-18 17:21:54 +01:00
parent f9829d4aea
commit 2f38884798
Signed by: bn8
GPG key ID: 3E2E1CE1907115BC
13 changed files with 314 additions and 480 deletions

View file

@ -22,7 +22,8 @@
} }
], ],
"require": { "require": {
"brenard/eesyphp": "@dev" "brenard/eesyphp": "@dev",
"brenard/php-unidecode": "dev-master"
}, },
"config": { "config": {
"allow-plugins": { "allow-plugins": {

View file

@ -6,6 +6,7 @@ use EesyPHP\Date;
use EesyPHP\Log; use EesyPHP\Log;
use EesyPHPExample\Db; use EesyPHPExample\Db;
use EesyPHPExample\Db\Item;
use function EesyPHP\___; use function EesyPHP\___;
@ -23,15 +24,15 @@ if (php_sapi_name() != "cli")
**/ **/
function print_item_info($item) { function print_item_info($item) {
printf(_("Item #%s:\n"), $item['id']); printf(_("Item #%s:\n"), $item->id);
printf("\t"._("ID: %s")."\n", $item['id']); printf("\t"._("ID: %s")."\n", $item->id);
printf("\t"._("Name: '%s'")."\n", $item['name']); printf("\t"._("Name: '%s'")."\n", $item->name);
printf("\t"._("Date: %s")."\n", Date :: format($item['date'])); printf("\t"._("Date: %s")."\n", Date :: format($item->date));
printf( printf(
"\t"._("Description: %s")."\n", "\t"._("Description: %s")."\n",
($item['description']?"'".$item['description']."'":_("Not set")) ($item->description?"'".$item->description."'":_("Not set"))
); );
printf("\t"._("Status: %s")."\n", $item['status']); printf("\t"._("Status: %s")."\n", $item->status);
return true; return true;
} }
@ -40,11 +41,11 @@ function print_item_info($item) {
* Common CLI commands * Common CLI commands
**/ **/
$orderbys = array('id', 'name', 'date', 'status', 'description');
function cli_list($command_args) { function cli_list($command_args) {
global $orderbys; global $orderbys;
$params = array( $params = array(
'order' => $orderbys[0], 'filters' => [],
'order' => Item :: default_order(),
'order_direction' => 'ASC', 'order_direction' => 'ASC',
'all' => true, 'all' => true,
); );
@ -54,7 +55,7 @@ function cli_list($command_args) {
case '-o': case '-o':
case '--orderby': case '--orderby':
$i++; $i++;
if(!in_array($command_args[$i], $orderbys)) if(!in_array($command_args[$i], Item :: possible_orders()))
Cli :: usage('Invalid --orderby clause'); Cli :: usage('Invalid --orderby clause');
$params['order'] = $command_args[$i]; $params['order'] = $command_args[$i];
break; break;
@ -67,7 +68,7 @@ function cli_list($command_args) {
$i++; $i++;
if(!check_status($command_args[$i])) if(!check_status($command_args[$i]))
Cli :: usage('Invalid -s/--status clause'); Cli :: usage('Invalid -s/--status clause');
$params['status'] = $command_args[$i]; $params['filters']['status'] = $command_args[$i];
break; break;
default: default:
$patterns[] = $command_args[$i]; $patterns[] = $command_args[$i];
@ -77,7 +78,7 @@ function cli_list($command_args) {
if (!empty($patterns)) if (!empty($patterns))
$params['pattern'] = implode(' ', $patterns); $params['pattern'] = implode(' ', $patterns);
$items = Db :: search_items($params); $items = Item :: search($params);
if (!is_array($items)) { if (!is_array($items)) {
Log :: error("Invalid DB info return.\n"); Log :: error("Invalid DB info return.\n");
return False; return False;
@ -101,11 +102,11 @@ function cli_list($command_args) {
foreach($items['items'] as $info) { foreach($items['items'] as $info) {
$tbl->addRow( $tbl->addRow(
array( array(
$info['id'], $info->id,
$info['name'], $info->name,
Date :: format($info['date']), Date :: format($info->date),
$info['status'], $info->status,
($info['description']?$info['description']:''), ($info->description?$info->description:''),
) )
); );
} }
@ -120,7 +121,7 @@ Cli :: add_command(
___("[patterns]"), ___("[patterns]"),
array( array(
___("-o|--orderby Ordering list criterion. Possible values:"), ___("-o|--orderby Ordering list criterion. Possible values:"),
" - ".implode("\n - ", $orderbys), " - ".implode("\n - ", Item :: possible_orders()),
___("-r|--reverse Reverse order"), ___("-r|--reverse Reverse order"),
___("-s|--status Filter on status. Possible values:"), ___("-s|--status Filter on status. Possible values:"),
" - ".implode("\n - ", array_keys($status_list)), " - ".implode("\n - ", array_keys($status_list)),
@ -132,7 +133,7 @@ function cli_show($command_args) {
Cli :: usage(_('You must provide a valid ID.')); Cli :: usage(_('You must provide a valid ID.'));
$item_id = $command_args[0]; $item_id = $command_args[0];
$item = Db :: get_item($item_id); $item = Item :: get($item_id);
if (!$item) if (!$item)
Log :: fatal(_("Item #%s not found."), $item_id); Log :: fatal(_("Item #%s not found."), $item_id);
@ -157,7 +158,7 @@ function cli_delete($command_args) {
// Check exist // Check exist
$item_id = $command_args[0]; $item_id = $command_args[0];
$item = Db :: get_item($item_id); $item = Item :: get($item_id);
if (!$item) if (!$item)
Log :: fatal(_("Item #%s not found."), $item_id); Log :: fatal(_("Item #%s not found."), $item_id);
@ -173,7 +174,7 @@ function cli_delete($command_args) {
} }
echo "\n"; echo "\n";
if (!Db :: delete_item($item['id'])) if (!$item -> delete())
Log :: fatal(_("An error occurred deleting item #%d."), $item_id); Log :: fatal(_("An error occurred deleting item #%d."), $item_id);
return True; return True;
@ -187,7 +188,7 @@ Cli :: add_command(
function cli_export($command_args) { function cli_export($command_args) {
$fd = fopen((count($command_args) >= 1?$command_args[0]:'php://output'), 'w'); $fd = fopen((count($command_args) >= 1?$command_args[0]:'php://output'), 'w');
Db :: export_items($fd); Item :: export($fd);
fclose($fd); fclose($fd);
Log :: 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')."'.");
} }
@ -200,7 +201,7 @@ Cli :: add_command(
function cli_restore($command_args) { 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');
Db :: restore_items($fd); Item :: restore($fd);
fclose($fd); fclose($fd);
Log :: info( Log :: info(
"Items restored from '%s'", "Items restored from '%s'",
@ -243,33 +244,33 @@ function cli_cron($command_args) {
'Invalid $item_max_age value set in configuration: it\'s must be a positive integer.'); 'Invalid $item_max_age value set in configuration: it\'s must be a positive integer.');
Log :: 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); $limit = Date :: from_timestamp(time() - ($item_max_age * 86400));
Log :: debug("Handle items expiration with creation date limit ".Date :: format($limit)."."); Log :: debug("Handle items expiration with creation date limit ".Date :: format($limit).".");
$items = Db :: search_items(array('all' => true)); $items = Item :: search(array('all' => true));
$error = false; $error = false;
foreach($items['items'] as $item) { foreach($items['items'] as $item) {
if ($item['date'] < $limit) { if ($item -> date < $limit) {
if ($just_try) { if ($just_try) {
Log :: 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'], Date :: format($item['date']) $item->id, $item->name, Date :: format($item->date)
); );
} }
else if (Db :: delete_item($item['id'])) { else if ($item->delete()) {
Log :: info('Item #%s (%s) deleted (creation date: %s)', Log :: info('Item #%s (%s) deleted (creation date: %s)',
$item['id'], $item['name'], Date :: format($item['date']) $item->id, $item->name, Date :: format($item->date)
); );
} }
else { else {
Log :: 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'], Date :: format($item['date']) $item->id, $item->name, Date :: format($item->date)
); );
$error = true; $error = true;
} }
} }
else { else {
Log :: debug('Item "%s" (%s) still valid (creation date: %s)', Log :: debug('Item "%s" (%s) still valid (creation date: %s)',
$item['id'], $item['name'], Date :: format($item['date']) $item->id, $item->name, Date :: format($item->date)
); );
} }
} }

View file

@ -1,10 +1,9 @@
<?php <?php
use EesyPHP\App; use EesyPHP\App;
use EesyPHP\Db;
use EesyPHP\SentrySpan; use EesyPHP\SentrySpan;
use EesyPHPExample\Db;
use function EesyPHP\___; use function EesyPHP\___;
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED); error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED);

View file

@ -77,10 +77,10 @@ function handle_item_post_data(&$info, $enabled_fields=null, $required_fields=nu
} }
} }
if (empty($field_errors) && is_array($item) && !is_null($changes)) { if (empty($field_errors) && $item && !is_null($changes)) {
$changes = array(); $changes = array();
foreach ($info as $key => $value) { foreach ($info as $key => $value) {
if ($value != $item[$key]) if ($value != $item->$key)
$changes[$key] = $value; $changes[$key] = $value;
} }
} }
@ -113,7 +113,7 @@ function can_delete($item) {
} }
function can_do($item, $status=array()) { function can_do($item, $status=array()) {
return in_array($item['status'], $status); return in_array($item->status, $status);
} }
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab # vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab

View file

@ -44,16 +44,16 @@ function smarty_item_status($params) {
'refused' => 'danger', 'refused' => 'danger',
'archived' => 'secondary', 'archived' => 'secondary',
); );
if (array_key_exists($params['item']['status'], $status2class)) { if (array_key_exists($params['item']->status, $status2class)) {
$class = $status2class[$params['item']['status']]; $class = $status2class[$params['item']->status];
} }
else else
$class='danger'; $class='danger';
echo "<span class='badge bg-$class'>"; echo "<span class='badge bg-$class'>";
echo ( echo (
array_key_exists($params['item']['status'], $status_list)? array_key_exists($params['item']->status, $status_list)?
$status_list[$params['item']['status']]: $status_list[$params['item']->status]:
"Inconnu (".$params['item']['status'].")" "Inconnu (".$params['item']->status.")"
); );
echo "</span>"; echo "</span>";
} }

View file

@ -4,14 +4,14 @@ use EesyPHP\Check;
use EesyPHP\Log; use EesyPHP\Log;
use EesyPHP\Tpl; use EesyPHP\Tpl;
use EesyPHPExample\Db; use EesyPHPExample\Db\Item;
function get_item_from_url($id, $fatal=false) { function get_item_from_url($id, $fatal=false) {
if (!Check :: id($id)) if (!Check :: id($id))
Log :: fatal(_('Invalid element identifier.')); Log :: fatal(_('Invalid element identifier.'));
$item = Db :: get_item($id); $item = Item :: get($id);
if(!is_array($item)) { if(!$item instanceof Item) {
$error = sprintf(_("Item #%s not found."), $id); $error = sprintf(_("Item #%s not found."), $id);
if ($fatal) if ($fatal)
Log :: fatal($error); Log :: fatal($error);

View file

@ -5,7 +5,7 @@ use EesyPHP\Log;
use EesyPHP\Tpl; use EesyPHP\Tpl;
use EesyPHP\Url; use EesyPHP\Url;
use EesyPHPExample\Db; use EesyPHPExample\Db\Item;
use function EesyPHP\vardump; use function EesyPHP\vardump;
@ -28,7 +28,7 @@ function handle_search($request) {
) { ) {
$_SESSION['search']=array( $_SESSION['search']=array(
'pattern' => false, 'pattern' => false,
'status' => 'all', 'filters' => ['status' => 'all'],
'order' => 'name', 'order' => 'name',
'order_direction' => 'ASC', 'order_direction' => 'ASC',
); );
@ -40,7 +40,7 @@ function handle_search($request) {
$status_list['all'] = _('Any'); $status_list['all'] = _('Any');
if (isset($_REQUEST['status'])) { if (isset($_REQUEST['status'])) {
if (check_status($_REQUEST['status']) || $_REQUEST['status'] == 'all') if (check_status($_REQUEST['status']) || $_REQUEST['status'] == 'all')
$_SESSION['search']['status'] = $_REQUEST['status']; $_SESSION['search']['filters']['status'] = $_REQUEST['status'];
else else
Tpl :: assign('status_error', true); Tpl :: assign('status_error', true);
} }
@ -82,7 +82,7 @@ function handle_search($request) {
} }
// Nb par page // Nb par page
$nbs_by_page=array(10,25,50,100,500); $nbs_by_page = array(10,25,50,100,500);
if (isset($_REQUEST['nb_by_page']) && in_array(intval($_REQUEST['nb_by_page']),$nbs_by_page)) { if (isset($_REQUEST['nb_by_page']) && in_array(intval($_REQUEST['nb_by_page']),$nbs_by_page)) {
$_SESSION['search']['nb_by_page']=intval($_REQUEST['nb_by_page']); $_SESSION['search']['nb_by_page']=intval($_REQUEST['nb_by_page']);
$_SESSION['search']['page']=1; $_SESSION['search']['page']=1;
@ -92,7 +92,7 @@ function handle_search($request) {
} }
Log :: debug('Search params : '.vardump($_SESSION['search'])); Log :: debug('Search params : '.vardump($_SESSION['search']));
$result = Db :: search_items($_SESSION['search']); $result = Item :: search($_SESSION['search']);
if (!is_array($result)) if (!is_array($result))
Tpl :: fatal_error( Tpl :: fatal_error(
_("An error occurred while listing the items. ". _("An error occurred while listing the items. ".
@ -138,10 +138,7 @@ function handle_show($request) {
'js/myconfirm.js', 'js/myconfirm.js',
); );
Tpl :: display( Tpl :: display("show.tpl", _("Element %s"), $item->name);
"show.tpl", _("Element %s"),
(is_array($item)?$item['name']:"#".$request -> id)
);
} }
Url :: add_url_handler('|^item/(?P<id>[0-9]+)$|', 'handle_show'); Url :: add_url_handler('|^item/(?P<id>[0-9]+)$|', 'handle_show');
@ -156,10 +153,11 @@ function handle_create($request) {
$info = array(); $info = array();
$field_errors = handle_item_post_data($info); $field_errors = handle_item_post_data($info);
if (isset($_POST['submit']) && empty($field_errors)) { if (isset($_POST['submit']) && empty($field_errors)) {
$item = Db :: add_item($info); $item = new Item();
if (is_array($item)) { $item -> apply($info);
Tpl :: add_message(_("The element '%s' has been created."), $item['name']); if ($item->save()) {
Url :: redirect('item/'.$item['id']); Tpl :: add_message(_("The element '%s' has been created."), $item->name);
Url :: redirect('item/'.$item->id);
} }
Tpl :: add_error(_("An error occurred while saving this item.")); Tpl :: add_error(_("An error occurred while saving this item."));
} }
@ -182,28 +180,29 @@ function handle_modify($request) {
global $status_list; global $status_list;
$item = get_item_from_url($request -> id); $item = get_item_from_url($request -> id);
if(!is_array($item)) if(!$item)
Url :: error_404(); Url :: error_404();
if (!can_modify($item)) { if (!can_modify($item)) {
Tpl :: add_error(_('You cannot edit this item.')); Tpl :: add_error(_('You cannot edit this item.'));
Url :: redirect('item/'.$item['id']); Url :: redirect('item/'.$item->id);
} }
$info = array(); $info = array();
$field_errors = handle_item_post_data($info); $field_errors = handle_item_post_data($info);
if (isset($_POST['submit']) && empty($field_errors)) { if (isset($_POST['submit']) && empty($field_errors)) {
$changes = array(); $changes = array();
foreach ($info as $key => $value) { foreach ($info as $key => $value) {
if ($value != $item[$key]) if ($value != $item->$key)
$changes[$key] = $value; $changes[$key] = $value;
} }
Log :: debug('Changes : '.vardump($changes)); Log :: debug('Changes : '.vardump($changes));
if (empty($changes)) { if (empty($changes)) {
Tpl :: add_message(_("You have not made any changes to element '%s'."), $item['name']); Tpl :: add_message(_("You have not made any changes to element '%s'."), $item->name);
Url :: redirect('item/'.$item['id']); Url :: redirect('item/'.$item->id);
} }
if (Db :: update_item($item['id'], $changes) === true) { $item->apply($changes);
Tpl :: add_message(_("The element '%s' has been updated successfully."), $item['name']); if ($item->save()) {
Url :: redirect('item/'.$item['id']); Tpl :: add_message(_("The element '%s' has been updated successfully."), $item->name);
Url :: redirect('item/'.$item->id);
} }
Tpl :: add_error(_("An error occurred while updating this item.")); Tpl :: add_error(_("An error occurred while updating this item."));
} }
@ -215,11 +214,11 @@ function handle_modify($request) {
_("There are errors preventing this item from being saved. ". _("There are errors preventing this item from being saved. ".
"Please correct them before attempting to save your changes.")); "Please correct them before attempting to save your changes."));
Tpl :: assign('info', (!empty($info)?$info:$item)); Tpl :: assign('info', (!empty($info)?$info:$item));
Tpl :: assign('item_id', $item['id']); Tpl :: assign('item_id', $item->id);
Tpl :: assign('field_errors', $field_errors); Tpl :: assign('field_errors', $field_errors);
Tpl :: assign('status_list', $status_list); Tpl :: assign('status_list', $status_list);
Tpl :: display("form.tpl", _("Element %s: Modification"), $item['name']); Tpl :: display("form.tpl", _("Element %s: Modification"), $item->name);
} }
Url :: add_url_handler('|^item/(?P<id>[0-9]+)/modify$|', 'handle_modify'); Url :: add_url_handler('|^item/(?P<id>[0-9]+)/modify$|', 'handle_modify');
@ -230,23 +229,23 @@ Url :: add_url_handler('|^item/(?P<id>[0-9]+)/modify$|', 'handle_modify');
*/ */
function handle_archive($request) { function handle_archive($request) {
$item = get_item_from_url($request -> id); $item = get_item_from_url($request -> id);
if(!is_array($item)) { if(!$item) {
Tpl :: add_error(_("Item #%s not found."), $request -> id); Tpl :: add_error(_("Item #%s not found."), $request -> id);
Url :: redirect('item'); Url :: redirect('item');
} }
if ($item['status'] == 'archived') { if ($item->status == 'archived') {
Tpl :: add_message(_("This item is already archived.")); Tpl :: add_message(_("This item is already archived."));
} }
else if (!can_archive($item)) { else if (!can_archive($item)) {
Tpl :: add_error(_('You cannot archive this item.')); Tpl :: add_error(_('You cannot archive this item.'));
} }
else if (Db :: archive_item($item['id']) === true) { else if ($item->archive()) {
Tpl :: add_message(_("The element '%s' has been archived successfully."), $item['name']); Tpl :: add_message(_("The element '%s' has been archived successfully."), $item->name);
} }
else { else {
Tpl :: add_error(_('An error occurred while archiving this item.')); Tpl :: add_error(_('An error occurred while archiving this item.'));
} }
Url :: redirect('item/'.$item['id']); Url :: redirect('item/'.$item->id);
} }
Url :: add_url_handler('|^item/(?P<id>[0-9]+)/archive$|', 'handle_archive'); Url :: add_url_handler('|^item/(?P<id>[0-9]+)/archive$|', 'handle_archive');
@ -257,18 +256,18 @@ Url :: add_url_handler('|^item/(?P<id>[0-9]+)/archive$|', 'handle_archive');
*/ */
function handle_delete($request) { function handle_delete($request) {
$item = get_item_from_url($request -> id); $item = get_item_from_url($request -> id);
if(!is_array($item)) { if(!$item) {
Tpl :: add_error(_("Item #%s not found."), $request -> id); Tpl :: add_error(_("Item #%s not found."), $request -> id);
} }
else if (!can_delete($item)) { else if (!can_delete($item)) {
Tpl :: add_error(_('You cannot delete this item.')); Tpl :: add_error(_('You cannot delete this item.'));
} }
else if (Db :: delete_item($item['id']) === true) { else if ($item->delete()) {
Tpl :: add_message(_("The element '%s' has been deleted successfully."), $item['name']); Tpl :: add_message(_("The element '%s' has been deleted successfully."), $item->name);
} }
else { else {
Tpl :: add_error(_('An error occurred while deleting this item.')); Tpl :: add_error(_('An error occurred while deleting this item.'));
Url :: redirect('item/'.$item['id']); Url :: redirect('item/'.$item->id);
} }
Url :: redirect('item'); Url :: redirect('item');
} }

View file

@ -1,374 +0,0 @@
<?php
namespace EesyPHPExample;
use EesyPHP\App;
use EesyPHP\Hook;
use EesyPHP\Log;
use Unidecode\Unidecode;
use Exception;
class Db extends \EesyPHP\Db {
/*
* Methods to handle items
*/
static public function get_items($orderby='id', $raw_values=false) {
try {
$info = self :: get_many('item', null, null, $orderby);
if (!is_array($info))
return;
if ($raw_values)
return $info;
$items = array();
foreach ($info as $item)
$items[$item['id']] = self :: format_row_info($item, array('date'));
return $items;
}
catch (Exception $e) {
Log :: error("Error retrieving items info from database : ".$e->getMessage());
}
return false;
}
static public function get_item($id, $raw_values=false) {
try {
$info = self :: get_one('item', array('id' => $id));
if (!is_array($info))
return false;
if ($raw_values)
return $info;
return self :: format_row_info($info, array('date'));
}
catch (Exception $e) {
Log :: error("Error retrieving item #$id info from database : ".$e->getMessage());
}
return false;
}
static public function add_item($values) {
$values['date'] = self :: time2datetime(time());
try {
$result = self :: $fpdo -> insertInto('item')
-> values($values)
-> execute();
if ($result !== false) {
$item = self :: get_item($result);
Log :: info("New item #$result added");
Hook :: trigger('item_added', $item);
return $item;
}
}
catch (Exception $e) {
Log :: error("Error creating item in database : ".$e->getMessage());
}
return false;
}
static public function update_item($id, $changes) {
if (!is_array($changes))
return false;
if (empty($changes))
return true;
$item = self :: get_item($id, true);
if (!is_array($item)) return false;
if (isset($changes['date']) && $changes['date'])
$changes['date'] = self :: time2datetime($changes['date']);
try {
$result = self :: $fpdo -> update('item')
-> set($changes)
-> where('id', $id)
-> execute();
if ($result !== false) {
Log :: info("Item #$id updated");
Hook :: trigger('item_updated', $item);
return true;
}
}
catch (Exception $e) {
Log :: error("Error updating item #$id in database : ".$e->getMessage());
}
return false;
}
static public function change_item_status($id, $status) {
if (self :: update_item($id, array('status' => $status))) {
Log :: info("Status of item #$id changed to $status.");
return true;
}
return false;
}
static public function archive_item($id) {
return self :: change_item_status($id, 'archived');
}
static public function delete_item($id) {
try {
$result = self :: $fpdo -> deleteFrom('item')
-> where('id', $id)
-> execute();
if ($result !== false) {
Log :: info("Item #$id deleted");
return True;
}
}
catch (Exception $e) {
Log :: error("Error deleting item #$id from database : ".$e->getMessage());
}
return false;
}
static public function search_items($params) {
// Detect PgSQL backend
$is_pgsql = (strpos(App::get('db.dsn', '', 'string'), "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 = self :: $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) {
Log :: error('search_items() : search in DB return false');
return false;
}
$rows = $result -> fetchAll();
$items = array();
foreach ($rows as $row) {
$items[] = self :: 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 = self :: $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) {
Log :: 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 occurred searching items with params %s infos from database : ",
preg_replace("/\n[ \t]*/", " ", print_r($params, true))
);
}
return false;
}
static public function export_items($fd=null) {
if (!$fd) $fd = fopen('php://output', 'w');
fputcsv(
$fd,
array (
'id',
'name',
'date',
'status',
'description',
)
);
$items = self :: get_items();
foreach($items as $item) {
fputcsv(
$fd,
array(
$item['id'],
$item['name'],
$item['date'],
$item['status'],
$item['description'],
)
);
}
return True;
}
static public function restore_items($fd=null) {
if (!$fd) $fd = fopen('php://stdin', 'r');
try {
$result = self :: $fpdo -> deleteFrom('item')
-> execute();
if ($result === false) {
Log :: error("An unknown error occurred truncating item table in database.");
return false;
}
}
catch (Exception $e) {
Log :: 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)?self :: time2datetime($row[$i]):$row[$i]);
$values[$field] = $value;
}
$result = self :: $fpdo -> insertInto('item')
-> values($values)
-> execute();
if ($result !== false) {
$restored++;
}
else {
Log :: error("Unknown error occurred restoring item from line #$line :\n".print_r($values, true));
$error = true;
}
}
catch (Exception $e) {
Log :: error(
"Error restoring item from line #$line : ".$e->getMessage()."\n".print_r($values, true));
$error = true;
}
}
Log :: info("$restored items restored");
// Trigger hooks
Hook :: trigger('items_restored');
return !$error;
}
}

172
example/src/Db/Item.php Normal file
View file

@ -0,0 +1,172 @@
<?php
namespace EesyPHPExample\Db;
use EesyPHP\Hook;
use EesyPHP\Log;
use EesyPHP\Db\AttrBool;
use EesyPHP\Db\AttrInt;
use EesyPHP\Db\AttrStr;
use EesyPHP\Db\AttrTimestamp;
use EesyPHP\Db\DbObject;
use EesyPHP\Export\CSV;
use Unidecode\Unidecode;
/**
* Item database object
* @property int|null $id
* @property string $name
* @property \DateTime|null $date
* @property string $status
* @property string|null $description
*/
class Item extends DbObject {
protected const TABLE = 'item';
protected const PRIMARY_KEYS = ['id'];
protected const DEFAULT_ORDER = 'id';
protected const DEFAULT_ORDER_DIRECTION = 'DESC';
protected const POSSIBLE_ORDERS = ['id', 'name', 'date', 'status'];
protected static function get_schema() {
return [
'id' => new AttrInt(['autoincrement' => true]),
'name' => new AttrStr(['required' => true]),
'date' => new AttrTimestamp(['default' => 'time']),
'status' => new AttrStr(['required' => true, 'default' => 'pending']),
'description' => new AttrStr(),
];
}
/**
* Change item status in DB
* @param string $status New item status
* @return bool
*/
public function change_status($status) {
$this->status = $status;
if ($this -> save()) {
Log :: info("Status of item #$this->id changed to $status.");
return true;
}
return false;
}
/**
* Set item as archived in DB
* @return bool
*/
public function archive() {
return $this -> change_status('archived');
}
/**
* Compute WHERE clauses from word pattern
* @throws \EesyPHP\Db\DbException
* @return array
*/
public static function word_to_filters($word) {
$patterns_word = [];
// 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 (self :: DB_CLASS :: is_pgsql()) {
$word = Unidecode::unidecode($word);
$patterns_word["unaccent($field) ILIKE ?"] = "%$word%";
}
else
$patterns_word["$field LIKE ?"] = "%$word%";
}
}
return $patterns_word;
}
/**
* Search objects
* @throws \EesyPHP\Db\DbException
* @see \EesyPHP\Db\DbObject::search()
* @return array|false The search result as an array, or False in case of error.
*/
public static function search($params) {
if (
$params &&
isset($params['filters']) &&
isset($params['filters']['status']) &&
$params['filters']['status'] == 'all'
)
unset($params['filters']['status']);
return parent :: search($params);
}
/**
* Export items
* @param resource|null $fd The file pointer where to export (optional, default: php://output)
* @param array<int,array<string,mixed>>|null $items Items to export
* @param array<string,\EesyPHP\Db\Attr> $schema The object schema (optional)
* @return boolean
*/
static function export($fd=null, $items=null, $schema=null) {
$schema = $schema ? $schema : static :: get_schema();
$export = new CSV(static :: csv_export_fields($schema));
$items = $items?$items:static :: list();
if ($items === false)
return false;
$rows = [];
foreach ($items as $item) {
$row = [];
foreach($schema as $attr => $attr_type)
$row[$attr] = $item->$attr;
$rows[] = $row;
}
return $export->export($rows, $fd);
}
static function restore($fd=null, $schema=null) {
$schema = $schema ? $schema : static :: get_schema();
$import = new CSV(static :: csv_export_fields($schema));
$items = $import->load($fd);
if ($items === false) {
Log :: error("Error loading items.");
return false;
}
if (!$items) {
Log :: error("No item loaded.");
return false;
}
try {
if (self :: DB_CLASS :: truncate(self :: TABLE)) {
Log :: debug("Table %s truncated", self :: TABLE);
}
else {
Log :: error("An unknown error occurred truncating table %s in database.", self :: TABLE);
return false;
}
}
catch (\Exception $e) {
Log :: error("Error truncating item table in database : ".$e->getMessage());
return false;
}
$success = true;
foreach($items as $item) {
$obj = new Item();
$obj->apply($item);
$success = $success && $obj->save();
}
// Trigger hooks
Hook :: trigger('items_restored');
return $success;
}
}

View file

@ -7,9 +7,11 @@
<label class="col-sm-2 col-form-label required">{t}Name{/t}</label> <label class="col-sm-2 col-form-label required">{t}Name{/t}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input name="name" type="text" required <input name="name" type="text" required
value='{if array_key_exists('name', $info) && $info.name}{$info.name|escape:'quotes'}{/if}' value='{if $info->name}{$info->name|escape:'quotes'}{/if}'
class="form-control{if array_key_exists('name', $field_errors)} is-invalid{else if $submitted} is-valid{/if}"/> class="form-control{if array_key_exists('name', $field_errors)} is-invalid{else if $submitted} is-valid{/if}"/>
{if array_key_exists('name', $field_errors)}<div class="invalid-feedback">{$field_errors['name']}</div>{/if} {if array_key_exists('name', $field_errors)}
<div class="invalid-feedback">{$field_errors['name']}</div>
{/if}
</div> </div>
</div> </div>
@ -18,22 +20,30 @@
<div class="col-sm-10"> <div class="col-sm-10">
<select name="status" id="status" required <select name="status" id="status" required
class="form-select{if array_key_exists('status', $field_errors)} is-invalid{else if $submitted} is-valid{/if}"> class="form-select{if array_key_exists('status', $field_errors)} is-invalid{else if $submitted} is-valid{/if}">
{html_options options=$status_list selected=$info.status} {html_options options=$status_list selected=$info->status}
</select> </select>
{if array_key_exists('status', $field_errors)}<div class="invalid-feedback">{$field_errors['status']}</div>{/if} {if array_key_exists('status', $field_errors)}
<div class="invalid-feedback">{$field_errors['status']}</div>
{/if}
</div> </div>
</div> </div>
<div class="mb-3 row"> <div class="mb-3 row">
<label class="col-sm-2 col-form-label">{t}Description{/t}</label> <label class="col-sm-2 col-form-label">{t}Description{/t}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<textarea name="description" id="description" class="form-control{if array_key_exists('description', $field_errors)} is-invalid{else if $submitted} is-valid{/if}">{if array_key_exists('description', $info) && $info.description}{$info.description|escape:"htmlall"}{/if}</textarea> <textarea name="description" id="description"
{if array_key_exists('description', $field_errors)}<div class="form-error">{$field_errors['description']}</div>{/if} class="form-control{if array_key_exists('description', $field_errors)} is-invalid{else if $submitted} is-valid{/if}"
>{if $info->description}{$info->description|escape:"htmlall"}{/if}</textarea>
{if array_key_exists('description', $field_errors)}
<div class="form-error">{$field_errors['description']}</div>
{/if}
</div> </div>
</div> </div>
<div class="center"> <div class="center">
<a href="{if isset($item_id)}item/{$item_id}{else}search{/if}" class="btn btn-light"><i class="fa fa-undo"></i> {t}Back{/t}</a> <a href="{if isset($item_id)}item/{$item_id}{else}search{/if}" class="btn btn-light">
<i class="fa fa-undo"></i> {t}Back{/t}
</a>
<button type="submit" name="submit" value="submitted" class="btn btn-primary"> <button type="submit" name="submit" value="submitted" class="btn btn-primary">
{if isset($item_id)} {if isset($item_id)}
<i class="fa fa-floppy-o" aria-hidden="true"></i> {t}Save{/t} <i class="fa fa-floppy-o" aria-hidden="true"></i> {t}Save{/t}

View file

@ -13,8 +13,9 @@
<div class="col"> <div class="col">
<div class="input-group mb-3"> <div class="input-group mb-3">
<label class="input-group-text" for="status">{t}Status{/t}</label> <label class="input-group-text" for="status">{t}Status{/t}</label>
<select name="status" id="status" class="form-select{if isset($status_error) && $status_error} is-invalid{/if}"> <select name="status" id="status"
{html_options options=$status_list selected=$search.status} class="form-select{if isset($status_error) && $status_error} is-invalid{/if}">
{html_options options=$status_list selected=$search.filters.status}
</select> </select>
</div> </div>
</div> </div>
@ -31,8 +32,12 @@
<div class="col-md-3"> <div class="col-md-3">
<div class="container"> <div class="container">
<div class="btn-group" role="group" aria-label="Actions"> <div class="btn-group" role="group" aria-label="Actions">
<button type="submit" name="submit" class="btn btn-primary"><i class="fa fa-search"></i> {t}Search{/t}</button> <button type="submit" name="submit" class="btn btn-primary">
<a href="{$request->current_url}?clear=true" class="btn btn-warning"><i class="fa fa-trash"></i> {t}Reset{/t}</a> <i class="fa fa-search"></i> {t}Search{/t}
</button>
<a href="{$request->current_url}?clear=true" class="btn btn-warning">
<i class="fa fa-trash"></i> {t}Reset{/t}
</a>
</div> </div>
</div> </div>
</div> </div>
@ -46,8 +51,12 @@
<table class="table" style='margin-top: 1em;'> <table class="table" style='margin-top: 1em;'>
<thead> <thead>
<tr> <tr>
<th scope="col">{table_ordered_th url="{$request->current_url}" order="date" text="{t}Date{/t}" search=$search}</th> <th scope="col">
<th scope="col">{table_ordered_th url="{$request->current_url}" order="name" text="{t}Name{/t}" search=$search}</th> {table_ordered_th url="{$request->current_url}" order="date" text="{t}Date{/t}" search=$search}
</th>
<th scope="col">
{table_ordered_th url="{$request->current_url}" order="name" text="{t}Name{/t}" search=$search}
</th>
<th scope="col">{t}Status{/t}</th> <th scope="col">{t}Status{/t}</th>
<th scope="col" class="center">{t}Actions{/t}</th> <th scope="col" class="center">{t}Actions{/t}</th>
</tr> </tr>
@ -55,14 +64,36 @@
<tbody> <tbody>
{foreach $result.items as $item} {foreach $result.items as $item}
<tr> <tr>
<td>{format_time time=$item.date}</td> <td>{format_time time=$item->date}</td>
<td><a href="item/{$item.id}"><span title='{$item.id|escape:'quotes'}'>{$item.name}</span></a></td> <td>
<a href="item/{$item->id}">
<span title='{$item->id|escape:'quotes'}'>{$item->name}</span>
</a>
</td>
<td>{item_status item=$item}</td> <td>{item_status item=$item}</td>
<td class="center"> <td class="center">
<a href="item/{$item.id}" class="btn btn-sm btn-info"><i class="fa fa-eye"></i> {t}View{/t}</a> <a href="item/{$item->id}" class="btn btn-sm btn-info">
{if can_modify($item)}<a href="item/{$item.id}/modify" class="btn btn-sm btn-primary"><i class="fa fa-edit"></i> {t}Modify{/t}</a>{/if} <i class="fa fa-eye"></i> {t}View{/t}
{if can_archive($item)}<a data-myconfirm-url="item/{$item.id}/archive" data-myconfirm-question="{t}Are you sure you want to archive this item?{/t}" class="btn btn-sm btn-warning myconfirm-link"><i class="fa fa-archive"></i> {t}Archive{/t}</a>{/if} </a>
{if can_delete($item)}<a href="#" data-myconfirm-url="item/{$item.id}/delete" data-myconfirm-question="{t}Are you sure you want to delete this item?{/t}" class="btn btn-sm btn-danger myconfirm-link"><i class="fa fa-trash"></i> {t}Delete{/t}</a>{/if} {if can_modify($item)}
<a href="item/{$item->id}/modify" class="btn btn-sm btn-primary">
<i class="fa fa-edit"></i> {t}Modify{/t}
</a>
{/if}
{if can_archive($item)}
<a data-myconfirm-url="item/{$item->id}/archive"
data-myconfirm-question="{t}Are you sure you want to archive this item?{/t}"
class="btn btn-sm btn-warning myconfirm-link">
<i class="fa fa-archive"></i> {t}Archive{/t}
</a>
{/if}
{if can_delete($item)}
<a href="#" data-myconfirm-url="item/{$item->id}/delete"
data-myconfirm-question="{t}Are you sure you want to delete this item?{/t}"
class="btn btn-sm btn-danger myconfirm-link">
<i class="fa fa-trash"></i> {t}Delete{/t}
</a>
{/if}
</td> </td>
</tr> </tr>

View file

@ -5,7 +5,7 @@
<label class="col-sm-4 col-form-label">{t}Name{/t}</label> <label class="col-sm-4 col-form-label">{t}Name{/t}</label>
<div class="col-sm-8"> <div class="col-sm-8">
<span class="form-control-plaintext"> <span class="form-control-plaintext">
{$item.name} {$item->name}
</span> </span>
</div> </div>
</div> </div>
@ -14,7 +14,7 @@
<label class="col-sm-4 col-form-label">{t}Creation date{/t}</label> <label class="col-sm-4 col-form-label">{t}Creation date{/t}</label>
<div class="col-sm-8"> <div class="col-sm-8">
<span class="form-control-plaintext"> <span class="form-control-plaintext">
{format_time time=$item.date} {format_time time=$item->date}
</span> </span>
</div> </div>
</div> </div>
@ -32,7 +32,7 @@
<label class="col-sm-4 col-form-label">{t}Description{/t}</label> <label class="col-sm-4 col-form-label">{t}Description{/t}</label>
<div class="col-sm-8"> <div class="col-sm-8">
<span class="form-control-plaintext"> <span class="form-control-plaintext">
{if $item.description}{$item.description|escape:'htmlall'}{else}{t}Unspecified.{/t}{/if} {if $item->description}{$item->description|escape:'htmlall'}{else}{t}Unspecified.{/t}{/if}
</span> </span>
</div> </div>
</div> </div>
@ -40,8 +40,8 @@
<div class="center"> <div class="center">
<a href="item" class="btn btn-light"><i class="fa fa-undo"></i> {t}Back{/t}</a> <a href="item" class="btn btn-light"><i class="fa fa-undo"></i> {t}Back{/t}</a>
{if can_modify($item)}<a href="item/{$item.id}/modify" class="btn btn-primary"><i class="fa fa-edit"></i> {t}Modify{/t}</a>{/if} {if can_modify($item)}<a href="item/{$item->id}/modify" class="btn btn-primary"><i class="fa fa-edit"></i> {t}Modify{/t}</a>{/if}
{if can_archive($item)}<a data-myconfirm-url="item/{$item.id}/archive" data-myconfirm-question="{t}Are you sure you want to archive this item?{/t}" class="btn btn-warning myconfirm-link"><i class="fa fa-archive"></i> {t}Archive{/t}</a>{/if} {if can_archive($item)}<a data-myconfirm-url="item/{$item->id}/archive" data-myconfirm-question="{t}Are you sure you want to archive this item?{/t}" class="btn btn-warning myconfirm-link"><i class="fa fa-archive"></i> {t}Archive{/t}</a>{/if}
{if can_delete($item)}<a href="#" data-myconfirm-url="item/{$item.id}/delete" data-myconfirm-question="{t}Are you sure you want to delete this item?{/t}" class="btn btn-danger myconfirm-link"><i class="fa fa-trash"></i> {t}Delete{/t}</a>{/if} {if can_delete($item)}<a href="#" data-myconfirm-url="item/{$item->id}/delete" data-myconfirm-question="{t}Are you sure you want to delete this item?{/t}" class="btn btn-danger myconfirm-link"><i class="fa fa-trash"></i> {t}Delete{/t}</a>{/if}
</div> </div>
{/block} {/block}

View file

@ -32,8 +32,3 @@ parameters:
- -
message: "#Call to static method isError\\(\\) on an unknown class PEAR\\.#" message: "#Call to static method isError\\(\\) on an unknown class PEAR\\.#"
path: src/Email.php path: src/Email.php
-
message: "#Variable \\$status_list might not be defined\\.#"
paths:
- example/includes/cli.php