diff --git a/controllers/EditAlbum.php b/controllers/EditAlbum.php
index 73affff..9691148 100644
--- a/controllers/EditAlbum.php
+++ b/controllers/EditAlbum.php
@@ -41,13 +41,13 @@ class EditAlbum extends HTMLController
exit;
}
else
- trigger_error('Cannot delete album: an error occured while processing the request.', E_USER_ERROR);
+ throw new Exception('Cannot delete album: an error occured while processing the request.');
}
// Editing one, then, surely.
else
{
if ($album->kind !== 'Album')
- trigger_error('Cannot edit album: not an album.', E_USER_ERROR);
+ throw new Exception('Cannot edit album: not an album.');
parent::__construct('Edit album \'' . $album->tag . '\'');
$form_title = 'Edit album \'' . $album->tag . '\'';
@@ -67,7 +67,7 @@ class EditAlbum extends HTMLController
// Gather possible parents for this album to be filed into
$parentChoices = [0 => '-root-'];
- foreach (PhotoAlbum::getHierarchy('tag', 'up') as $parent)
+ foreach (Tag::getOffset(0, 9999, 'tag', 'up', true) as $parent)
{
if (!empty($id_tag) && $parent['id_tag'] == $id_tag)
continue;
@@ -139,6 +139,10 @@ class EditAlbum extends HTMLController
];
}
}
+ elseif (empty($_POST) && isset($album))
+ {
+ $formDefaults = get_object_vars($album);
+ }
elseif (empty($_POST) && count($parentChoices) > 1)
{
// Choose the first non-root album as the default parent
@@ -146,9 +150,8 @@ class EditAlbum extends HTMLController
next($parentChoices);
$formDefaults = ['id_parent' => key($parentChoices)];
}
-
- if (!isset($formDefaults))
- $formDefaults = isset($album) ? get_object_vars($album) : $_POST;
+ else
+ $formDefaults = $_POST;
// Create the form, add in default values.
$this->form->setData($formDefaults);
diff --git a/controllers/EditAsset.php b/controllers/EditAsset.php
index d5582d1..26b99fc 100644
--- a/controllers/EditAsset.php
+++ b/controllers/EditAsset.php
@@ -67,7 +67,7 @@ class EditAsset extends HTMLController
// Get a list of available photo albums
$allAlbums = [];
- foreach (PhotoAlbum::getHierarchy('tag', 'up') as $album)
+ foreach (Tag::getOffset(0, 9999, 'tag', 'up', true) as $album)
$allAlbums[$album['id_tag']] = $album['tag'];
// Figure out the current album id
diff --git a/controllers/EditTag.php b/controllers/EditTag.php
index 364a6f4..116d226 100644
--- a/controllers/EditTag.php
+++ b/controllers/EditTag.php
@@ -39,13 +39,13 @@ class EditTag extends HTMLController
exit;
}
else
- trigger_error('Cannot delete tag: an error occured while processing the request.', E_USER_ERROR);
+ throw new Exception('Cannot delete tag: an error occured while processing the request.');
}
// Editing one, then, surely.
else
{
if ($tag->kind === 'Album')
- trigger_error('Cannot edit tag: is actually an album.', E_USER_ERROR);
+ throw new Exception('Cannot edit tag: is actually an album.');
parent::__construct('Edit tag \'' . $tag->tag . '\'');
$form_title = 'Edit tag \'' . $tag->tag . '\'';
diff --git a/controllers/EditUser.php b/controllers/EditUser.php
index cf44235..39715e9 100644
--- a/controllers/EditUser.php
+++ b/controllers/EditUser.php
@@ -33,7 +33,7 @@ class EditUser extends HTMLController
{
// Don't be stupid.
if ($current_user->getUserId() == $id_user)
- trigger_error('Sorry, I cannot allow you to delete yourself.', E_USER_ERROR);
+ throw new Exception('Sorry, I cannot allow you to delete yourself.');
// So far so good?
$user = Member::fromId($id_user);
@@ -43,7 +43,7 @@ class EditUser extends HTMLController
exit;
}
else
- trigger_error('Cannot delete user: an error occured while processing the request.', E_USER_ERROR);
+ throw new Exception('Cannot delete user: an error occured while processing the request.');
}
// Editing one, then, surely.
else
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'] . '
' .
- '
The server does not understand your request.
')); - $page->html_main(); + http_response_code(400); + self::errorPage('Bad request', 'The server does not understand your request.'); exit; } - public static function trigger403() + private static function trigger403() { - header('HTTP/1.1 403 Forbidden'); - $page = new MainTemplate('Access denied'); - $page->adopt(new DummyBox('Forbidden', 'You do not have access to the page you requested.
')); - $page->html_main(); + http_response_code(403); + self::errorPage('Forbidden', 'You do not have access to this page.'); exit; } - public static function trigger404() + private static function trigger404() { - header('HTTP/1.1 404 Not Found'); - $page = new MainTemplate('Page not found'); - - if (Registry::has('user') && Registry::get('user')->isAdmin()) - { - $page->appendStylesheet(BASEURL . '/css/admin.css'); - } - - $page->adopt(new DummyBox('Well, this is a bit embarrassing!', 'The page you requested could not be found. Don\'t worry, it\'s probably not your fault. You\'re welcome to browse the website, though!
', 'errormsg')); - $page->addClass('errorpage'); - $page->html_main(); - exit; + http_response_code(404); + $page = new ViewErrorPage('Page not found!'); + $page->showContent(); } } diff --git a/models/ErrorHandler.php b/models/ErrorHandler.php index 254291a..12afbd6 100644 --- a/models/ErrorHandler.php +++ b/models/ErrorHandler.php @@ -3,7 +3,7 @@ * ErrorHandler.php * Contains key class ErrorHandler. * - * Kabuki CMS (C) 2013-2016, Aaron van Geffen + * Kabuki CMS (C) 2013-2025, Aaron van Geffen *****************************************************************************/ class ErrorHandler @@ -47,10 +47,8 @@ class ErrorHandler // Log the error in the database. self::logError($error_message, $debug_info, $file, $line); - // Are we considering this fatal? Then display and exit. - // !!! TODO: should we consider warnings fatal? - if (true) // DEBUG || (!DEBUG && $error_level === E_WARNING || $error_level === E_USER_WARNING)) - self::display($file . ' (' . $line . ')Our software could not connect to the database. We apologise for any inconvenience and ask you to check back later.
'; exit; } @@ -138,7 +136,7 @@ class ErrorHandler return $error_message; } - public static function display($message, $debug_info, $is_sensitive = true) + public static function display($message, $file, $line, $debug_info, $is_sensitive = true) { $is_admin = Registry::has('user') && Registry::get('user')->isAdmin(); @@ -167,7 +165,8 @@ class ErrorHandler $is_admin = Registry::has('user') && Registry::get('user')->isAdmin(); if (DEBUG || $is_admin) { - $page->adopt(new DummyBox('An error occurred!', '' . $message . '
' . $debug_info . '')); + $debug_info = sprintf("Trigger point:\n%s (L%d)\n\n%s", $file, $line, $debug_info); + $page->adopt(new ErrorPage('An error occurred!', $message, $debug_info)); // Let's provide the admin navigation despite it all! if ($is_admin) @@ -176,9 +175,9 @@ class ErrorHandler } } elseif (!$is_sensitive) - $page->adopt(new DummyBox('An error occurred!', '
' . $message . '
')); + $page->adopt(new ErrorPage('An error occurred!', '' . $message . '
')); else - $page->adopt(new DummyBox('An error occurred!', 'Our apologies, an error occurred while we were processing your request. Please try again later, or contact us if the problem persists.
')); + $page->adopt(new ErrorPage('An error occurred!', 'Our apologies, an error occurred while we were processing your request. Please try again later, or contact us if the problem persists.')); // If we got this far, make sure we're not showing stuff twice. ob_end_clean(); 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 d276313..2df8198 100644 --- a/models/Member.php +++ b/models/Member.php @@ -196,6 +196,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/Registry.php b/models/Registry.php index 849bd16..3f88bd2 100644 --- a/models/Registry.php +++ b/models/Registry.php @@ -24,7 +24,7 @@ class Registry public static function get($key) { if (!isset(self::$storage[$key])) - trigger_error('Key does not exist in Registry: ' . $key, E_USER_ERROR); + throw new Exception('Key does not exist in Registry: ' . $key); return self::$storage[$key]; } @@ -32,7 +32,7 @@ class Registry public static function remove($key) { if (!isset(self::$storage[$key])) - trigger_error('Key does not exist in Registry: ' . $key, E_USER_ERROR); + throw new Exception('Key does not exist in Registry: ' . $key); unset(self::$storage[$key]); } diff --git a/models/Session.php b/models/Session.php index d1f6e04..91f7747 100644 --- a/models/Session.php +++ b/models/Session.php @@ -33,7 +33,7 @@ class Session public static function getSessionToken() { if (empty($_SESSION['session_token'])) - trigger_error('Call to getSessionToken without a session token being set!', E_USER_ERROR); + throw new Exception('Call to getSessionToken without a session token being set!'); return $_SESSION['session_token']; } @@ -41,7 +41,7 @@ class Session public static function getSessionTokenKey() { if (empty($_SESSION['session_token_key'])) - trigger_error('Call to getSessionTokenKey without a session token key being set!', E_USER_ERROR); + throw new Exception('Call to getSessionTokenKey without a session token key being set!'); return $_SESSION['session_token_key']; } diff --git a/models/Tag.php b/models/Tag.php index 145db1e..dac43fd 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'); @@ -276,7 +281,7 @@ class Tag $data); if (!$res) - trigger_error('Could not create the requested tag.', E_USER_ERROR); + throw new Exception('Could not create the requested tag.'); $data['id_tag'] = $db->insert_id(); return $return_format === 'object' ? new Tag($data) : $data; @@ -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/ErrorPage.php b/templates/ErrorPage.php new file mode 100644 index 0000000..ee27f21 --- /dev/null +++ b/templates/ErrorPage.php @@ -0,0 +1,41 @@ +title = $title; + $this->message = $message; + $this->debug_info = $debug_info; + } + + public function html_main() + { + echo ' +', nl2br(htmlspecialchars($this->message)), '
'; + + if (isset($this->debug_info)) + { + echo ' +', htmlspecialchars($this->debug_info), ''; + } + + echo ' +