diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f727290..3d78451 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,8 +22,14 @@ repos: rev: 1.4.0 hooks: - id: php-stan - files: \.(php)$ + files: ^(?!example/).*\.(php)$ args: ["--configuration=phpstan.neon"] + - repo: https://github.com/digitalpulp/pre-commit-php.git + rev: 1.4.0 + hooks: + - id: php-stan + files: ^example/.*\.(php)$ + args: ["--configuration=example/phpstan.neon"] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..c2bf663 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,3 @@ +# Exclude composer installed libs +/vendor +/composer.lock diff --git a/example/composer.json b/example/composer.json new file mode 100644 index 0000000..fa0f244 --- /dev/null +++ b/example/composer.json @@ -0,0 +1,35 @@ +{ + "name": "brenard/eesyphpexample", + "description": "Example app using EesyPHP framework", + "type": "project", + "license": "GPL-3.0-or-later", + "autoload": { + "psr-4": { + "EesyPHPExample\\": "src/" + } + }, + "authors": [ + { + "name": "Benjamin Renard", + "email": "brenard@easter-eggs.com" + } + ], + "minimum-stability": "dev", + "repositories": [ + { + "type": "path", + "url": "../" + } + ], + "require": { + "brenard/eesyphp": "@dev" + }, + "config": { + "allow-plugins": { + "php-http/discovery": true + } + }, + "require-dev": { + "phpstan/phpstan": "2.0.x-dev" + } +} diff --git a/example/includes/cli.php b/example/includes/cli.php index 4779dff..8d2a281 100644 --- a/example/includes/cli.php +++ b/example/includes/cli.php @@ -5,6 +5,8 @@ use EesyPHP\Cli; use EesyPHP\Date; use EesyPHP\Log; +use EesyPHPExample\Db; + use function EesyPHP\___; /* @@ -75,7 +77,7 @@ function cli_list($command_args) { if (!empty($patterns)) $params['pattern'] = implode(' ', $patterns); - $items = search_items($params); + $items = Db :: search_items($params); if (!is_array($items)) { Log :: error("Invalid DB info return.\n"); return False; @@ -130,7 +132,7 @@ function cli_show($command_args) { Cli :: usage(_('You must provide a valid ID.')); $item_id = $command_args[0]; - $item = get_item($item_id); + $item = Db :: get_item($item_id); if (!$item) Log :: fatal(_("Item #%s not found."), $item_id); @@ -155,7 +157,7 @@ function cli_delete($command_args) { // Check exist $item_id = $command_args[0]; - $item = get_item($item_id); + $item = Db :: get_item($item_id); if (!$item) Log :: fatal(_("Item #%s not found."), $item_id); @@ -171,7 +173,7 @@ function cli_delete($command_args) { } echo "\n"; - if (!delete_item($item['id'])) + if (!Db :: delete_item($item['id'])) Log :: fatal(_("An error occurred deleting item #%d."), $item_id); return True; @@ -185,7 +187,7 @@ Cli :: add_command( function cli_export($command_args) { $fd = fopen((count($command_args) >= 1?$command_args[0]:'php://output'), 'w'); - export_items($fd); + Db :: export_items($fd); fclose($fd); Log :: info("Items export to '".(count($command_args) >= 1?$command_args[0]:'STDOUT')."'."); } @@ -198,7 +200,7 @@ Cli :: add_command( function cli_restore($command_args) { $fd = fopen((count($command_args) >= 1?$command_args[0]:'php://stdin'), 'r'); - restore_items($fd); + Db :: restore_items($fd); fclose($fd); Log :: info( "Items restored from '%s'", @@ -244,7 +246,7 @@ function cli_cron($command_args) { $limit = time() - ($item_max_age * 86400); Log :: debug("Handle items expiration with creation date limit ".Date :: format($limit)."."); - $items = search_items(array('all' => true)); + $items = Db :: search_items(array('all' => true)); $error = false; foreach($items['items'] as $item) { if ($item['date'] < $limit) { @@ -253,7 +255,7 @@ function cli_cron($command_args) { $item['id'], $item['name'], Date :: format($item['date']) ); } - else if (delete_item($item['id'])) { + else if (Db :: delete_item($item['id'])) { Log :: info('Item #%s (%s) deleted (creation date: %s)', $item['id'], $item['name'], Date :: format($item['date']) ); diff --git a/example/includes/core.php b/example/includes/core.php index 8843155..364f730 100644 --- a/example/includes/core.php +++ b/example/includes/core.php @@ -3,13 +3,15 @@ use EesyPHP\App; use EesyPHP\SentrySpan; +use EesyPHPExample\Db; + use function EesyPHP\___; error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED); // Root directory path $script = null; -if (defined('__FILE__') && constant('__FILE__')) { // @phpstan-ignore-line +if (defined('__FILE__') && constant('__FILE__')) { $script = __FILE__; } else { @@ -69,7 +71,8 @@ foreach($status_list as $key => $value) require_once('cli.php'); require_once('templates.php'); require_once('url-helpers.php'); -require_once('db.php'); + +Db :: init(); $sentry_span->finish(); diff --git a/example/includes/db.php b/example/includes/db.php deleted file mode 100644 index 1fddadb..0000000 --- a/example/includes/db.php +++ /dev/null @@ -1,390 +0,0 @@ - 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']] = $db -> format_row_info($item, array('date')); - return $items; - } - catch (Exception $e) { - Log :: error("Error retrieving items info from database : ".$e->getMessage()); - } - return false; -} - -function get_item($id, $raw_values=false) { - global $db; - try { - $info = $db -> get_one('item', array('id' => $id)); - - if (!is_array($info)) - return false; - - if ($raw_values) - return $info; - - return $db -> format_row_info($info, array('date')); - } - catch (Exception $e) { - Log :: error("Error retrieving item #$id info from database : ".$e->getMessage()); - } - return false; -} - -function add_item($values) { - global $db; - $values['date'] = $db -> time2datetime(time()); - try { - $result = $db -> fpdo -> insertInto('item') - -> values($values) - -> execute(); - - if ($result !== false) { - $item = 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; -} - -function update_item($id, $changes) { - global $db; - - 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']) && $changes['date']) - $changes['date'] = $db -> time2datetime($changes['date']); - - try { - $result = $db -> 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; -} - -function change_item_status($id, $status) { - if (update_item($id, array('status' => $status))) { - Log :: 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 $db; - try { - $result = $db -> 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; -} - -function search_items($params) { - global $db; - - // 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 = $db -> 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[] = $db -> 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 = $db -> 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; -} - -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 $db; - if (!$fd) $fd = fopen('php://stdin', 'r'); - try { - $result = $db -> 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)?$db -> time2datetime($row[$i]):$row[$i]); - $values[$field] = $value; - } - $result = $db -> 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; -} - -# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab diff --git a/example/includes/url-helpers.php b/example/includes/url-helpers.php index 1b19d75..67ef0bd 100644 --- a/example/includes/url-helpers.php +++ b/example/includes/url-helpers.php @@ -4,11 +4,13 @@ use EesyPHP\Check; use EesyPHP\Log; use EesyPHP\Tpl; +use EesyPHPExample\Db; + function get_item_from_url($id, $fatal=false) { if (!Check :: id($id)) Log :: fatal(_('Invalid element identifier.')); - $item = get_item($id); + $item = Db :: get_item($id); if(!is_array($item)) { $error = sprintf(_("Item #%s not found."), $id); if ($fatal) diff --git a/example/includes/url-public.php b/example/includes/url-public.php index a3a496f..806226d 100644 --- a/example/includes/url-public.php +++ b/example/includes/url-public.php @@ -5,6 +5,8 @@ use EesyPHP\Log; use EesyPHP\Tpl; use EesyPHP\Url; +use EesyPHPExample\Db; + use function EesyPHP\vardump; if (php_sapi_name() == "cli") @@ -90,7 +92,7 @@ function handle_search($request) { } Log :: debug('Search params : '.vardump($_SESSION['search'])); - $result = search_items($_SESSION['search']); + $result = Db :: search_items($_SESSION['search']); if (!is_array($result)) Tpl :: fatal_error( _("An error occurred while listing the items. ". @@ -154,7 +156,7 @@ function handle_create($request) { $info = array(); $field_errors = handle_item_post_data($info); if (isset($_POST['submit']) && empty($field_errors)) { - $item = add_item($info); + $item = Db :: add_item($info); if (is_array($item)) { Tpl :: add_message(_("The element '%s' has been created."), $item['name']); Url :: redirect('item/'.$item['id']); @@ -199,7 +201,7 @@ function handle_modify($request) { Tpl :: add_message(_("You have not made any changes to element '%s'."), $item['name']); Url :: redirect('item/'.$item['id']); } - if (update_item($item['id'], $changes) === true) { + if (Db :: update_item($item['id'], $changes) === true) { Tpl :: add_message(_("The element '%s' has been updated successfully."), $item['name']); Url :: redirect('item/'.$item['id']); } @@ -238,7 +240,7 @@ function handle_archive($request) { else if (!can_archive($item)) { Tpl :: add_error(_('You cannot archive this item.')); } - else if (archive_item($item['id']) === true) { + else if (Db :: archive_item($item['id']) === true) { Tpl :: add_message(_("The element '%s' has been archived successfully."), $item['name']); } else { @@ -261,7 +263,7 @@ function handle_delete($request) { else if (!can_delete($item)) { Tpl :: add_error(_('You cannot delete this item.')); } - else if (delete_item($item['id']) === true) { + else if (Db :: delete_item($item['id']) === true) { Tpl :: add_message(_("The element '%s' has been deleted successfully."), $item['name']); } else { diff --git a/example/phpstan.neon b/example/phpstan.neon new file mode 100644 index 0000000..eeed8e1 --- /dev/null +++ b/example/phpstan.neon @@ -0,0 +1,17 @@ +parameters: + level: 5 + paths: + - src + - includes + - public_html + universalObjectCratesClasses: + - EesyPHP\HookEvent + - EesyPHP\UrlRequest + - EesyPHP\Auth\User + treatPhpDocTypesAsCertain: false + ignoreErrors: + - + message: "#Variable \\$status_list might not be defined\\.#" + paths: + - includes/cli.php + diff --git a/example/src/Db.php b/example/src/Db.php new file mode 100644 index 0000000..80d3efc --- /dev/null +++ b/example/src/Db.php @@ -0,0 +1,374 @@ +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; + } +} diff --git a/example/vendor b/example/vendor deleted file mode 120000 index 9c39cc9..0000000 --- a/example/vendor +++ /dev/null @@ -1 +0,0 @@ -../vendor \ No newline at end of file diff --git a/phpstan.neon b/phpstan.neon index 8fbe194..986038e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,11 +2,6 @@ parameters: level: 5 paths: - src - - example/includes - - example/public_html - excludePaths: - - example/includes/config.local.php - - example/data/tmp/templates_c universalObjectCratesClasses: - EesyPHP\HookEvent - EesyPHP\UrlRequest diff --git a/src/App.php b/src/App.php index f6768e8..3fe0fa8 100644 --- a/src/App.php +++ b/src/App.php @@ -54,6 +54,7 @@ class App { 'mail.enabled' => true, 'i18n.enabled' => true, 'cli.enabled' => true, + 'db.enabled' => false, ) ); @@ -88,6 +89,8 @@ class App { I18n::init(); if (self :: get('cli.enabled', null, 'bool')) Cli::init(); + if (self :: get('db.enabled', null, 'bool')) + Db::init(); // Define common upload_tmp_dir & upload_max_filesize PHP ini if (self::isset('upload_tmp_directory')) diff --git a/src/Auth/Db.php b/src/Auth/Db.php index aaa8a5a..a439385 100644 --- a/src/Auth/Db.php +++ b/src/Auth/Db.php @@ -3,7 +3,6 @@ namespace EesyPHP\Auth; use EesyPHP\App; -use EesyPHP\Db as DbConnection; use EesyPHP\Log; use Exception; @@ -12,33 +11,9 @@ class Db extends Backend { /** * Database connection object - * @var \EesyPHP\Db|null + * @var class-string|null */ - private static $db = null; - - /** - * Database connection parameters - */ - - /** - * @var string - */ - private static $dsn; - - /** - * @var string|null - */ - private static $user; - - /** - * @var string|null - */ - private static $password; - - /** - * @var array - */ - private static $options; + private static $class = null; /** * Users table name @@ -73,24 +48,21 @@ class Db extends Backend { App :: set_default( 'auth.db', array( - 'dsn' => null, - 'user' => null, - 'password' => null, - 'options' => array(), + 'class' => '\\EesyPHP\\Db', 'users_table' => 'users', 'username_field' => 'username', 'password_field' => 'password', 'exposed_fields' => array('name', 'mail'), ) ); - self :: $dsn = App::get('auth.db.dsn', null, 'string'); - if (!self :: $dsn) { - Log :: warning('No database DSN configured, can not initialize this authentication backend'); + self :: $class = App::get('auth.db.class', null, 'string'); + if (!self :: $class || !class_exists(self :: $class)) { + Log :: warning( + 'Database class %s configured as authentication backend does not exists, can not '. + 'initialize this authentication backend.', self :: $class + ); return false; } - self :: $user = App::get('auth.db.user', null, 'string'); - self :: $password = App::get('auth.db.password', null, 'string'); - self :: $options = App::get('auth.db.options', null, 'array'); self :: $users_table = App::get('auth.db.users_table', null, 'string'); self :: $username_field = App::get('auth.db.username_field', null, 'string'); self :: $password_field = App::get('auth.db.password_field', null, 'string'); @@ -103,10 +75,7 @@ class Db extends Backend { * @return void */ private static function connect() { - if (!self :: $db) - self :: $db = new DbConnection( - self :: $dsn, self :: $user, self :: $password, self :: $options - ); + self :: $class :: connect(); } /** @@ -117,7 +86,7 @@ class Db extends Backend { public static function get_user($username) { self :: connect(); try { - $query = self :: $db -> fpdo -> from(self :: $users_table) + $query = self :: $class :: $fpdo -> from(self :: $users_table) -> select(null) -> select(self :: $exposed_fields) -> where(self :: $username_field, $username); @@ -143,7 +112,7 @@ class Db extends Backend { public static function check_password($user, $password) { self :: connect(); try { - $query = self :: $db -> fpdo -> from(self :: $users_table) + $query = self :: $class :: $fpdo -> from(self :: $users_table) -> select(null) -> select(self :: $password_field) -> where(self :: $username_field, $user->username); diff --git a/src/Db.php b/src/Db.php index 81fb330..eebe19e 100644 --- a/src/Db.php +++ b/src/Db.php @@ -9,34 +9,22 @@ use \Envms\FluentPDO\Query; class Db { /** - * The PDO object of the database connection - * @var PDO + * Configuration prefix + * @var string */ - public $pdo; + protected static $config_prefix = 'db'; /** * The PDO object of the database connection - * @var \Envms\FluentPDO\Query + * @var PDO|null */ - public $fpdo; + public static $pdo = null; /** - * Date format as returned by database - * @var string + * The PDO object of the database connection + * @var \Envms\FluentPDO\Query|null */ - protected $date_format = '%Y-%m-%d'; - - /** - * Datetime format as returned by database - * @var string - */ - protected $datetime_format = '%Y-%m-%d %H:%M:%S'; - - /** - * Locale for time (as expected by LC_TIME) - * @var string|null - */ - protected $locale_time = null; + public static $fpdo = null; /** * Keep trace of total queries times (in ns) @@ -45,31 +33,58 @@ class Db { public static $total_query_time = 0; /** - * Connect to database and return FluentPDO Query object - * @param string $dsn Database DSN - * @param string|null $user Username (optional) - * @param string|null $password password (optional) - * @param string|array $options Connection options (optional) - * @param string|null $date_format Date format in DB (optional) - * @param string|null $datetime_format Datetime format in DB (optional) - * @param string|null $locale_time Locale for time (optional) + * Initialization * @return void */ - public function __construct($dsn, $user=null, $password=null, $options=null, - $date_format=null, $datetime_format=null, $locale_time=null) { - if (!$dsn) - Log :: fatal('Database DSN not configured'); - if ($date_format) $this -> date_format = $date_format; - if ($datetime_format) $this -> datetime_format = $datetime_format; - if ($locale_time) $this -> locale_time = $locale_time; + public static function init() { + // In phpstan context, do not initialize + // @phpstan-ignore-next-line + if (defined('__PHPSTAN_RUNNING__') && constant('__PHPSTAN_RUNNING__')) + return; + // Set config default values + App :: set_default( + static :: $config_prefix, + array( + 'dsn' => null, + 'user' => null, + 'password' => null, + 'options' => [], + 'date_format' => '%Y-%m-%d', + 'datetime_format' => '%Y-%m-%d %H:%M:%S', + 'locale_time' => null, + 'auto_connect' => true, + ), + ); + + if (!App::get(static :: $config_prefix.".dsn")) + Log :: fatal('Database DSN not configured (%s.dsn)', static :: $config_prefix); + + if (App::get(static :: $config_prefix.".auto_connect")) + self :: connect(); + + } + + /** + * Connection + * @return void + */ + public static function connect() { + if (self :: $pdo && self :: $fpdo) + return; + $dsn = App::get(static :: $config_prefix.".dsn"); try { // Connect to database - $this -> pdo = new PDO($dsn, $user, $password, $options); - $this -> fpdo = new Query($this -> pdo); + self :: $pdo = new PDO( + $dsn, + App::get(static :: $config_prefix.".user"), + App::get(static :: $config_prefix.".password"), + App::get(static :: $config_prefix.".options"), + ); + self :: $fpdo = new Query(self :: $pdo); // Register the debug query handler to log it - $this -> fpdo -> debug = array(self :: class, 'debug_query'); + self :: $fpdo -> debug = array(self :: class, 'debug_query'); Log :: trace("DB connection established (DSN: '%s')", $dsn); } @@ -119,8 +134,8 @@ class Db { * @see https://www.php.net/manual/en/pdo.setattribute.php * @return void */ - public function set_autocommit($value) { - $this -> pdo -> setAttribute(PDO::ATTR_AUTOCOMMIT, $value); + public static function set_autocommit($value) { + self :: $pdo -> setAttribute(PDO::ATTR_AUTOCOMMIT, $value); } /* @@ -136,9 +151,9 @@ class Db { * @param array|array{0: 'LEFT'|'RIGHT'|'INNER'|'OUTER'|'FULL', 1: string, 2: string} $joins Join specification as array (see apply_joins()) * @return array|false */ - public function get_one($table, $where, $fields=null, $joins=null) { + public static function get_one($table, $where, $fields=null, $joins=null) { try { - $query = $this -> fpdo -> from($table) -> where($where); + $query = self :: $fpdo -> from($table) -> where($where); if ($joins) self :: apply_joins($query, $joins); if ($fields) @@ -169,11 +184,11 @@ class Db { * @param array|array{0: 'LEFT'|'RIGHT'|'INNER'|'OUTER'|'FULL', 1: string, 2: string} $joins Join specification as array (see apply_joins()) * @return array|false */ - public function get_many( + public static function get_many( $table, $where=null, $fields=null, $order_by=null, $limit=null, $joins=null ) { try { - $query = $this -> fpdo -> from($table); + $query = self :: $fpdo -> from($table); if ($joins) self :: apply_joins($query, $joins); if ($fields) @@ -204,9 +219,9 @@ class Db { * @param array|array{0: 'LEFT'|'RIGHT'|'INNER'|'OUTER'|'FULL', 1: string, 2: string} $joins Join specification as array (see apply_joins()) * @return int|false */ - public function count($table, $where, $what=null, $joins=null) { + public static function count($table, $where, $what=null, $joins=null) { try { - $query = $this -> fpdo -> from($table) -> where($where); + $query = self :: $fpdo -> from($table) -> where($where); if ($joins) self :: apply_joins($query, $joins); $query -> select(null) -> select(sprintf("COUNT(%s) as count", $what?$what:"*")); @@ -274,7 +289,7 @@ class Db { * @param array>|null $joins Join specification as array (see apply_joins()) * @return void */ - private function _log_simple_select_query_error( + private static function _log_simple_select_query_error( $multiple, $table, $e, $where=null, $fields=null, $order_by=null, $limit=null, $joins=null ) { $msg = "Error occurred getting %s of the table %s"; @@ -314,9 +329,9 @@ class Db { * @param boolean $want_id Set to true if you want to retrieve the ID of the inserted row * @return bool|int The ID of the inserted row if $want_id, or true/false in case of success/error */ - public function insert($table, $values, $want_id=false) { + public static function insert($table, $values, $want_id=false) { try { - $id = $this -> fpdo -> insertInto($table) + $id = self :: $fpdo -> insertInto($table) -> values($values) -> execute(); } @@ -346,10 +361,10 @@ class Db { * default: null == 1) * @return bool */ - public function update($table, $changes, $where, $expected_row_changes=null) { + public static function update($table, $changes, $where, $expected_row_changes=null) { if (is_null($expected_row_changes)) $expected_row_changes = 1; try { - $result = $this -> fpdo -> update($table) + $result = self :: $fpdo -> update($table) -> set($changes) -> where($where) -> execute(); @@ -383,10 +398,10 @@ class Db { * default: null == 1) * @return bool */ - public function delete($table, $where, $expected_row_changes=null) { + public static function delete($table, $where, $expected_row_changes=null) { if (is_null($expected_row_changes)) $expected_row_changes = 1; try { - $result = $this -> fpdo -> deleteFrom($table) + $result = self :: $fpdo -> deleteFrom($table) -> where($where) -> execute(); } @@ -416,37 +431,37 @@ class Db { /* * Handle date/datetime format */ - public function set_locale() { - if ($this -> locale_time) - setlocale(LC_TIME, $this -> locale_time); + public static function set_locale() { + if (App::get(static :: $config_prefix.".locale_time")) + setlocale(LC_TIME, App::get(static :: $config_prefix.".locale_time")); } - public function date2time($date) { - $this -> set_locale(); - $pdate = strptime($date, $this -> date_format); + public static function date2time($date) { + self :: set_locale(); + $pdate = strptime($date, App::get(static :: $config_prefix.".date_format")); return mktime( $pdate['tm_hour'], $pdate['tm_min'], $pdate['tm_sec'], $pdate['tm_mon'] + 1, $pdate['tm_mday'], $pdate['tm_year'] + 1900 ); } - public function time2date($time) { - $this -> set_locale(); - return strftime($this -> date_format, $time); + public static function time2date($time) { + self :: set_locale(); + return strftime(App::get(static :: $config_prefix.".date_format"), $time); } - public function datetime2time($date) { - $this -> set_locale(); - $pdate = strptime($date, $this -> datetime_format); + public static function datetime2time($date) { + self :: set_locale(); + $pdate = strptime($date, App::get(static :: $config_prefix.".datetime_format")); return mktime( $pdate['tm_hour'], $pdate['tm_min'], $pdate['tm_sec'], $pdate['tm_mon'] + 1, $pdate['tm_mday'], $pdate['tm_year'] + 1900 ); } - public function time2datetime($time) { - $this -> set_locale(); - return strftime($this -> datetime_format, $time); + public static function time2datetime($time) { + self :: set_locale(); + return strftime(App::get(static :: $config_prefix.".datetime_format"), $time); } /** @@ -456,17 +471,17 @@ class Db { * @param array $date_fields List of field in date format * @return array */ - public function format_row_info($row, $datetime_fields=null, $date_fields=null) { + public static function format_row_info($row, $datetime_fields=null, $date_fields=null) { // Convert datetime fields if (is_array($datetime_fields)) foreach($datetime_fields as $field) if ($row[$field]) - $row[$field] = $this -> datetime2time($row[$field]); + $row[$field] = self :: datetime2time($row[$field]); // Convert date fields if (is_array($date_fields)) foreach($date_fields as $field) if ($row[$field]) - $row[$field] = $this -> date2time($row[$field]); + $row[$field] = self :: date2time($row[$field]); return $row; }