From e57289eeb6408a73bf05c292a606995e694962fd Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Thu, 19 Dec 2024 11:56:00 +0100 Subject: [PATCH 01/11] GenericTable: drop support for get_count_params, get_data_params --- models/GenericTable.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/models/GenericTable.php b/models/GenericTable.php index 415ef53..a5535c7 100644 --- a/models/GenericTable.php +++ b/models/GenericTable.php @@ -41,7 +41,7 @@ class GenericTable $this->tableIsSortable = !empty($options['base_url']); // How much data do we have? - $this->recordCount = $options['get_count'](...(!empty($options['get_count_params']) ? $options['get_count_params'] : [])); + $this->recordCount = $options['get_count'](); // How much data do we need to retrieve? $this->items_per_page = !empty($options['items_per_page']) ? $options['items_per_page'] : 30; @@ -58,8 +58,6 @@ class GenericTable // Gather parameters for the data gather function first. $parameters = [$this->start, $this->items_per_page, $options['sort_order'], $options['sort_direction']]; - if (!empty($options['get_data_params']) && is_array($options['get_data_params'])) - $parameters = array_merge($parameters, $options['get_data_params']); // Okay, let's fetch the data! $data = $options['get_data'](...$parameters); -- 2.46.0 From 06c95853f5b9088e42f9f5d62eb9234eafd784e5 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Thu, 19 Dec 2024 12:01:00 +0100 Subject: [PATCH 02/11] GenericTable: drop $tableIsSortable property --- models/GenericTable.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/models/GenericTable.php b/models/GenericTable.php index a5535c7..b63df32 100644 --- a/models/GenericTable.php +++ b/models/GenericTable.php @@ -15,7 +15,6 @@ class GenericTable private $title; private $title_class; - private $tableIsSortable = false; public $form_above; public $form_below; @@ -37,9 +36,6 @@ class GenericTable if (!empty($options['sort_direction']) && !in_array($options['sort_direction'], ['up', 'down'])) $options['sort_direction'] = 'up'; - // Make sure we know whether we can actually sort on something. - $this->tableIsSortable = !empty($options['base_url']); - // How much data do we have? $this->recordCount = $options['get_count'](); @@ -100,7 +96,7 @@ class GenericTable if (empty($column['header'])) continue; - $isSortable = $this->tableIsSortable && !empty($column['is_sortable']); + $isSortable = !empty($column['is_sortable']); $sortDirection = $key == $this->sort_order && $this->sort_direction === 'up' ? 'down' : 'up'; $header = [ -- 2.46.0 From bb8a8bad272b0e5f369c13ae48c25ca035dad639 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Thu, 19 Dec 2024 15:00:00 +0100 Subject: [PATCH 03/11] GenericTable: refactor order and pagination initalisation --- controllers/ManageAlbums.php | 24 ++++------- controllers/ManageAssets.php | 21 ++++------ controllers/ManageErrors.php | 29 ++++++------- controllers/ManageTags.php | 23 ++++------- controllers/ManageUsers.php | 27 ++++-------- models/GenericTable.php | 79 ++++++++++++++++++------------------ 6 files changed, 85 insertions(+), 118 deletions(-) diff --git a/controllers/ManageAlbums.php b/controllers/ManageAlbums.php index 662714b..aa48f20 100644 --- a/controllers/ManageAlbums.php +++ b/controllers/ManageAlbums.php @@ -54,27 +54,19 @@ class ManageAlbums extends HTMLController 'value' => 'count', ], ], - 'start' => !empty($_GET['start']) ? (int) $_GET['start'] : 0, - 'sort_order' => !empty($_GET['order']) ? $_GET['order'] : null, - 'sort_direction' => !empty($_GET['dir']) ? $_GET['dir'] : null, + 'default_sort_order' => 'tag', + 'default_sort_direction' => 'up', + 'start' => $_GET['start'] ?? 0, + 'sort_order' => $_GET['order'] ?? '', + 'sort_direction' => $_GET['dir'] ?? '', 'title' => 'Manage albums', 'no_items_label' => 'No albums meet the requirements of the current filter.', 'items_per_page' => 9999, 'index_class' => 'col-md-6', 'base_url' => BASEURL . '/managealbums/', - 'get_data' => function($offset = 0, $limit = 9999, $order = '', $direction = 'up') { - if (!in_array($order, ['id_tag', 'tag', 'slug', 'count'])) - $order = 'tag'; - if (!in_array($direction, ['up', 'down'])) - $direction = 'up'; - - $rows = PhotoAlbum::getHierarchy($order, $direction); - - return [ - 'rows' => $rows, - 'order' => $order, - 'direction' => ($direction == 'up' ? 'up' : 'down'), - ]; + 'get_data' => function($offset, $limit, $order, $direction) { + assert(in_array($order, ['id_tag', 'tag', 'slug', 'count'])); + return PhotoAlbum::getHierarchy($order, $direction); }, 'get_count' => function() { return 9999; diff --git a/controllers/ManageAssets.php b/controllers/ManageAssets.php index 5ae3597..88d9c46 100644 --- a/controllers/ManageAssets.php +++ b/controllers/ManageAssets.php @@ -121,19 +121,20 @@ class ManageAssets extends HTMLController ], ], ], - 'start' => !empty($_GET['start']) ? (int) $_GET['start'] : 0, - 'sort_order' => !empty($_GET['order']) ? $_GET['order'] : '', - 'sort_direction' => !empty($_GET['dir']) ? $_GET['dir'] : '', + 'default_sort_order' => 'id_asset', + 'default_sort_direction' => 'down', + 'start' => $_GET['start'] ?? 0, + 'sort_order' => $_GET['order'] ?? '', + 'sort_direction' => $_GET['dir'] ?? '', 'title' => 'Manage assets', 'no_items_label' => 'No assets meet the requirements of the current filter.', 'items_per_page' => 30, 'index_class' => 'col-md-6', 'base_url' => BASEURL . '/manageassets/', - 'get_data' => function($offset = 0, $limit = 30, $order = '', $direction = 'down') { - if (!in_array($order, ['id_asset', 'id_user_uploaded', 'title', 'subdir', 'filename'])) - $order = 'id_asset'; + 'get_data' => function($offset, $limit, $order, $direction) { + assert(in_array($order, ['id_asset', 'id_user_uploaded', 'title', 'subdir', 'filename'])); - $data = Registry::get('db')->queryAssocs(' + return Registry::get('db')->queryAssocs(' SELECT a.id_asset, a.subdir, a.filename, a.image_width, a.image_height, a.mimetype, u.id_user, u.first_name, u.surname @@ -146,12 +147,6 @@ class ManageAssets extends HTMLController 'offset' => $offset, 'limit' => $limit, ]); - - return [ - 'rows' => $data, - 'order' => $order, - 'direction' => $direction, - ]; }, 'get_count' => 'Asset::getCount', ]; diff --git a/controllers/ManageErrors.php b/controllers/ManageErrors.php index f510c7b..a3c7628 100644 --- a/controllers/ManageErrors.php +++ b/controllers/ManageErrors.php @@ -14,8 +14,8 @@ class ManageErrors extends HTMLController if (!Registry::get('user')->isAdmin()) throw new NotAllowedException(); - // Flushing, are we? - if (isset($_POST['flush']) && Session::validateSession('get')) + // Clearing, are we? + if (isset($_POST['clear']) && Session::validateSession('get')) { ErrorLog::flush(); header('Location: ' . BASEURL . '/manageerrors/'); @@ -31,7 +31,7 @@ class ManageErrors extends HTMLController 'method' => 'post', 'class' => 'col-md-6 text-end', 'buttons' => [ - 'flush' => [ + 'clear' => [ 'type' => 'submit', 'caption' => 'Delete all', 'class' => 'btn-danger', @@ -39,7 +39,7 @@ class ManageErrors extends HTMLController ], ], 'columns' => [ - 'id' => [ + 'id_entry' => [ 'value' => 'id_entry', 'header' => '#', 'is_sortable' => true, @@ -95,19 +95,20 @@ class ManageErrors extends HTMLController ], ], ], - 'start' => !empty($_GET['start']) ? (int) $_GET['start'] : 0, - 'sort_order' => !empty($_GET['order']) ? $_GET['order'] : '', - 'sort_direction' => !empty($_GET['dir']) ? $_GET['dir'] : '', + 'default_sort_order' => 'id_entry', + 'default_sort_direction' => 'down', + 'start' => $_GET['start'] ?? 0, + 'sort_order' => $_GET['order'] ?? '', + 'sort_direction' => $_GET['dir'] ?? '', 'no_items_label' => "No errors to display -- we're all good!", 'items_per_page' => 20, 'index_class' => 'col-md-6', 'base_url' => BASEURL . '/manageerrors/', 'get_count' => 'ErrorLog::getCount', - 'get_data' => function($offset = 0, $limit = 20, $order = '', $direction = 'down') { - if (!in_array($order, ['id_entry', 'file', 'line', 'time', 'ipaddress', 'id_user'])) - $order = 'id_entry'; + 'get_data' => function($offset, $limit, $order, $direction) { + assert(in_array($order, ['id_entry', 'file', 'line', 'time', 'ipaddress', 'id_user'])); - $data = Registry::get('db')->queryAssocs(' + return Registry::get('db')->queryAssocs(' SELECT * FROM log_errors ORDER BY {raw:order} @@ -117,12 +118,6 @@ class ManageErrors extends HTMLController 'offset' => $offset, 'limit' => $limit, ]); - - return [ - 'rows' => $data, - 'order' => $order, - 'direction' => $direction, - ]; }, ]; diff --git a/controllers/ManageTags.php b/controllers/ManageTags.php index ec12de0..34431c9 100644 --- a/controllers/ManageTags.php +++ b/controllers/ManageTags.php @@ -70,21 +70,20 @@ class ManageTags extends HTMLController 'value' => 'count', ], ], - 'start' => !empty($_GET['start']) ? (int) $_GET['start'] : 0, - 'sort_order' => !empty($_GET['order']) ? $_GET['order'] : null, - 'sort_direction' => !empty($_GET['dir']) ? $_GET['dir'] : null, + 'default_sort_order' => 'tag', + 'default_sort_direction' => 'up', + 'start' => $_GET['start'] ?? 0, + 'sort_order' => $_GET['order'] ?? '', + 'sort_direction' => $_GET['dir'] ?? '', 'title' => 'Manage tags', 'no_items_label' => 'No tags meet the requirements of the current filter.', 'items_per_page' => 30, 'index_class' => 'col-md-6', 'base_url' => BASEURL . '/managetags/', - 'get_data' => function($offset = 0, $limit = 30, $order = '', $direction = 'up') { - if (!in_array($order, ['id_tag', 'tag', 'slug', 'kind', 'count'])) - $order = 'tag'; - if (!in_array($direction, ['up', 'down'])) - $direction = 'up'; + 'get_data' => function($offset, $limit, $order, $direction) { + assert(in_array($order, ['id_tag', 'tag', 'slug', 'count'])); - $data = Registry::get('db')->queryAssocs(' + return Registry::get('db')->queryAssocs(' SELECT t.*, u.id_user, u.first_name, u.surname FROM tags AS t LEFT JOIN users AS u ON t.id_user_owner = u.id_user @@ -97,12 +96,6 @@ class ManageTags extends HTMLController 'limit' => $limit, 'album' => 'Album', ]); - - return [ - 'rows' => $data, - 'order' => $order, - 'direction' => ($direction == 'up' ? 'up' : 'down'), - ]; }, 'get_count' => function() { return Registry::get('db')->queryValue(' diff --git a/controllers/ManageUsers.php b/controllers/ManageUsers.php index 1be66a5..7f43b88 100644 --- a/controllers/ManageUsers.php +++ b/controllers/ManageUsers.php @@ -90,19 +90,20 @@ class ManageUsers extends HTMLController ], ], ], - 'start' => !empty($_GET['start']) ? (int) $_GET['start'] : 0, - 'sort_order' => !empty($_GET['order']) ? $_GET['order'] : '', - 'sort_direction' => !empty($_GET['dir']) ? $_GET['dir'] : '', + 'default_sort_order' => 'id_user', + 'default_sort_direction' => 'down', + 'start' => $_GET['start'] ?? 0, + 'sort_order' => $_GET['order'] ?? '', + 'sort_direction' => $_GET['dir'] ?? '', 'title' => 'Manage users', 'no_items_label' => 'No users meet the requirements of the current filter.', 'items_per_page' => 30, 'index_class' => 'col-md-6', 'base_url' => BASEURL . '/manageusers/', - 'get_data' => function($offset = 0, $limit = 30, $order = '', $direction = 'down') { - if (!in_array($order, ['id_user', 'surname', 'first_name', 'slug', 'emailaddress', 'last_action_time', 'ip_address', 'is_admin'])) - $order = 'id_user'; + 'get_data' => function($offset, $limit, $order, $direction) { + assert(in_array($order, ['id_user', 'surname', 'first_name', 'slug', 'emailaddress', 'last_action_time', 'ip_address', 'is_admin'])); - $data = Registry::get('db')->queryAssocs(' + return Registry::get('db')->queryAssocs(' SELECT * FROM users ORDER BY {raw:order} @@ -112,18 +113,8 @@ class ManageUsers extends HTMLController 'offset' => $offset, 'limit' => $limit, ]); - - return [ - 'rows' => $data, - 'order' => $order, - 'direction' => $direction, - ]; }, - 'get_count' => function() { - return Registry::get('db')->queryValue(' - SELECT COUNT(*) - FROM users'); - } + 'get_count' => 'Member::getCount', ]; $table = new GenericTable($options); diff --git a/models/GenericTable.php b/models/GenericTable.php index b63df32..df48c83 100644 --- a/models/GenericTable.php +++ b/models/GenericTable.php @@ -28,53 +28,22 @@ class GenericTable public function __construct($options) { - // Make sure we're actually sorting on something sortable. - if (!isset($options['sort_order']) || (!empty($options['sort_order']) && empty($options['columns'][$options['sort_order']]['is_sortable']))) - $options['sort_order'] = ''; + $this->initOrder($options); + $this->initPagination($options); - // Order in which direction? - if (!empty($options['sort_direction']) && !in_array($options['sort_direction'], ['up', 'down'])) - $options['sort_direction'] = 'up'; - - // How much data do we have? - $this->recordCount = $options['get_count'](); - - // How much data do we need to retrieve? - $this->items_per_page = !empty($options['items_per_page']) ? $options['items_per_page'] : 30; - - // Figure out where to start. - $this->start = empty($options['start']) || !is_numeric($options['start']) || $options['start'] < 0 || $options['start'] > $this->recordCount ? 0 : $options['start']; - - // Figure out where we are on the whole, too. - $numPages = max(1, ceil($this->recordCount / $this->items_per_page)); - $this->currentPage = min(ceil($this->start / $this->items_per_page) + 1, $numPages); - - // Let's bear a few things in mind... - $this->base_url = $options['base_url']; - - // Gather parameters for the data gather function first. - $parameters = [$this->start, $this->items_per_page, $options['sort_order'], $options['sort_direction']]; - - // Okay, let's fetch the data! - $data = $options['get_data'](...$parameters); - - // Extract data into local variables. - $rawRowData = $data['rows']; - $this->sort_order = $data['order']; - $this->sort_direction = $data['direction']; - unset($data); + $data = $options['get_data']($this->start, $this->items_per_page, + $this->sort_order, $this->sort_direction); // Okay, now for the column headers... $this->generateColumnHeaders($options); // Should we create a page index? - $needsPageIndex = !empty($this->items_per_page) && $this->recordCount > $this->items_per_page; - if ($needsPageIndex) + if ($this->recordCount > $this->items_per_page) $this->generatePageIndex($options); // Process the data to be shown into rows. - if (!empty($rawRowData)) - $this->processAllRows($rawRowData, $options); + if (!empty($data)) + $this->processAllRows($data, $options); else $this->body = $options['no_items_label'] ?? ''; @@ -89,6 +58,38 @@ class GenericTable $this->form_below = $options['form_below'] ?? $options['form'] ?? null; } + private function initOrder($options) + { + assert(isset($options['default_sort_order'])); + assert(isset($options['default_sort_direction'])); + + // Validate sort order (column) + $this->sort_order = $options['sort_order']; + if (empty($this->sort_order) || empty($options['columns'][$this->sort_order]['is_sortable'])) + $this->sort_order = $options['default_sort_order']; + + // Validate sort direction + $this->sort_direction = $options['sort_direction']; + if (empty($this->sort_direction) || !in_array($this->sort_direction, ['up', 'down'])) + $this->sort_direction = $options['default_sort_direction']; + } + + private function initPagination(array $options) + { + assert(isset($options['base_url'])); + assert(isset($options['items_per_page'])); + + $this->base_url = $options['base_url']; + + $this->recordCount = $options['get_count'](); + $this->items_per_page = !empty($options['items_per_page']) ? $options['items_per_page'] : 30; + + $this->start = empty($options['start']) || !is_numeric($options['start']) || $options['start'] < 0 || $options['start'] > $this->recordCount ? 0 : $options['start']; + + $numPages = max(1, ceil($this->recordCount / $this->items_per_page)); + $this->currentPage = min(ceil($this->start / $this->items_per_page) + 1, $numPages); + } + private function generateColumnHeaders($options) { foreach ($options['columns'] as $key => $column) @@ -218,7 +219,7 @@ class GenericTable { // Basic option: simply take a use a particular data property. case 'value': - $value = htmlspecialchars($rowData[$options['data']]); + $value = htmlspecialchars($rowData[$options['data']] ?? ''); break; // Processing via a lambda function. -- 2.46.0 From 1e26a51d0867dfd59f0378669a8bbbddc05caa42 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 22 Dec 2024 15:35:50 +0100 Subject: [PATCH 04/11] ErrorLog: use DELETE FROM instead of TRUNCATE --- models/ErrorLog.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/ErrorLog.php b/models/ErrorLog.php index 0f13d5b..979073b 100644 --- a/models/ErrorLog.php +++ b/models/ErrorLog.php @@ -24,7 +24,7 @@ class ErrorLog public static function flush() { - return Registry::get('db')->query('TRUNCATE log_errors'); + return Registry::get('db')->query('DELETE FROM log_errors'); } public static function getCount() -- 2.46.0 From 2d2ef384229ba39af6743cf32bbea011d3bdca71 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 22 Dec 2024 15:45:44 +0100 Subject: [PATCH 05/11] MainNavBar: harden Registry access --- templates/MainNavBar.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/MainNavBar.php b/templates/MainNavBar.php index 51ae4c1..7b7b5e4 100644 --- a/templates/MainNavBar.php +++ b/templates/MainNavBar.php @@ -33,7 +33,7 @@ class MainNavBar extends NavBar '; - if (Registry::get('user')->isLoggedIn()) + if (Registry::has('user') && Registry::get('user')->isLoggedIn()) { echo '