Merge branch 'master' into password-reset

This commit is contained in:
2025-03-02 15:00:34 +01:00
22 changed files with 444 additions and 514 deletions

View File

@@ -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);

View File

@@ -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

View File

@@ -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 . '\'';

View File

@@ -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

View File

@@ -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);
}
];

View File

@@ -38,40 +38,33 @@ class ManageAssets extends HTMLController
'checkbox' => [
'header' => '<input type="checkbox" id="selectall">',
'is_sortable' => false,
'parse' => [
'type' => 'function',
'data' => function($row) {
return '<input type="checkbox" class="asset_select" name="delete[]" value="' . $row['id_asset'] . '">';
},
],
'format' => fn($row) =>
'<input type="checkbox" class="asset_select" name="delete[]" value="' . $row['id_asset'] . '">',
],
'thumbnail' => [
'header' => '&nbsp;',
'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('<img src="%s" style="width: %s; height: %s;">', $thumb, $width, $height);
},
],
$width = isset($width) ? $width . 'px' : 'auto';
$height = isset($height) ? $height . 'px' : 'auto';
return sprintf('<img src="%s" style="width: %s; height: %s;">', $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('<a href="%s/edituser/?id=%d">%s</a>', BASEURL, $row['id_user'],
$row['first_name'] . ' ' . $row['surname']);
else
return 'n/a';
},
],
'format' => function($row) {
if (!empty($row['id_user']))
return sprintf('<a href="%s/edituser/?id=%d">%s</a>', 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',
];

View File

@@ -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'] . '<br>' .
'<div><a onclick="this.parentNode.childNodes[1].style.display=\'block\';this.style.display=\'none\';">Show debug info</a>' .
'<pre style="display: none">' . htmlspecialchars($row['debug_info']) .
'</pre></div>' .
'<small><a href="' . BASEURL .
htmlspecialchars($row['request_uri']) . '">' .
htmlspecialchars($row['request_uri']) . '</a></small>';
}
],
'header' => 'Message / URL',
'is_sortable' => false,
'format' => function($row) {
return $row['message'] . '<br>' .
'<div><a onclick="this.parentNode.childNodes[1].style.display=\'block\';this.style.display=\'none\';">Show debug info</a>' .
'<pre style="display: none">' . htmlspecialchars($row['debug_info']) .
'</pre></div>' .
'<small><a href="' . BASEURL .
htmlspecialchars($row['request_uri']) . '">' .
htmlspecialchars($row['request_uri']) . '</a></small>';
},
],
'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);

View File

@@ -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('<a href="%s/edituser/?id=%d">%s</a>', BASEURL, $row['id_user'],
$row['first_name'] . ' ' . $row['surname']);
else
return 'n/a';
},
],
'format' => function($row) {
if (!empty($row['id_user']))
return sprintf('<a href="%s/edituser/?id=%d">%s</a>', 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);
}
];

View File

@@ -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);