diff --git a/controllers/ManageAlbums.php b/controllers/ManageAlbums.php index 662714b..248e369 100644 --- a/controllers/ManageAlbums.php +++ b/controllers/ManageAlbums.php @@ -35,18 +35,14 @@ class ManageAlbums extends HTMLController 'tag' => [ 'header' => 'Album', 'is_sortable' => true, - 'parse' => [ - 'link' => BASEURL . '/editalbum/?id={ID_TAG}', - 'data' => 'tag', - ], + 'link' => BASEURL . '/editalbum/?id={ID_TAG}', + 'value' => 'tag', ], 'slug' => [ 'header' => 'Slug', 'is_sortable' => true, - 'parse' => [ - 'link' => BASEURL . '/editalbum/?id={ID_TAG}', - 'data' => 'slug', - ], + 'link' => BASEURL . '/editalbum/?id={ID_TAG}', + 'value' => 'slug', ], 'count' => [ 'header' => '# Photos', @@ -54,30 +50,21 @@ 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) { + return Tag::getOffset($offset, $limit, $order, $direction, true); }, 'get_count' => function() { - return 9999; + return Tag::getCount(false, 'Album', true); } ]; diff --git a/controllers/ManageAssets.php b/controllers/ManageAssets.php index 5ae3597..59895ff 100644 --- a/controllers/ManageAssets.php +++ b/controllers/ManageAssets.php @@ -38,40 +38,33 @@ class ManageAssets extends HTMLController 'checkbox' => [ 'header' => '', 'is_sortable' => false, - 'parse' => [ - 'type' => 'function', - 'data' => function($row) { - return ''; - }, - ], + 'format' => fn($row) => + '', ], 'thumbnail' => [ 'header' => ' ', 'is_sortable' => false, 'cell_class' => 'text-center', - 'parse' => [ - 'type' => 'function', - 'data' => function($row) { - $asset = Image::byRow($row); - $width = $height = 65; - if ($asset->isImage()) - { - if ($asset->isPortrait()) - $width = null; - else - $height = null; - - $thumb = $asset->getThumbnailUrl($width, $height); - } + 'format' => function($row) { + $asset = Image::byRow($row); + $width = $height = 65; + if ($asset->isImage()) + { + if ($asset->isPortrait()) + $width = null; else - $thumb = BASEURL . '/images/nothumb.svg'; + $height = null; - $width = isset($width) ? $width . 'px' : 'auto'; - $height = isset($height) ? $height . 'px' : 'auto'; + $thumb = $asset->getThumbnailUrl($width, $height); + } + else + $thumb = BASEURL . '/images/nothumb.svg'; - return sprintf('', $thumb, $width, $height); - }, - ], + $width = isset($width) ? $width . 'px' : 'auto'; + $height = isset($height) ? $height . 'px' : 'auto'; + + return sprintf('', $thumb, $width, $height); + }, ], 'id_asset' => [ 'value' => 'id_asset', @@ -87,72 +80,42 @@ class ManageAssets extends HTMLController 'value' => 'filename', 'header' => 'Filename', 'is_sortable' => true, - 'parse' => [ - 'type' => 'value', - 'link' => BASEURL . '/editasset/?id={ID_ASSET}', - 'data' => 'filename', - ], + 'link' => BASEURL . '/editasset/?id={ID_ASSET}', + 'value' => 'filename', ], 'id_user_uploaded' => [ 'header' => 'User uploaded', 'is_sortable' => true, - 'parse' => [ - 'type' => 'function', - 'data' => function($row) { - if (!empty($row['id_user'])) - return sprintf('%s', BASEURL, $row['id_user'], - $row['first_name'] . ' ' . $row['surname']); - else - return 'n/a'; - }, - ], + 'format' => function($row) { + if (!empty($row['id_user'])) + return sprintf('%s', BASEURL, $row['id_user'], + $row['first_name'] . ' ' . $row['surname']); + else + return 'n/a'; + }, ], 'dimensions' => [ 'header' => 'Dimensions', 'is_sortable' => false, - 'parse' => [ - 'type' => 'function', - 'data' => function($row) { - if (!empty($row['image_width'])) - return $row['image_width'] . ' x ' . $row['image_height']; - else - return 'n/a'; - }, - ], + 'format' => function($row) { + if (!empty($row['image_width'])) + return $row['image_width'] . ' x ' . $row['image_height']; + else + return 'n/a'; + }, ], ], - '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'; - - $data = 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 - FROM assets AS a - LEFT JOIN users AS u ON a.id_user_uploaded = u.id_user - ORDER BY {raw:order} - LIMIT {int:offset}, {int:limit}', - [ - 'order' => $order . ($direction == 'up' ? ' ASC' : ' DESC'), - 'offset' => $offset, - 'limit' => $limit, - ]); - - return [ - 'rows' => $data, - 'order' => $order, - 'direction' => $direction, - ]; - }, + 'get_data' => 'Asset::getOffset', 'get_count' => 'Asset::getCount', ]; diff --git a/controllers/ManageErrors.php b/controllers/ManageErrors.php index f510c7b..75186aa 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,26 +39,23 @@ class ManageErrors extends HTMLController ], ], 'columns' => [ - 'id' => [ + 'id_entry' => [ 'value' => 'id_entry', 'header' => '#', 'is_sortable' => true, ], 'message' => [ - 'parse' => [ - 'type' => 'function', - 'data' => function($row) { - return $row['message'] . '
' . - '
Show debug info' . - '
' . htmlspecialchars($row['debug_info']) .
-								'
' . - '' . - htmlspecialchars($row['request_uri']) . ''; - } - ], 'header' => 'Message / URL', 'is_sortable' => false, + 'format' => function($row) { + return $row['message'] . '
' . + '
Show debug info' . + '
' . htmlspecialchars($row['debug_info']) .
+							'
' . + '' . + htmlspecialchars($row['request_uri']) . ''; + }, ], 'file' => [ 'value' => 'file', @@ -71,12 +68,10 @@ class ManageErrors extends HTMLController 'is_sortable' => true, ], 'time' => [ - 'parse' => [ + 'format' => [ 'type' => 'timestamp', - 'data' => [ - 'timestamp' => 'time', - 'pattern' => 'long', - ], + 'pattern' => 'long', + 'value' => 'time', ], 'header' => 'Time', 'is_sortable' => true, @@ -89,41 +84,21 @@ class ManageErrors extends HTMLController 'uid' => [ 'header' => 'UID', 'is_sortable' => true, - 'parse' => [ - 'link' => BASEURL . '/edituser/?id={ID_USER}', - 'data' => 'id_user', - ], + 'link' => BASEURL . '/edituser/?id={ID_USER}', + 'value' => 'id_user', ], ], - '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'; - - $data = Registry::get('db')->queryAssocs(' - SELECT * - FROM log_errors - ORDER BY {raw:order} - LIMIT {int:offset}, {int:limit}', - [ - 'order' => $order . ($direction === 'up' ? ' ASC' : ' DESC'), - 'offset' => $offset, - 'limit' => $limit, - ]); - - return [ - 'rows' => $data, - 'order' => $order, - 'direction' => $direction, - ]; - }, + 'get_data' => 'ErrorLog::getOffset', ]; $error_log = new GenericTable($options); diff --git a/controllers/ManageTags.php b/controllers/ManageTags.php index ec12de0..9dc653b 100644 --- a/controllers/ManageTags.php +++ b/controllers/ManageTags.php @@ -37,32 +37,25 @@ class ManageTags extends HTMLController 'tag' => [ 'header' => 'Tag', 'is_sortable' => true, - 'parse' => [ - 'link' => BASEURL . '/edittag/?id={ID_TAG}', - 'data' => 'tag', - ], + 'link' => BASEURL . '/edittag/?id={ID_TAG}', + 'value' => 'tag', ], 'slug' => [ 'header' => 'Slug', 'is_sortable' => true, - 'parse' => [ - 'link' => BASEURL . '/edittag/?id={ID_TAG}', - 'data' => 'slug', - ], + 'link' => BASEURL . '/edittag/?id={ID_TAG}', + 'value' => 'slug', ], 'id_user_owner' => [ 'header' => 'Owning user', 'is_sortable' => true, - 'parse' => [ - 'type' => 'function', - 'data' => function($row) { - if (!empty($row['id_user'])) - return sprintf('%s', BASEURL, $row['id_user'], - $row['first_name'] . ' ' . $row['surname']); - else - return 'n/a'; - }, - ], + 'format' => function($row) { + if (!empty($row['id_user'])) + return sprintf('%s', BASEURL, $row['id_user'], + $row['first_name'] . ' ' . $row['surname']); + else + return 'n/a'; + }, ], 'count' => [ 'header' => 'Cardinality', @@ -70,46 +63,21 @@ 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'; - - $data = 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 - WHERE kind != {string:album} - ORDER BY {raw:order} - LIMIT {int:offset}, {int:limit}', - [ - 'order' => $order . ($direction == 'up' ? ' ASC' : ' DESC'), - 'offset' => $offset, - 'limit' => $limit, - 'album' => 'Album', - ]); - - return [ - 'rows' => $data, - 'order' => $order, - 'direction' => ($direction == 'up' ? 'up' : 'down'), - ]; + 'get_data' => function($offset, $limit, $order, $direction) { + return Tag::getOffset($offset, $limit, $order, $direction, false); }, 'get_count' => function() { - return Registry::get('db')->queryValue(' - SELECT COUNT(*) - FROM tags - WHERE kind != {string:album}', - ['album' => 'Album']); + return Tag::getCount(false, null, false); } ]; diff --git a/controllers/ManageUsers.php b/controllers/ManageUsers.php index 1be66a5..8473596 100644 --- a/controllers/ManageUsers.php +++ b/controllers/ManageUsers.php @@ -37,26 +37,20 @@ class ManageUsers extends HTMLController 'surname' => [ 'header' => 'Last name', 'is_sortable' => true, - 'parse' => [ - 'link' => BASEURL . '/edituser/?id={ID_USER}', - 'data' => 'surname', - ], + 'link' => BASEURL . '/edituser/?id={ID_USER}', + 'value' => 'surname', ], 'first_name' => [ 'header' => 'First name', 'is_sortable' => true, - 'parse' => [ - 'link' => BASEURL . '/edituser/?id={ID_USER}', - 'data' => 'first_name', - ], + 'link' => BASEURL . '/edituser/?id={ID_USER}', + 'value' => 'first_name', ], 'slug' => [ 'header' => 'Slug', 'is_sortable' => true, - 'parse' => [ - 'link' => BASEURL . '/edituser/?id={ID_USER}', - 'data' => 'slug', - ], + 'link' => BASEURL . '/edituser/?id={ID_USER}', + 'value' => 'slug', ], 'emailaddress' => [ 'value' => 'emailaddress', @@ -64,12 +58,10 @@ class ManageUsers extends HTMLController 'is_sortable' => true, ], 'last_action_time' => [ - 'parse' => [ + 'format' => [ 'type' => 'timestamp', - 'data' => [ - 'timestamp' => 'last_action_time', - 'pattern' => 'long', - ], + 'pattern' => 'long', + 'value' => 'last_action_time', ], 'header' => 'Last activity', 'is_sortable' => true, @@ -82,48 +74,21 @@ class ManageUsers extends HTMLController 'is_admin' => [ 'is_sortable' => true, 'header' => 'Admin?', - 'parse' => [ - 'type' => 'function', - 'data' => function($row) { - return $row['is_admin'] ? 'yes' : 'no'; - } - ], + 'format' => fn($row) => $row['is_admin'] ? 'yes' : 'no', ], ], - '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'; - - $data = Registry::get('db')->queryAssocs(' - SELECT * - FROM users - ORDER BY {raw:order} - LIMIT {int:offset}, {int:limit}', - [ - 'order' => $order . ($direction == 'up' ? ' ASC' : ' DESC'), - 'offset' => $offset, - 'limit' => $limit, - ]); - - return [ - 'rows' => $data, - 'order' => $order, - 'direction' => $direction, - ]; - }, - 'get_count' => function() { - return Registry::get('db')->queryValue(' - SELECT COUNT(*) - FROM users'); - } + 'get_data' => 'Member::getOffset', + 'get_count' => 'Member::getCount', ]; $table = new GenericTable($options); diff --git a/models/Asset.php b/models/Asset.php index edadd5f..b3803bf 100644 --- a/models/Asset.php +++ b/models/Asset.php @@ -680,6 +680,23 @@ class Asset FROM assets'); } + public static function getOffset($offset, $limit, $order, $direction) + { + 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 + FROM assets AS a + LEFT JOIN users AS u ON a.id_user_uploaded = u.id_user + ORDER BY {raw:order} + LIMIT {int:offset}, {int:limit}', + [ + 'order' => $order . ($direction == 'up' ? ' ASC' : ' DESC'), + 'offset' => $offset, + 'limit' => $limit, + ]); + } + public function save() { if (empty($this->id_asset)) diff --git a/models/ErrorLog.php b/models/ErrorLog.php index 0f13d5b..f406ae3 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() @@ -33,4 +33,20 @@ class ErrorLog SELECT COUNT(*) FROM log_errors'); } + + public static function getOffset($offset, $limit, $order, $direction) + { + assert(in_array($order, ['id_entry', 'file', 'line', 'time', 'ipaddress', 'id_user'])); + + return Registry::get('db')->queryAssocs(' + SELECT * + FROM log_errors + ORDER BY {raw:order} + LIMIT {int:offset}, {int:limit}', + [ + 'order' => $order . ($direction === 'up' ? ' ASC' : ' DESC'), + 'offset' => $offset, + 'limit' => $limit, + ]); + } } diff --git a/models/GenericTable.php b/models/GenericTable.php index 415ef53..ec76a1a 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; @@ -29,58 +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'; - - // 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'](...(!empty($options['get_count_params']) ? $options['get_count_params'] : [])); - - // 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']]; - 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); - - // 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'] ?? ''; @@ -95,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) @@ -102,14 +97,14 @@ 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 = [ 'class' => isset($column['class']) ? $column['class'] : '', 'cell_class' => isset($column['cell_class']) ? $column['cell_class'] : null, 'colspan' => !empty($column['header_colspan']) ? $column['header_colspan'] : 1, - 'href' => $isSortable ? $this->getLink($this->start, $key, $sortDirection) : null, + 'href' => $isSortable ? $this->getHeaderLink($this->start, $key, $sortDirection) : null, 'label' => $column['header'], 'scope' => 'col', 'sort_mode' => $key == $this->sort_order ? $this->sort_direction : null, @@ -126,7 +121,7 @@ class GenericTable 'base_url' => $this->base_url, 'index_class' => $options['index_class'] ?? '', 'items_per_page' => $this->items_per_page, - 'linkBuilder' => [$this, 'getLink'], + 'linkBuilder' => [$this, 'getHeaderLink'], 'recordCount' => $this->recordCount, 'sort_direction' => $this->sort_direction, 'sort_order' => $this->sort_order, @@ -134,7 +129,7 @@ class GenericTable ]); } - public function getLink($start = null, $order = null, $dir = null) + public function getHeaderLink($start = null, $order = null, $dir = null) { if ($start === null) $start = $this->start; @@ -196,12 +191,18 @@ class GenericTable foreach ($options['columns'] as $column) { - // Process data for this particular cell. - if (isset($column['parse'])) - $value = self::processCell($column['parse'], $row); + // Process formatting + if (isset($column['format']) && is_callable($column['format'])) + $value = $column['format']($row); + elseif (isset($column['format'])) + $value = self::processFormatting($column['format'], $row); else $value = $row[$column['value']]; + // Turn value into a link? + if (!empty($column['link'])) + $value = $this->processLink($column['link'], $value, $row); + // Append the cell to the row. $newRow['cells'][] = [ 'class' => $column['cell_class'] ?? '', @@ -214,68 +215,47 @@ class GenericTable } } - private function processCell($options, $rowData) + private function processFormatting($options, $rowData) { - if (!isset($options['type'])) - $options['type'] = 'value'; - - // Parse the basic value first. - switch ($options['type']) + if ($options['type'] === 'timestamp') { - // Basic option: simply take a use a particular data property. - case 'value': - $value = htmlspecialchars($rowData[$options['data']]); - break; + if (empty($options['pattern']) || $options['pattern'] === 'long') + $pattern = 'Y-m-d H:i'; + elseif ($options['pattern'] === 'short') + $pattern = 'Y-m-d'; + else + $pattern = $options['pattern']; - // Processing via a lambda function. - case 'function': - $value = $options['data']($rowData); - break; + assert(isset($rowData[$options['value']])); + if (!is_numeric($rowData[$options['value']])) + $timestamp = strtotime($rowData[$options['value']]); + else + $timestamp = (int) $rowData[$options['value']]; - // Using sprintf to fill out a particular pattern. - case 'sprintf': - $parameters = [$options['data']['pattern']]; - foreach ($options['data']['arguments'] as $identifier) - $parameters[] = $rowData[$identifier]; + if (isset($options['if_null']) && $timestamp == 0) + $value = $options['if_null']; + else + $value = date($pattern, $timestamp); - $value = sprintf(...$parameters); - break; - - // Timestamps get custom treatment. - case 'timestamp': - if (empty($options['data']['pattern']) || $options['data']['pattern'] === 'long') - $pattern = 'Y-m-d H:i'; - elseif ($options['data']['pattern'] === 'short') - $pattern = 'Y-m-d'; - else - $pattern = $options['data']['pattern']; - - if (!isset($rowData[$options['data']['timestamp']])) - $timestamp = 0; - elseif (!is_numeric($rowData[$options['data']['timestamp']])) - $timestamp = strtotime($rowData[$options['data']['timestamp']]); - else - $timestamp = (int) $rowData[$options['data']['timestamp']]; - - if (isset($options['data']['if_null']) && $timestamp == 0) - $value = $options['data']['if_null']; - else - $value = date($pattern, $timestamp); - break; + return $value; } + else + throw ValueError('Unexpected formatter type: ' . $options['type']); + } - // Generate a link, if requested. - if (!empty($options['link'])) - { - // First, generate the replacement variables. - $keys = array_keys($rowData); - $values = array_values($rowData); - foreach ($keys as $keyKey => $keyValue) - $keys[$keyKey] = '{' . strtoupper($keyValue) . '}'; + private function processLink($template, $value, array $rowData) + { + $href = $this->rowReplacements($template, $rowData); + return '' . $value . ''; + } - $value = '' . $value . ''; - } + private function rowReplacements($template, array $rowData) + { + $keys = array_keys($rowData); + $values = array_values($rowData); + foreach ($keys as $keyKey => $keyValue) + $keys[$keyKey] = '{' . strtoupper($keyValue) . '}'; - return $value; + return str_replace($keys, $values, $template); } } diff --git a/models/Member.php b/models/Member.php index 84912a4..0bb2ef5 100644 --- a/models/Member.php +++ b/models/Member.php @@ -187,6 +187,22 @@ class Member extends User FROM users'); } + public static function getOffset($offset, $limit, $order, $direction) + { + assert(in_array($order, ['id_user', 'surname', 'first_name', 'slug', 'emailaddress', 'last_action_time', 'ip_address', 'is_admin'])); + + return Registry::get('db')->queryAssocs(' + SELECT * + FROM users + ORDER BY {raw:order} + LIMIT {int:offset}, {int:limit}', + [ + 'order' => $order . ($direction === 'up' ? ' ASC' : ' DESC'), + 'offset' => $offset, + 'limit' => $limit, + ]); + } + public function getProps() { // We should probably phase out the use of this function, or refactor the access levels of member properties... diff --git a/models/PhotoAlbum.php b/models/PhotoAlbum.php deleted file mode 100644 index efdc41f..0000000 --- a/models/PhotoAlbum.php +++ /dev/null @@ -1,76 +0,0 @@ -query(' - SELECT * - FROM tags - WHERE kind = {string:album} - ORDER BY id_parent, {raw:order}', - [ - 'order' => $order . ($direction == 'up' ? ' ASC' : ' DESC'), - 'album' => 'Album', - ]); - - $albums_by_parent = []; - while ($row = $db->fetch_assoc($res)) - { - if (!isset($albums_by_parent[$row['id_parent']])) - $albums_by_parent[$row['id_parent']] = []; - - $albums_by_parent[$row['id_parent']][] = $row + ['children' => []]; - } - - $albums = self::getChildrenRecursively(0, 0, $albums_by_parent); - $rows = self::flattenChildrenRecursively($albums); - - return $rows; - } - - private static function getChildrenRecursively($id_parent, $level, &$albums_by_parent) - { - $children = []; - if (!isset($albums_by_parent[$id_parent])) - return $children; - - foreach ($albums_by_parent[$id_parent] as $child) - { - if (isset($albums_by_parent[$child['id_tag']])) - $child['children'] = self::getChildrenRecursively($child['id_tag'], $level + 1, $albums_by_parent); - - $child['tag'] = ($level ? str_repeat('—', $level * 2) . ' ' : '') . $child['tag']; - $children[] = $child; - } - - return $children; - } - - private static function flattenChildrenRecursively($albums) - { - if (empty($albums)) - return []; - - $rows = []; - foreach ($albums as $album) - { - $rows[] = array_intersect_key($album, array_flip(['id_tag', 'tag', 'slug', 'count'])); - if (!empty($album['children'])) - { - $children = self::flattenChildrenRecursively($album['children']); - foreach ($children as $child) - $rows[] = array_intersect_key($child, array_flip(['id_tag', 'tag', 'slug', 'count'])); - } - } - - return $rows; - } -} diff --git a/models/Tag.php b/models/Tag.php index 145db1e..d5f6db7 100644 --- a/models/Tag.php +++ b/models/Tag.php @@ -24,6 +24,11 @@ class Tag $this->$attribute = $value; } + public function __toString() + { + return $this->tag; + } + public static function fromId($id_tag, $return_format = 'object') { $db = Registry::get('db'); @@ -409,27 +414,98 @@ class Tag ['tags' => $tags]); } - public static function getCount($only_active = 1, $kind = '') + public static function getCount($only_used = true, $kind = '', $isAlbum = false) { $where = []; - if ($only_active) + if ($only_used) $where[] = 'count > 0'; - if (!empty($kind)) - $where[] = 'kind = {string:kind}'; + if (empty($kind)) + $kind = 'Album'; - if (!empty($where)) - $where = 'WHERE ' . implode(' AND ', $where); - else - $where = ''; + $where[] = 'kind {raw:operator} {string:kind}'; + $where = implode(' AND ', $where); return Registry::get('db')->queryValue(' SELECT COUNT(*) - FROM tags ' . $where, - ['kind' => $kind]); + FROM tags + WHERE ' . $where, + [ + 'kind' => $kind, + 'operator' => $isAlbum ? '=' : '!=', + ]); } - public function __toString() + public static function getOffset($offset, $limit, $order, $direction, $isAlbum = false) { - return $this->tag; + assert(in_array($order, ['id_tag', 'tag', 'slug', 'count'])); + + $db = Registry::get('db'); + $res = $db->query(' + 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 + WHERE kind {raw:operator} {string:album} + ORDER BY id_parent, {raw:order} + LIMIT {int:offset}, {int:limit}', + [ + 'order' => $order . ($direction === 'up' ? ' ASC' : ' DESC'), + 'offset' => $offset, + 'limit' => $limit, + 'album' => 'Album', + 'operator' => $isAlbum ? '=' : '!=', + ]); + + $albums_by_parent = []; + while ($row = $db->fetch_assoc($res)) + { + if (!isset($albums_by_parent[$row['id_parent']])) + $albums_by_parent[$row['id_parent']] = []; + + $albums_by_parent[$row['id_parent']][] = $row + ['children' => []]; + } + + $albums = self::getChildrenRecursively(0, 0, $albums_by_parent); + $rows = self::flattenChildrenRecursively($albums); + + return $rows; + } + + private static function getChildrenRecursively($id_parent, $level, &$albums_by_parent) + { + $children = []; + if (!isset($albums_by_parent[$id_parent])) + return $children; + + foreach ($albums_by_parent[$id_parent] as $child) + { + if (isset($albums_by_parent[$child['id_tag']])) + $child['children'] = self::getChildrenRecursively($child['id_tag'], $level + 1, $albums_by_parent); + + $child['tag'] = ($level ? str_repeat('—', $level * 2) . ' ' : '') . $child['tag']; + $children[] = $child; + } + + return $children; + } + + private static function flattenChildrenRecursively($albums) + { + if (empty($albums)) + return []; + + $rows = []; + foreach ($albums as $album) + { + static $headers_to_keep = ['id_tag', 'tag', 'slug', 'count', 'id_user', 'first_name', 'surname']; + $rows[] = array_intersect_key($album, array_flip($headers_to_keep)); + if (!empty($album['children'])) + { + $children = self::flattenChildrenRecursively($album['children']); + foreach ($children as $child) + $rows[] = array_intersect_key($child, array_flip($headers_to_keep)); + } + } + + return $rows; } } 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 '