From 07bc784859c36bcc176b27c04b2996cdca5bb73f Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 12:58:30 +0100 Subject: [PATCH 01/79] Add bootstrap as a dependency --- composer.json | 5 ++++- public/vendor | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) create mode 120000 public/vendor diff --git a/composer.json b/composer.json index 2ad1e40..a4b52e4 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,9 @@ "ext-mysqli": "*", "ext-imagick": "*", "ext-gd": "*", - "ext-fileinfo": "*" + "ext-imagick": "*", + "ext-mysqli": "*", + "twbs/bootstrap": "^5.3", + "twbs/bootstrap-icons": "^1.10" } } diff --git a/public/vendor b/public/vendor new file mode 120000 index 0000000..42a408b --- /dev/null +++ b/public/vendor @@ -0,0 +1 @@ +../vendor/ \ No newline at end of file From daf6b6b26408a6166831fde7ecd4b4e72df8528b Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 13:12:12 +0100 Subject: [PATCH 02/79] MainTemplate: clean up HTML head; remove unused inline CSS function --- templates/MainTemplate.php | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/templates/MainTemplate.php b/templates/MainTemplate.php index 87a1c0d..b5224fb 100644 --- a/templates/MainTemplate.php +++ b/templates/MainTemplate.php @@ -25,14 +25,22 @@ class MainTemplate extends Template echo ' - ', $this->title, '', !empty($this->canonical_url) ? ' - ' : '', ' + ', $this->title, ''; + + if (!empty($this->canonical_url)) + echo ' + '; + + echo ' + + '; + + echo ' + + - - ', !empty($this->css) ? ' - ' : '', $this->header_html, ' - + ' + , $this->header_html, ' classes) ? ' class="' . implode(' ', $this->classes) . '"' : '', '>
@@ -84,11 +92,6 @@ class MainTemplate extends Template '; } - public function appendCss($css) - { - $this->css .= $css; - } - public function appendHeaderHtml($html) { $this->header_html .= "\n\t\t" . $html; From f9eefe7b4192452834193064295eea7c5b13e5b3 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 13:20:59 +0100 Subject: [PATCH 03/79] Replace generic alert, form and table templates with new Bootstrap equivalents --- controllers/ManageAlbums.php | 4 +- controllers/ManageErrors.php | 4 +- controllers/ManageTags.php | 4 +- controllers/ManageUsers.php | 4 +- controllers/ViewPeople.php | 2 +- controllers/ViewPhotoAlbum.php | 2 +- controllers/ViewTimeline.php | 2 +- models/Form.php | 341 +++++++++++++++++++++++++-------- models/GenericTable.php | 14 +- public/css/admin.css | 139 -------------- public/css/default.css | 156 +-------------- templates/AlbumIndex.php | 2 +- templates/Alert.php | 20 +- templates/FormView.php | 261 ++++++++++++++++++------- templates/PageIndexWidget.php | 61 ++++++ templates/Pagination.php | 64 ------- templates/PhotosIndex.php | 2 +- templates/TabularData.php | 172 +++++++++++++---- 18 files changed, 681 insertions(+), 573 deletions(-) create mode 100644 templates/PageIndexWidget.php delete mode 100644 templates/Pagination.php diff --git a/controllers/ManageAlbums.php b/controllers/ManageAlbums.php index f6a1b59..f989e6a 100644 --- a/controllers/ManageAlbums.php +++ b/controllers/ManageAlbums.php @@ -18,7 +18,7 @@ class ManageAlbums extends HTMLController 'form' => [ 'action' => BASEURL . '/editalbum/', 'method' => 'get', - 'class' => 'floatright', + 'class' => 'float-end', 'buttons' => [ 'add' => [ 'type' => 'submit', @@ -60,7 +60,7 @@ class ManageAlbums extends HTMLController 'title' => 'Manage albums', 'no_items_label' => 'No albums meet the requirements of the current filter.', 'items_per_page' => 9999, - 'index_class' => 'floatleft', + 'index_class' => 'float-start', 'base_url' => BASEURL . '/managealbums/', 'get_data' => function($offset = 0, $limit = 9999, $order = '', $direction = 'up') { if (!in_array($order, ['id_tag', 'tag', 'slug', 'count'])) diff --git a/controllers/ManageErrors.php b/controllers/ManageErrors.php index d45d37f..9619753 100644 --- a/controllers/ManageErrors.php +++ b/controllers/ManageErrors.php @@ -29,7 +29,7 @@ class ManageErrors extends HTMLController 'form' => [ 'action' => BASEURL . '/manageerrors/?' . Session::getSessionTokenKey() . '=' . Session::getSessionToken(), 'method' => 'post', - 'class' => 'floatright', + 'class' => 'float-end', 'buttons' => [ 'flush' => [ 'type' => 'submit', @@ -99,7 +99,7 @@ class ManageErrors extends HTMLController 'sort_direction' => !empty($_GET['dir']) ? $_GET['dir'] : '', 'no_items_label' => "No errors to display -- we're all good!", 'items_per_page' => 20, - 'index_class' => 'floatleft', + 'index_class' => 'float-start', 'base_url' => BASEURL . '/manageerrors/', 'get_count' => 'ErrorLog::getCount', 'get_data' => function($offset = 0, $limit = 20, $order = '', $direction = 'down') { diff --git a/controllers/ManageTags.php b/controllers/ManageTags.php index b600f2d..a49f9aa 100644 --- a/controllers/ManageTags.php +++ b/controllers/ManageTags.php @@ -18,7 +18,7 @@ class ManageTags extends HTMLController 'form' => [ 'action' => BASEURL . '/edittag/', 'method' => 'get', - 'class' => 'floatright', + 'class' => 'float-end', 'buttons' => [ 'add' => [ 'type' => 'submit', @@ -65,7 +65,7 @@ class ManageTags extends HTMLController 'title' => 'Manage tags', 'no_items_label' => 'No tags meet the requirements of the current filter.', 'items_per_page' => 30, - 'index_class' => 'floatleft', + 'index_class' => 'float-start', 'base_url' => BASEURL . '/managetags/', 'get_data' => function($offset = 0, $limit = 30, $order = '', $direction = 'up') { if (!in_array($order, ['id_tag', 'tag', 'slug', 'kind', 'count'])) diff --git a/controllers/ManageUsers.php b/controllers/ManageUsers.php index d7fadbd..14ef97f 100644 --- a/controllers/ManageUsers.php +++ b/controllers/ManageUsers.php @@ -18,7 +18,7 @@ class ManageUsers extends HTMLController 'form' => [ 'action' => BASEURL . '/edituser/', 'method' => 'get', - 'class' => 'floatright', + 'class' => 'float-end', 'buttons' => [ 'add' => [ 'type' => 'submit', @@ -94,7 +94,7 @@ class ManageUsers extends HTMLController 'title' => 'Manage users', 'no_items_label' => 'No users meet the requirements of the current filter.', 'items_per_page' => 30, - 'index_class' => 'floatleft', + 'index_class' => 'float-start', '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'])) diff --git a/controllers/ViewPeople.php b/controllers/ViewPeople.php index bf7d8ba..3829327 100644 --- a/controllers/ViewPeople.php +++ b/controllers/ViewPeople.php @@ -53,7 +53,7 @@ class ViewPeople extends HTMLController 'base_url' => BASEURL . '/people/', 'page_slug' => 'page/%PAGE%/', ]); - $this->page->adopt(new Pagination($pagination)); + $this->page->adopt(new PageIndexWidget($pagination)); $this->page->setCanonicalUrl(BASEURL . '/people/' . ($page > 1 ? 'page/' . $page . '/' : '')); } diff --git a/controllers/ViewPhotoAlbum.php b/controllers/ViewPhotoAlbum.php index aa0f10a..73c84e2 100644 --- a/controllers/ViewPhotoAlbum.php +++ b/controllers/ViewPhotoAlbum.php @@ -112,7 +112,7 @@ class ViewPhotoAlbum extends HTMLController 'base_url' => BASEURL . '/' . (isset($_GET['tag']) ? $_GET['tag'] . '/' : ''), 'page_slug' => 'page/%PAGE%/', ]); - $this->page->adopt(new Pagination($index)); + $this->page->adopt(new PageIndexWidget($index)); } // Set the canonical url. diff --git a/controllers/ViewTimeline.php b/controllers/ViewTimeline.php index 3aa29b9..bf9d56e 100644 --- a/controllers/ViewTimeline.php +++ b/controllers/ViewTimeline.php @@ -47,7 +47,7 @@ class ViewTimeline extends HTMLController 'base_url' => BASEURL . '/timeline/', 'page_slug' => 'page/%PAGE%/', ]); - $this->page->adopt(new Pagination($index)); + $this->page->adopt(new PageIndexWidget($index)); } // Set the canonical url. diff --git a/models/Form.php b/models/Form.php index 62952ae..3558533 100644 --- a/models/Form.php +++ b/models/Form.php @@ -3,7 +3,8 @@ * Form.php * Contains key class Form. * - * Kabuki CMS (C) 2013-2015, Aaron van Geffen + * Global Data Lab code (C) Radboud University Nijmegen + * Programming (C) Aaron van Geffen, 2015-2022 *****************************************************************************/ class Form @@ -12,9 +13,11 @@ class Form public $request_url; public $content_above; public $content_below; - private $fields; - private $data; - private $missing; + private $fields = []; + private $data = []; + private $missing = []; + private $submit_caption; + private $trim_inputs; // NOTE: this class does not verify the completeness of form options. public function __construct($options) @@ -24,9 +27,42 @@ class Form $this->fields = !empty($options['fields']) ? $options['fields'] : []; $this->content_below = !empty($options['content_below']) ? $options['content_below'] : null; $this->content_above = !empty($options['content_above']) ? $options['content_above'] : null; + $this->submit_caption = !empty($options['submit_caption']) ? $options['submit_caption'] : 'Save information'; + $this->trim_inputs = !empty($options['trim_inputs']); } - public function verify($post) + public function getFields() + { + return $this->fields; + } + + public function getData() + { + return $this->data; + } + + public function getSubmitButtonCaption() + { + return $this->submit_caption; + } + + public function getMissing() + { + return $this->missing; + } + + public function setData($data) + { + $this->verify($data, true); + $this->missing = []; + } + + public function setFieldAsMissing($field) + { + $this->missing[] = $field; + } + + public function verify($post, $initalisation = false) { $this->data = []; $this->missing = []; @@ -41,30 +77,43 @@ class Form } // No data present at all for this field? - if ((!isset($post[$field_id]) || $post[$field_id] == '') && empty($field['is_optional'])) + if ((!isset($post[$field_id]) || $post[$field_id] == '') && + $field['type'] !== 'captcha') { - $this->missing[] = $field_id; - $this->data[$field_id] = ''; + if (empty($field['is_optional'])) + $this->missing[] = $field_id; + + if ($field['type'] === 'select' && !empty($field['multiple'])) + $this->data[$field_id] = []; + else + $this->data[$field_id] = ''; + continue; } - // Verify data for all fields + // Should we trim this? + if ($this->trim_inputs && $field['type'] !== 'captcha' && empty($field['multiple'])) + $post[$field_id] = trim($post[$field_id]); + + // Using a custom validation function? + if (isset($field['validate']) && is_callable($field['validate'])) + { + // Validation functions can clean up the data if passed by reference + $this->data[$field_id] = $post[$field_id]; + + // Evaluate validation functions as boolean to see if data is missing + if (!$field['validate']($post[$field_id])) + $this->missing[] = $field_id; + + continue; + } + + // Verify data by field type switch ($field['type']) { case 'select': case 'radio': - // Skip validation? Dangerous territory! - if (isset($field['verify_options']) && $field['verify_options'] === false) - $this->data[$field_id] = $post[$field_id]; - // Check whether selected option is valid. - elseif (isset($post[$field_id]) && !isset($field['options'][$post[$field_id]])) - { - $this->missing[] = $field_id; - $this->data[$field_id] = ''; - continue 2; - } - else - $this->data[$field_id] = $post[$field_id]; + $this->validateSelect($field_id, $field, $post); break; case 'checkbox': @@ -73,61 +122,22 @@ class Form break; case 'color': - // Colors are stored as a string of length 3 or 6 (hex) - if (!isset($post[$field_id]) || (strlen($post[$field_id]) != 3 && strlen($post[$field_id]) != 6)) - { - $this->missing[] = $field_id; - $this->data[$field_id] = ''; - continue 2; - } - else - $this->data[$field_id] = $post[$field_id]; + $this->validateColor($field_id, $field, $post); break; case 'file': - // Needs to be verified elsewhere! + // Asset needs to be processed out of POST! This is just a filename. + $this->data[$field_id] = isset($post[$field_id]) ? $post[$field_id] : ''; break; case 'numeric': - $data = isset($post[$field_id]) ? $post[$field_id] : ''; - // Do we need to check bounds? - if (isset($field['min_value']) && is_numeric($data)) - { - if (is_float($field['min_value']) && (float) $data < $field['min_value']) - { - $this->missing[] = $field_id; - $this->data[$field_id] = 0.0; - } - elseif (is_int($field['min_value']) && (int) $data < $field['min_value']) - { - $this->missing[] = $field_id; - $this->data[$field_id] = 0; - } - else - $this->data[$field_id] = $data; - } - elseif (isset($field['max_value']) && is_numeric($data)) - { - if (is_float($field['max_value']) && (float) $data > $field['max_value']) - { - $this->missing[] = $field_id; - $this->data[$field_id] = 0.0; - } - elseif (is_int($field['max_value']) && (int) $data > $field['max_value']) - { - $this->missing[] = $field_id; - $this->data[$field_id] = 0; - } - else - $this->data[$field_id] = $data; - } - // Does it look numeric? - elseif (is_numeric($data)) - { - $this->data[$field_id] = $data; - } - // Let's consider it missing, then. - else + $this->validateNumeric($field_id, $field, $post); + break; + + case 'captcha': + if (isset($_POST['g-recaptcha-response']) && !$initalisation) + $this->validateCaptcha($field_id); + elseif (!$initalisation) { $this->missing[] = $field_id; $this->data[$field_id] = 0; @@ -137,29 +147,200 @@ class Form case 'text': case 'textarea': default: - $this->data[$field_id] = isset($post[$field_id]) ? $post[$field_id] : ''; + $this->validateText($field_id, $field, $post); } } } - public function setData($data) + private function validateCaptcha($field_id) { - $this->verify($data); - $this->missing = []; + $postdata = http_build_query([ + 'secret' => RECAPTCHA_API_SECRET, + 'response' => $_POST['g-recaptcha-response'], + ]); + + $opts = [ + 'http' => [ + 'method' => 'POST', + 'header' => 'Content-type: application/x-www-form-urlencoded', + 'content' => $postdata, + ] + ]; + + $context = stream_context_create($opts); + $result = file_get_contents('https://www.google.com/recaptcha/api/siteverify', false, $context); + $check = json_decode($result); + + if ($check->success) + { + $this->data[$field_id] = 1; + } + else + { + $this->data[$field_id] = 0; + $this->missing[] = $field_id; + } } - public function getFields() + private function validateColor($field_id, array $field, array $post) { - return $this->fields; + // Colors are stored as a string of length 3 or 6 (hex) + if (!isset($post[$field_id]) || (strlen($post[$field_id]) != 3 && strlen($post[$field_id]) != 6)) + { + $this->missing[] = $field_id; + $this->data[$field_id] = ''; + } + else + $this->data[$field_id] = $post[$field_id]; } - public function getData() + private function validateNumeric($field_id, array $field, array $post) { - return $this->data; + $data = isset($post[$field_id]) ? $post[$field_id] : ''; + + // Sanity check: does this even look numeric? + if (!is_numeric($data)) + { + $this->missing[] = $field_id; + $this->data[$field_id] = 0; + return; + } + + // Do we need to a minimum bound? + if (isset($field['min_value'])) + { + if (is_float($field['min_value']) && (float) $data < $field['min_value']) + { + $this->missing[] = $field_id; + $this->data[$field_id] = 0.0; + } + elseif (is_int($field['min_value']) && (int) $data < $field['min_value']) + { + $this->missing[] = $field_id; + $this->data[$field_id] = 0; + } + } + + // What about a maximum bound? + if (isset($field['max_value'])) + { + if (is_float($field['max_value']) && (float) $data > $field['max_value']) + { + $this->missing[] = $field_id; + $this->data[$field_id] = 0.0; + } + elseif (is_int($field['max_value']) && (int) $data > $field['max_value']) + { + $this->missing[] = $field_id; + $this->data[$field_id] = 0; + } + } + + $this->data[$field_id] = $data; } - public function getMissing() + private function validateSelect($field_id, array $field, array $post) { - return $this->missing; + // Skip validation? Dangerous territory! + if (isset($field['verify_options']) && $field['verify_options'] === false) + { + $this->data[$field_id] = $post[$field_id]; + return; + } + + // Check whether selected option is valid. + if (($field['type'] !== 'select' || empty($field['multiple'])) && empty($field['has_groups'])) + { + if (isset($post[$field_id]) && !isset($field['options'][$post[$field_id]])) + { + $this->missing[] = $field_id; + $this->data[$field_id] = ''; + return; + } + else + $this->data[$field_id] = $post[$field_id]; + } + // Multiple selections involve a bit more work. + elseif (!empty($field['multiple']) && empty($field['has_groups'])) + { + $this->data[$field_id] = []; + if (!is_array($post[$field_id])) + { + if (isset($field['options'][$post[$field_id]])) + $this->data[$field_id][] = $post[$field_id]; + else + $this->missing[] = $field_id; + return; + } + + foreach ($post[$field_id] as $option) + { + if (isset($field['options'][$option])) + $this->data[$field_id][] = $option; + } + + if (empty($this->data[$field_id])) + $this->missing[] = $field_id; + } + // Any optgroups involved? + elseif (!empty($field['has_groups'])) + { + if (!isset($post[$field_id])) + { + $this->missing[] = $field_id; + $this->data[$field_id] = ''; + return; + } + + // Expensive: iterate over all groups until the value selected has been found. + foreach ($field['options'] as $label => $options) + { + if (is_array($options)) + { + // Consider each of the options as a valid a value. + foreach ($options as $value => $label) + { + if ($post[$field_id] === $value) + { + $this->data[$field_id] = $options; + return; + } + } + } + else + { + // This is an ungrouped value in disguise! Treat it as such. + if ($post[$field_id] === $options) + { + $this->data[$field_id] = $options; + return; + } + else + continue; + } + } + + // If we've reached this point, we'll consider the data invalid. + $this->missing[] = $field_id; + $this->data[$field_id] = ''; + } + else + { + throw new UnexpectedValueException('Unexpected field configuration in validateSelect!'); + } + } + + private function validateText($field_id, array $field, array $post) + { + $this->data[$field_id] = isset($post[$field_id]) ? $post[$field_id] : ''; + + // Trim leading and trailing whitespace? + if (!empty($field['trim'])) + $this->data[$field_id] = trim($this->data[$field_id]); + + // Is there a length limit to enforce? + if (isset($field['maxlength']) && strlen($post[$field_id]) > $field['maxlength']) { + $post[$field_id] = substr($post[$field_id], 0, $field['maxlength']); + } } } diff --git a/models/GenericTable.php b/models/GenericTable.php index f2147f7..d98220a 100644 --- a/models/GenericTable.php +++ b/models/GenericTable.php @@ -3,7 +3,8 @@ * GenericTable.php * Contains key class GenericTable. * - * Kabuki CMS (C) 2013-2015, Aaron van Geffen + * Global Data Lab code (C) Radboud University Nijmegen + * Programming (C) Aaron van Geffen, 2015-2021 *****************************************************************************/ class GenericTable @@ -19,7 +20,7 @@ class GenericTable public $form_above; public $form_below; - + private $table_class; private $sort_direction; private $sort_order; private $base_url; @@ -84,6 +85,8 @@ class GenericTable else $this->body = $options['no_items_label'] ?? ''; + $this->table_class = $options['table_class'] ?? ''; + // Got a title? $this->title = $options['title'] ?? ''; $this->title_class = $options['title_class'] ?? ''; @@ -105,6 +108,7 @@ class GenericTable $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, 'label' => $column['header'], @@ -168,6 +172,11 @@ class GenericTable return $this->pageIndex; } + public function getTableClass() + { + return $this->table_class; + } + public function getTitle() { return $this->title; @@ -196,6 +205,7 @@ class GenericTable // Append the cell to the row. $newRow['cells'][] = [ + 'class' => $column['cell_class'] ?? '', 'value' => $value, ]; } diff --git a/public/css/admin.css b/public/css/admin.css index f3f3b90..93007cc 100644 --- a/public/css/admin.css +++ b/public/css/admin.css @@ -11,13 +11,6 @@ margin: 0 0 0.2em; } -.floatleft { - float: left; -} -.floatright { - float: right; -} - /* Admin bar styles ---------------------*/ body { @@ -81,22 +74,6 @@ body { } -/* Edit user screen ----------------------*/ -.edituser dt { - clear: left; - float: left; - width: 150px; -} -.edituser dd { - float: left; - margin-bottom: 5px; -} -.edituser form div:last-child { - padding: 1em 0 0; -} - - /* Admin widgets ------------------*/ .widget { @@ -195,119 +172,3 @@ body { top: 400px; left: 300px; } - - -/* The pagination styles below are based on Bootstrap 2.3.2 --------------------------------------------------------------*/ - -.table_pagination, .table_form { - margin: 20px 0; -} - -.table_pagination ul { - display: inline-block; - margin: 0; - padding: 0; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.table_pagination ul > li { - display: inline; -} - -.table_pagination ul > li > a, -.table_pagination ul > li > span { - float: left; - padding: 4px 12px; - line-height: 20px; - text-decoration: none; - background-color: #ffffff; - border: 1px solid #dddddd; - border-left-width: 0; -} - -.table_pagination ul > li > a:hover, -.table_pagination ul > li > a:focus, -.table_pagination ul > .active > a, -.table_pagination ul > .active > span { - background-color: #f5f5f5; -} - -.table_pagination ul > .active > a, -.table_pagination ul > .active > span { - color: #999999; - cursor: default; -} - -.table_pagination ul > .disabled > span, -.table_pagination ul > .disabled > a, -.table_pagination ul > .disabled > a:hover, -.table_pagination ul > .disabled > a:focus { - color: #999999; - cursor: default; - background-color: transparent; -} - -.table_pagination ul > li:first-child > a, -.table_pagination ul > li:first-child > span { - border-left-width: 1px; -} - - -/* The table styles below were taken from Bootstrap 2.3.2 ------------------------------------------------------------*/ -table { - max-width: 100%; - background-color: transparent; - border-collapse: collapse; - border-spacing: 0; -} - -.table { - width: 100%; - margin-bottom: 20px; -} - -.table th, -.table td { - border-top: 1px solid #dddddd; - line-height: 20px; - padding: 8px; - text-align: left; - vertical-align: top; -} - -.table th { - font-weight: bold; -} - -.table thead th { - vertical-align: bottom; -} - -.table caption + thead tr:first-child th, -.table caption + thead tr:first-child td, -.table colgroup + thead tr:first-child th, -.table colgroup + thead tr:first-child td, -.table thead:first-child tr:first-child th, -.table thead:first-child tr:first-child td { - border-top: 0; -} - -.table tbody + tbody { - border-top: 2px solid #dddddd; -} - -.table .table { - background-color: #ffffff; -} - -.table-striped tbody > tr:nth-child(odd) > td, -.table-striped tbody > tr:nth-child(odd) > th { - background-color: #f9f9f9; -} - -.table-hover tbody tr:hover > td, -.table-hover tbody tr:hover > th { - background-color: #f5f5f5; -} diff --git a/public/css/default.css b/public/css/default.css index 0aadb89..f4ecab9 100644 --- a/public/css/default.css +++ b/public/css/default.css @@ -15,7 +15,7 @@ } body { - font: 13px/1.7 "Open Sans", sans-serif; + font-family: "Open Sans", sans-serif; padding: 0 0 3em; margin: 0; background: #aaa 0 -50% fixed; @@ -94,51 +94,6 @@ ul#nav li a:hover { } -/* Pagination ----------------*/ -.pagination { - clear: both; - text-align: center; -} -.pagination ul { - display: inline-block; - margin: 0; - padding: 0; - box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); -} -.pagination ul > li { - display: inline; -} -.pagination ul > li > a, .pagination ul > li > span { - float: left; - font: 300 18px/2.2 "Open Sans", sans-serif; - padding: 6px 22px; - text-decoration: none; - background-color: #fff; - border-right: 1px solid #ddd; -} - -.pagination ul > li > a:hover, .pagination ul > li > a:focus, -.pagination ul > .active > a, .pagination ul > .active > span { - background-color: #eee; -} - -.pagination ul > .active > a, .pagination ul > .active > span { - cursor: default; -} - -.pagination ul > .disabled > span, .pagination ul > .disabled > a, -.pagination ul > .disabled > a:hover, .pagination ul > .disabled > a:focus { - color: #999; - cursor: default; - background-color: transparent; -} - -.pagination .page-padding { - cursor: pointer; -} - - /* Tiled grid ---------------*/ .tiled_header { @@ -372,51 +327,6 @@ footer a { /* Input ----------*/ - -input, select, .btn { - background: #fff; - border: 1px solid #dbdbdb; - border-radius: 4px; - color: #000; - font: 13px/1.7 "Open Sans", "Helvetica", sans-serif; - padding: 3px; -} -textarea { - border: 1px solid #dbdbdb; - border-radius: 4px; - font: 14px/1.4 'Inconsolata', 'DejaVu Sans Mono', monospace; - padding: 0.75%; -} - -input[type=submit], button, .btn { - background-color: #eee; - border-color: #dbdbdb; - border-width: 1px; - border-radius: 4px; - color: #363636; - cursor: pointer; - display: inline-block; - justify-content: center; - padding-bottom: calc(0.4em - 1px); - padding-left: 0.8em; - padding-right: 0.8em; - padding-top: calc(0.4em - 1px); - text-align: center; - white-space: nowrap; -} -input:hover, select:hover, button:hover, .btn:hover { - border-color: #b5b5b5; -} -input:focus, select:focus, button:focus, .btn:focus { - border-color: #3273dc; -} -input:focus:not(:active), select:focus:not(:active), button:focus:not(:active), .btn:focus:not(:active) { - box-shadow: 0px 0px 0px 2px rgba(50, 115, 220, 0.25); -} -input:active, select:active, button:active, .btn:active { - border-color: #4a4a4a; -} - .btn-red { background: #eebbaa; border-color: #cc9988; @@ -474,70 +384,6 @@ input:active, select:active, button:active, .btn:active { } -/* Alert boxes -- styling borrowed from Bootstrap 2 ------------------------------------------------------*/ -.alert { - padding: 8px 35px 8px 14px; - margin-bottom: 20px; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - background-color: #fcf8e3; - border: 1px solid #fbeed5; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} -.alert, -.alert h4 { - color: #c09853; -} -.alert h4 { - margin: 0; -} -.alert .close { - position: relative; - top: -2px; - right: -21px; - line-height: 20px; -} -.alert-success { - background-color: #dff0d8; - border-color: #d6e9c6; - color: #468847; -} -.alert-success h4 { - color: #468847; -} -.alert-danger, -.alert-error { - background-color: #f2dede; - border-color: #eed3d7; - color: #b94a48; -} -.alert-danger h4, -.alert-error h4 { - color: #b94a48; -} -.alert-info { - background-color: #d9edf7; - border-color: #bce8f1; - color: #3a87ad; -} -.alert-info h4 { - color: #3a87ad; -} -.alert-block { - padding-top: 14px; - padding-bottom: 14px; -} -.alert-block > p, -.alert-block > ul { - margin-bottom: 0; -} -.alert-block p + p { - margin-top: 5px; -} - - /* Styling for the photo pages --------------------------------*/ #photo_frame { diff --git a/templates/AlbumIndex.php b/templates/AlbumIndex.php index 29a856b..7fdcf64 100644 --- a/templates/AlbumIndex.php +++ b/templates/AlbumIndex.php @@ -26,7 +26,7 @@ class AlbumIndex extends SubTemplate protected function html_content() { echo ' -
'; +
'; foreach (array_chunk($this->albums, 3) as $photos) { diff --git a/templates/Alert.php b/templates/Alert.php index d5a2091..4f24031 100644 --- a/templates/Alert.php +++ b/templates/Alert.php @@ -6,7 +6,7 @@ * Kabuki CMS (C) 2013-2015, Aaron van Geffen *****************************************************************************/ -class Alert extends SubTemplate +class Alert extends Template { private $_type; private $_message; @@ -16,20 +16,20 @@ class Alert extends SubTemplate { $this->_title = $title; $this->_message = $message; - $this->_type = in_array($type, ['alert', 'error', 'success', 'info']) ? $type : 'alert'; + $this->_type = in_array($type, ['success', 'info', 'warning', 'danger']) ? $type : 'info'; } - protected function html_content() + public function html_main() { echo ' -
', (!empty($this->_title) ? ' - ' . $this->_title . '
' : ''), '

', $this->_message, '

'; - - $this->additional_alert_content(); - - echo '
'; +
' + , !empty($this->_title) ? '' . $this->_title . '
' : '', ' + ', $this->_message, + $this->additional_alert_content(), ' +
'; } protected function additional_alert_content() - {} + { + } } diff --git a/templates/FormView.php b/templates/FormView.php index 389fa11..5b775ef 100644 --- a/templates/FormView.php +++ b/templates/FormView.php @@ -3,52 +3,42 @@ * FormView.php * Contains the form template. * - * Kabuki CMS (C) 2013-2015, Aaron van Geffen + * Global Data Lab code (C) Radboud University Nijmegen + * Programming (C) Aaron van Geffen, 2015-2022 *****************************************************************************/ class FormView extends SubTemplate { - private $content_below; - private $content_above; - private $data; - private $missing; - private $fields; - private $request_method; - private $request_url; + private $form; + private array $data; + private array $missing; private $title; public function __construct(Form $form, $title = '') { + $this->form = $form; $this->title = $title; - $this->request_url = $form->request_url; - $this->request_method = $form->request_method; - $this->fields = $form->getFields(); - $this->missing = $form->getMissing(); - $this->data = $form->getData(); - $this->content_above = $form->content_above; - $this->content_below = $form->content_below; } protected function html_content($exclude = [], $include = []) { if (!empty($this->title)) echo ' -
-

', htmlspecialchars($this->title), '

'; +

', $this->title, '

'; foreach ($this->_subtemplates as $template) $template->html_main(); echo ' -
'; + '; - if (isset($this->content_above)) - echo $this->content_above; + if (isset($this->form->content_above)) + echo $this->form->content_above; - echo ' -
'; + $this->missing = $this->form->getMissing(); + $this->data = $this->form->getData(); - foreach ($this->fields as $field_id => $field) + foreach ($this->form->getFields() as $field_id => $field) { // Either we have a blacklist if (!empty($exclude) && in_array($field_id, $exclude)) @@ -62,107 +52,230 @@ class FormView extends SubTemplate } echo ' -
-
- '; +
+
+ '; - if (isset($this->content_below)) + if (isset($this->form->content_below)) echo ' - ', $this->content_below; + ', $this->form->content_below; echo ' +
'; - - if (!empty($this->title)) - echo ' -
'; } - protected function renderField($field_id, $field) + protected function renderField($field_id, array $field) { if (isset($field['before_html'])) - echo ' - ', $field['before_html'], ' -
'; - - if ($field['type'] != 'checkbox' && isset($field['label'])) echo ' -
missing) ? ' style="color: red"' : '', '>', $field['label'], '
'; - elseif ($field['type'] === 'checkbox' && isset($field['header'])) - echo ' -
missing) ? ' style="color: red"' : '', '>', $field['header'], '
'; + ', $field['before_html']; echo ' -
'; +
'; if (isset($field['before'])) echo $field['before']; + if ($field['type'] !== 'checkbox') + if (isset($field['label'])) + echo ' + +
'; + else + echo ' +
'; + switch ($field['type']) { case 'select': - echo ' - '; + $this->renderSelect($field_id, $field); break; case 'radio': - foreach ($field['options'] as $value => $option) - echo ' - data[$field_id] == $value ? ' checked' : '', !empty($field['disabled']) ? ' disabled' : '', '> ', htmlentities($option); + $this->renderRadio($field_id, $field); break; case 'checkbox': - echo ' - '; + $this->renderCheckbox($field_id, $field); break; case 'textarea': - echo ' - '; + $this->renderTextArea($field_id, $field); break; case 'color': - echo ' - '; + $this->renderColor($field_id, $field); break; case 'numeric': - echo ' - '; + $this->renderNumeric($field_id, $field); break; case 'file': - if (!empty($this->data[$field_id])) - echo '
'; + $this->renderFile($field_id, $field); + break; - echo ' - '; + case 'captcha': + $this->renderCaptcha($field_id, $field); break; case 'text': case 'password': default: - echo ' - '; + $this->renderText($field_id, $field); } if (isset($field['after'])) echo ' ', $field['after']; + if ($field['type'] !== 'checkbox') + echo ' +
'; + echo ' -
'; +
'; + } + + private function renderCaptcha($field_id, array $field) + { + echo ' +
+ '; + } + + private function renderCheckbox($field_id, array $field) + { + echo ' +
+
+ data[$field_id] ? ' checked' : '', !empty($field['disabled']) ? ' disabled' : '', ' name="', $field_id, '" id="check-', $field_id, '"> + +
+
'; + } + + private function renderColor($field_id, array $field) + { + echo ' + '; + } + + private function renderFile($field_id, array $field) + { + if (!empty($this->data[$field_id])) + echo 'Currently using asset ', $this->data[$field_id], '. Upload to overwrite.
'; + + echo ' + '; + } + + private function renderNumeric($field_id, array $field) + { + echo ' + '; + } + + private function renderRadio($field_id, array $field) + { + foreach ($field['options'] as $value => $option) + echo ' +
+ data[$field_id] == $value ? ' checked' : '', !empty($field['disabled']) ? ' disabled' : '', '> + +
'; + } + + private function renderSelect($field_id, array $field) + { + echo ' + '; + } + + private function renderSelectOption($field_id, $label, $value, $multiple = false) + { + echo ' + '; + } + + private function renderSelectOptionGroup($field_id, $label, $options) + { + echo ' + '; + + foreach ($options as $value => $option) + $this->renderSelectOption($field_id, $option, $value); + + echo ' + '; + } + + private function renderText($field_id, array $field) + { + echo ' + data[$field_id]) ? ' value="' . htmlspecialchars($this->data[$field_id]) . '"' : '', + isset($field['placeholder']) ? ' placeholder="' . $field['placeholder'] . '"' : '', + !empty($field['disabled']) ? ' disabled' : '', + isset($field['trigger']) ? ' class="trigger-' . $field['trigger'] . '"' : '', + '>'; + } + + private function renderTextArea($field_id, array $field) + { + echo ' + '; } } diff --git a/templates/PageIndexWidget.php b/templates/PageIndexWidget.php new file mode 100644 index 0000000..a10070e --- /dev/null +++ b/templates/PageIndexWidget.php @@ -0,0 +1,61 @@ +index = $index; + $this->class = $index->getPageIndexClass(); + } + + public function html_main() + { + self::paginate($this->index, $this->class); + } + + public static function paginate(PageIndex $index, $class = null) + { + $page_index = $index->getPageIndex(); + if (empty($page_index) || count($page_index) == 1) + return; + + if (!isset($class)) + $class = $index->getPageIndexClass(); + + echo ' + '; + } +} diff --git a/templates/Pagination.php b/templates/Pagination.php deleted file mode 100644 index fea1df4..0000000 --- a/templates/Pagination.php +++ /dev/null @@ -1,64 +0,0 @@ -index = $index; - $this->class = $index->getPageIndexClass(); - } - - protected function html_content() - { - $index = $this->index->getPageIndex(); - - echo ' -
-
    -
  • <', !empty($index['previous']) ? 'a href="' . $index['previous']['href'] . '"' : 'span', '>« previous
  • '; - - $num_wildcards = 0; - foreach ($index as $key => $page) - { - if (!is_numeric($key)) - continue; - - if (!is_array($page)) - { - $num_wildcards++; - echo ' -
  • ...
  • '; - } - else - echo ' -
  • ', $page['index'], '
  • '; - } - - echo ' -
  • <', !empty($index['next']) ? 'a href="' . $index['next']['href'] . '"' : 'span', '>next »
  • -
-
'; - - if ($num_wildcards) - { - echo ' - '; - } - } -} diff --git a/templates/PhotosIndex.php b/templates/PhotosIndex.php index 2ec9795..c988e54 100644 --- a/templates/PhotosIndex.php +++ b/templates/PhotosIndex.php @@ -45,7 +45,7 @@ class PhotosIndex extends SubTemplate protected function html_content() { echo ' -
'; +
'; for ($i = $this->row_limit; $i > 0 && $row = $this->mosaic->getRow(); $i--) { diff --git a/templates/TabularData.php b/templates/TabularData.php index 587d4f0..86f17de 100644 --- a/templates/TabularData.php +++ b/templates/TabularData.php @@ -3,56 +3,73 @@ * TabularData.php * Contains the template that displays tabular data. * - * Kabuki CMS (C) 2013-2015, Aaron van Geffen + * Global Data Lab code (C) Radboud University Nijmegen + * Programming (C) Aaron van Geffen, 2015-2022 *****************************************************************************/ class TabularData extends SubTemplate { - private Pagination $pager; private GenericTable $_t; public function __construct(GenericTable $table) { $this->_t = $table; - - $pageIndex = $table->getPageIndex(); - if ($pageIndex) - $this->pager = new Pagination($pageIndex); } protected function html_content() { - echo ' -
'; - $title = $this->_t->getTitle(); if (!empty($title)) + { + $titleclass = $this->_t->getTitleClass(); echo ' -

', $title, '

'; +
+

', htmlspecialchars($title), '

'; + } - // Showing a page index? - if (isset($this->pager)) - $this->pager->html_content(); + foreach ($this->_subtemplates as $template) + $template->html_main(); - // Maybe even a small form? - if (isset($this->_t->form_above)) - $this->showForm($this->_t->form_above); + // Showing an inline form? + $pager = $this->_t->getPageIndex(); + if (!empty($pager) || isset($this->_t->form_above)) + { + echo ' +
'; + + // Page index? + if (!empty($pager)) + PageIndexWidget::paginate($pager); + + // Form controls? + if (isset($this->_t->form_above)) + $this->showForm($this->_t->form_above); + + echo ' +
'; + } + + $tableClass = $this->_t->getTableClass(); + if ($tableClass) + echo ' +
'; // Build the table! echo ' - +
'; - // Show the table's headers. - foreach ($this->_t->getHeader() as $th) + // Show all headers in their full glory! + $header = $this->_t->getHeader(); + foreach ($header as $th) { echo ' 1 ? ' colspan="' . $th['colspan'] . '"' : ''), ' scope="', $th['scope'], '">', $th['href'] ? '' . $th['label'] . '' : $th['label']; - if ($th['sort_mode'] ) - echo ' ', $th['sort_mode'] === 'up' ? '↑' : '↓'; + if ($th['sort_mode']) + echo ' '; echo ''; } @@ -62,7 +79,7 @@ class TabularData extends SubTemplate '; - // Show the table's body. + // The body is what we came to see! $body = $this->_t->getBody(); if (is_array($body)) { @@ -72,51 +89,134 @@ class TabularData extends SubTemplate '; foreach ($tr['cells'] as $td) + { echo ' - ', $td['value'], ''; + '; + + if (!empty($td['class'])) + echo '', $td['value'], ''; + else + echo $td['value']; + + echo ''; + } echo ' '; } } + // !!! Sum colspan! else echo ' - + '; echo '
', $body, '', $body, '
'; - // Maybe another small form? - if (isset($this->_t->form_below)) - $this->showForm($this->_t->form_below); + if ($tableClass) + echo ' +
'; - // Showing a page index? - if (isset($this->pager)) - $this->pager->html_content(); + // Showing an inline form? + if (!empty($pager) || isset($this->_t->form_below)) + { + echo ' +
'; - echo ' + // Page index? + if (!empty($pager)) + PageIndexWidget::paginate($pager); + + // Form controls? + if (isset($this->_t->form_below)) + $this->showForm($this->_t->form_below); + + echo ' +
'; + } + + if (!empty($title)) + echo '
'; } protected function showForm($form) { echo ' -
'; + '; + + if (!empty($form['is_group'])) + echo ' +
'; if (!empty($form['fields'])) + { foreach ($form['fields'] as $name => $field) - echo ' - '; + { + if ($field['type'] === 'select') + { + echo ' + '; + } + else + echo ' + '; + + if (isset($field['html_after'])) + echo $field['html_after']; + } + } + + echo ' + '; if (!empty($form['buttons'])) foreach ($form['buttons'] as $name => $button) + { echo ' - '; + '; + + if (isset($button['html_after'])) + echo $button['html_after']; + } + + if (!empty($form['is_group'])) + echo ' +
'; echo ' -
'; + '; } } From 0366df9b5fa55682c1eedf2484557a76349fb580 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 13:30:02 +0100 Subject: [PATCH 04/79] Alerts: replace 'error' class with 'danger' --- controllers/EditAlbum.php | 4 ++-- controllers/EditTag.php | 4 ++-- controllers/EditUser.php | 12 ++++++------ controllers/Login.php | 2 +- controllers/ResetPassword.php | 4 ++-- models/Dispatcher.php | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/controllers/EditAlbum.php b/controllers/EditAlbum.php index 26e2c6a..5888876 100644 --- a/controllers/EditAlbum.php +++ b/controllers/EditAlbum.php @@ -123,7 +123,7 @@ class EditAlbum extends HTMLController // Anything missing? if (!empty($form->getMissing())) - return $formview->adopt(new Alert('Some data missing', 'Please fill out the following fields: ' . implode(', ', $form->getMissing()), 'error')); + return $formview->adopt(new Alert('Some data missing', 'Please fill out the following fields: ' . implode(', ', $form->getMissing()), 'danger')); $data = $form->getData(); @@ -140,7 +140,7 @@ class EditAlbum extends HTMLController $data['kind'] = 'Album'; $newTag = Tag::createNew($data); if ($newTag === false) - return $formview->adopt(new Alert('Cannot create this album', 'Something went wrong while creating the album...', 'error')); + return $formview->adopt(new Alert('Cannot create this album', 'Something went wrong while creating the album...', 'danger')); if (isset($_POST['submit_and_new'])) { diff --git a/controllers/EditTag.php b/controllers/EditTag.php index 24126c3..4b42926 100644 --- a/controllers/EditTag.php +++ b/controllers/EditTag.php @@ -115,7 +115,7 @@ class EditTag extends HTMLController // Anything missing? if (!empty($form->getMissing())) - return $formview->adopt(new Alert('Some data missing', 'Please fill out the following fields: ' . implode(', ', $form->getMissing()), 'error')); + return $formview->adopt(new Alert('Some data missing', 'Please fill out the following fields: ' . implode(', ', $form->getMissing()), 'danger')); $data = $form->getData(); @@ -127,7 +127,7 @@ class EditTag extends HTMLController { $return = Tag::createNew($data); if ($return === false) - return $formview->adopt(new Alert('Cannot create this tag', 'Something went wrong while creating the tag...', 'error')); + return $formview->adopt(new Alert('Cannot create this tag', 'Something went wrong while creating the tag...', 'danger')); if (isset($_POST['submit_and_new'])) { diff --git a/controllers/EditUser.php b/controllers/EditUser.php index bbce589..c5f6578 100644 --- a/controllers/EditUser.php +++ b/controllers/EditUser.php @@ -129,7 +129,7 @@ class EditUser extends HTMLController // Anything missing? if (!empty($form->getMissing())) - return $formview->adopt(new Alert('Some data missing', 'Please fill out the following fields: ' . implode(', ', $form->getMissing()), 'error')); + return $formview->adopt(new Alert('Some data missing', 'Please fill out the following fields: ' . implode(', ', $form->getMissing()), 'danger')); $data = $form->getData(); @@ -150,18 +150,18 @@ class EditUser extends HTMLController // If it looks like an e-mail address... if (!empty($data['emailaddress']) && !preg_match('~^[^ ]+@[^ ]+\.[a-z]+$~', $data['emailaddress'])) - return $formview->adopt(new Alert('Email addresses invalid', 'The email address you entered is not a valid email address.', 'error')); + return $formview->adopt(new Alert('Email addresses invalid', 'The email address you entered is not a valid email address.', 'danger')); // Check whether email address is already linked to an account in the database -- just not to the account we happen to be editing, of course. elseif (!empty($data['emailaddress']) && Member::exists($data['emailaddress']) && !($id_user && $user->getEmailAddress() == $data['emailaddress'])) - return $formview->adopt(new Alert('Email address already in use', 'Another account is already using the e-mail address you entered.', 'error')); + return $formview->adopt(new Alert('Email address already in use', 'Another account is already using the e-mail address you entered.', 'danger')); // Setting passwords? We'll need two! if (!$id_user || !empty($data['password1']) && !empty($data['password2'])) { if (strlen($data['password1']) < 6 || !preg_match('~[^A-z]~', $data['password1'])) - return $formview->adopt(new Alert('Password not acceptable', 'Please fill in a password that is at least six characters long and contains at least one non-alphabetic character (e.g. a number or symbol).', 'error')); + return $formview->adopt(new Alert('Password not acceptable', 'Please fill in a password that is at least six characters long and contains at least one non-alphabetic character (e.g. a number or symbol).', 'danger')); elseif ($data['password1'] !== $data['password2']) - return $formview->adopt(new Alert('Passwords do not match', 'The passwords you entered do not match. Please try again.', 'error')); + return $formview->adopt(new Alert('Passwords do not match', 'The passwords you entered do not match. Please try again.', 'danger')); else $data['password'] = $data['password1']; @@ -173,7 +173,7 @@ class EditUser extends HTMLController { $return = Member::createNew($data); if ($return === false) - return $formview->adopt(new Alert('Cannot create this user', 'Something went wrong while creating the user...', 'error')); + return $formview->adopt(new Alert('Cannot create this user', 'Something went wrong while creating the user...', 'danger')); if (isset($_POST['submit_and_new'])) { diff --git a/controllers/Login.php b/controllers/Login.php index 46e5da6..d91785d 100644 --- a/controllers/Login.php +++ b/controllers/Login.php @@ -44,7 +44,7 @@ class Login extends HTMLController parent::__construct('Log in - ' . SITE_TITLE); $form = new LogInForm('Log in'); if ($login_error) - $form->adopt(new Alert('', 'Invalid email address or password.', 'error')); + $form->adopt(new Alert('', 'Invalid email address or password.', 'danger')); // Tried anything? Be helpful, at least. if (isset($_POST['emailaddress'])) diff --git a/controllers/ResetPassword.php b/controllers/ResetPassword.php index 7153498..24fa7b6 100644 --- a/controllers/ResetPassword.php +++ b/controllers/ResetPassword.php @@ -48,7 +48,7 @@ class ResetPassword extends HTMLController exit; } else - $form->adopt(new Alert('Some fields require your attention', '
  • ' . implode('
  • ', $missing) . '
', 'error')); + $form->adopt(new Alert('Some fields require your attention', '
  • ' . implode('
  • ', $missing) . '
', 'danger')); } } else @@ -63,7 +63,7 @@ class ResetPassword extends HTMLController $id_user = Authentication::getUserid(trim($_POST['emailaddress'])); if ($id_user === false) { - $form->adopt(new Alert('Invalid email address', 'The email address you provided could not be found in our system. Please try again.', 'error')); + $form->adopt(new Alert('Invalid email address', 'The email address you provided could not be found in our system. Please try again.', 'danger')); return; } diff --git a/models/Dispatcher.php b/models/Dispatcher.php index d5688ab..8280797 100644 --- a/models/Dispatcher.php +++ b/models/Dispatcher.php @@ -50,7 +50,7 @@ class Dispatcher public static function kickGuest($title = null, $message = null) { $form = new LogInForm('Log in'); - $form->adopt(new Alert($title ?? '', $message ?? 'You need to be logged in to view this page.', 'error')); + $form->adopt(new Alert($title ?? '', $message ?? 'You need to be logged in to view this page.', 'danger')); $form->setRedirectUrl($_SERVER['REQUEST_URI']); $page = new MainTemplate('Login required'); From 307d34430a18118273873904b6154261175ca60a Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 13:37:59 +0100 Subject: [PATCH 05/79] SubTemplate: use SubTemplates for boxed content only --- public/css/default.css | 22 +++++++++++++++------- templates/AdminBar.php | 4 ++-- templates/AlbumButtonBox.php | 4 ++-- templates/AlbumHeaderBox.php | 4 ++-- templates/AlbumIndex.php | 4 ++-- templates/MediaUploader.php | 6 +++--- templates/PhotoPage.php | 4 ++-- templates/PhotosIndex.php | 4 ++-- templates/SubTemplate.php | 24 +++++++++++++++++++++++- 9 files changed, 53 insertions(+), 23 deletions(-) diff --git a/public/css/default.css b/public/css/default.css index f4ecab9..510683d 100644 --- a/public/css/default.css +++ b/public/css/default.css @@ -8,18 +8,15 @@ @import url(//fonts.googleapis.com/css?family=Open+Sans:400,400italic,700,700italic); @font-face { - font-family: 'Invaders'; - src: url('fonts/invaders.ttf') format('truetype'); - font-weight: normal; - font-style: normal; + font-family: 'Invaders'; + src: url('fonts/invaders.ttf') format('truetype'); + font-weight: normal; + font-style: normal; } body { font-family: "Open Sans", sans-serif; - padding: 0 0 3em; - margin: 0; background: #aaa 0 -50% fixed; - background-image: radial-gradient(ellipse at top, #ccc 0%, #aaa 55%, #333 100%); } #wrapper, header { @@ -94,6 +91,17 @@ ul#nav li a:hover { } +/* Content boxes +------------------*/ +.content-box { + background-color: #fff; + margin: 0 auto 2rem; + padding: 2rem; + border-radius: 0.5rem; + box-shadow: 0 0.25rem 0.5rem rgba(0,0,0,0.1); +} + + /* Tiled grid ---------------*/ .tiled_header { diff --git a/templates/AdminBar.php b/templates/AdminBar.php index 65662fe..daa81a8 100644 --- a/templates/AdminBar.php +++ b/templates/AdminBar.php @@ -6,11 +6,11 @@ * Kabuki CMS (C) 2013-2015, Aaron van Geffen *****************************************************************************/ -class AdminBar extends SubTemplate +class AdminBar extends Template { private $extra_items = []; - protected function html_content() + public function html_main() { echo '
diff --git a/templates/AlbumButtonBox.php b/templates/AlbumButtonBox.php index d18ef31..4126b72 100644 --- a/templates/AlbumButtonBox.php +++ b/templates/AlbumButtonBox.php @@ -6,7 +6,7 @@ * Kabuki CMS (C) 2013-2016, Aaron van Geffen *****************************************************************************/ -class AlbumButtonBox extends SubTemplate +class AlbumButtonBox extends Template { private $buttons; @@ -15,7 +15,7 @@ class AlbumButtonBox extends SubTemplate $this->buttons = $buttons; } - protected function html_content() + public function html_main() { echo '
'; diff --git a/templates/AlbumHeaderBox.php b/templates/AlbumHeaderBox.php index 0de991d..7dfccf1 100644 --- a/templates/AlbumHeaderBox.php +++ b/templates/AlbumHeaderBox.php @@ -6,7 +6,7 @@ * Kabuki CMS (C) 2013-2016, Aaron van Geffen *****************************************************************************/ -class AlbumHeaderBox extends SubTemplate +class AlbumHeaderBox extends Template { private $back_link_title; private $back_link; @@ -21,7 +21,7 @@ class AlbumHeaderBox extends SubTemplate $this->back_link_title = $back_link_title; } - protected function html_content() + public function html_main() { echo '
diff --git a/templates/AlbumIndex.php b/templates/AlbumIndex.php index 7fdcf64..56635a4 100644 --- a/templates/AlbumIndex.php +++ b/templates/AlbumIndex.php @@ -6,7 +6,7 @@ * Kabuki CMS (C) 2013-2015, Aaron van Geffen *****************************************************************************/ -class AlbumIndex extends SubTemplate +class AlbumIndex extends Template { protected $albums; protected $show_edit_buttons; @@ -23,7 +23,7 @@ class AlbumIndex extends SubTemplate $this->show_labels = $show_labels; } - protected function html_content() + public function html_main() { echo '
'; diff --git a/templates/MediaUploader.php b/templates/MediaUploader.php index 008cfc9..d577ec7 100644 --- a/templates/MediaUploader.php +++ b/templates/MediaUploader.php @@ -18,14 +18,14 @@ class MediaUploader extends SubTemplate protected function html_content() { echo ' -
+

Upload new photos to "', $this->tag->tag, '"

Select files

- +
- +
diff --git a/templates/PhotoPage.php b/templates/PhotoPage.php index 3ce2b55..ded285f 100644 --- a/templates/PhotoPage.php +++ b/templates/PhotoPage.php @@ -6,7 +6,7 @@ * Kabuki CMS (C) 2013-2016, Aaron van Geffen *****************************************************************************/ -class PhotoPage extends SubTemplate +class PhotoPage extends Template { protected $photo; private $exif; @@ -34,7 +34,7 @@ class PhotoPage extends SubTemplate $this->is_asset_owner = $flag; } - protected function html_content() + public function html_main() { $this->photoNav(); $this->photo(); diff --git a/templates/PhotosIndex.php b/templates/PhotosIndex.php index c988e54..25255c5 100644 --- a/templates/PhotosIndex.php +++ b/templates/PhotosIndex.php @@ -6,7 +6,7 @@ * Kabuki CMS (C) 2013-2015, Aaron van Geffen *****************************************************************************/ -class PhotosIndex extends SubTemplate +class PhotosIndex extends Template { protected $mosaic; protected $show_edit_buttons; @@ -42,7 +42,7 @@ class PhotosIndex extends SubTemplate $this->show_labels = $show_labels; } - protected function html_content() + public function html_main() { echo '
'; diff --git a/templates/SubTemplate.php b/templates/SubTemplate.php index 75030de..79f0ed0 100644 --- a/templates/SubTemplate.php +++ b/templates/SubTemplate.php @@ -8,10 +8,32 @@ abstract class SubTemplate extends Template { + protected $_class = 'content-box container'; + protected $_id; + protected $_title; + + public function __construct($title = '') + { + $this->_title = $title; + } + public function html_main() { - echo $this->html_content(); + echo ' +
_id) ? ' id="' . $this->_id . '"' : '', '>', + $this->html_content(), ' +
'; } abstract protected function html_content(); + + public function setClassName($className) + { + $this->_class = $className; + } + + public function setDOMId($id) + { + $this->_id = $id; + } } From 2d1a299fe0874d331a325be721850a289353fe49 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 13:44:36 +0100 Subject: [PATCH 06/79] Replace login and password reset templates --- templates/ForgotPasswordForm.php | 24 +++++++++------ templates/LogInForm.php | 52 ++++++++++++++++++++------------ templates/PasswordResetForm.php | 36 ++++++++++++---------- 3 files changed, 67 insertions(+), 45 deletions(-) diff --git a/templates/ForgotPasswordForm.php b/templates/ForgotPasswordForm.php index 7a50660..447ebeb 100644 --- a/templates/ForgotPasswordForm.php +++ b/templates/ForgotPasswordForm.php @@ -11,19 +11,25 @@ class ForgotPasswordForm extends SubTemplate protected function html_content() { echo ' -
-

Password reset procedure

'; +

Password reset procedure

'; foreach ($this->_subtemplates as $template) $template->html_main(); echo ' -

Please fill in the email address you used to sign up in the form below. You will be sent a reset link to your email address.

- -
- - - -
'; +

Please fill in the email address you used to sign up in the form below. We will send a reset link to your email address.

+
+
+ +
+ +
+
+
+
+ +
+
+
'; } } diff --git a/templates/LogInForm.php b/templates/LogInForm.php index c2b8835..0c28e70 100644 --- a/templates/LogInForm.php +++ b/templates/LogInForm.php @@ -11,45 +11,57 @@ class LogInForm extends SubTemplate private $redirect_url = ''; private $emailaddress = ''; + protected $_class = 'content-box container w-50'; + public function setRedirectUrl($url) { - $_SESSION['login_url'] = $url; $this->redirect_url = $url; } public function setEmail($addr) { - $this->emailaddress = htmlentities($addr); + $this->emailaddress = htmlspecialchars($addr); } protected function html_content() { - echo ' -
-

Log in

'; + if (!empty($this->_title)) + echo ' +

Log in to your account

'; - foreach ($this->_subtemplates as $template) - $template->html_main(); + if (!empty($this->_subtemplates)) + { + foreach ($this->_subtemplates as $template) + $template->html_main(); + } echo ' -
-
-
- -
-
-
'; + +
+ +
+ +
+
+
+ +
+ +
+
'; // Throw in a redirect url if asked for. if (!empty($this->redirect_url)) echo ' - '; + '; echo ' - Forgotten your password? -
- -
-
'; +
+
+ + Forgotten your password? +
+
+ '; } } diff --git a/templates/PasswordResetForm.php b/templates/PasswordResetForm.php index 4bc9c5e..2393bd5 100644 --- a/templates/PasswordResetForm.php +++ b/templates/PasswordResetForm.php @@ -20,27 +20,31 @@ class PasswordResetForm extends SubTemplate protected function html_content() { echo ' -
-

Password reset procedure

'; +

Password reset procedure

'; foreach ($this->_subtemplates as $template) $template->html_main(); echo ' -

You have successfully confirmed your identify. Please use the form below to set a new password.

-
-

- - -

- -

- - -

- +

You have successfully confirmed your identify. Please use the form below to set a new password.

+ +
+ +
+ +
+
+
+ +
+ +
+
+
+
- -
'; +
+
+ '; } } From cf31f0af074933ddff62161ccfb09d2f1c4a5d60 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 13:51:12 +0100 Subject: [PATCH 07/79] Replace more custom button classes with Bootstrap counterparts --- public/css/default.css | 32 -------------------------------- templates/AlbumButtonBox.php | 2 +- templates/PhotoPage.php | 2 +- 3 files changed, 2 insertions(+), 34 deletions(-) diff --git a/public/css/default.css b/public/css/default.css index 510683d..b92ea4b 100644 --- a/public/css/default.css +++ b/public/css/default.css @@ -280,29 +280,12 @@ ul#nav li a:hover { margin-bottom: 20px; } .album_button_box > a { - background: #fff; box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); - display: inline-block; - float: left; - font-size: 1em; padding: 8px 10px; margin-left: 12px; } -/* Generic boxed content ---------------------------*/ -.boxed_content { - background: #fff; - padding: 25px; - box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); -} -.boxed_content h2 { - font: 300 24px "Open Sans", sans-serif; - margin: 0 0 0.2em; -} - - /* Error pages ----------------*/ .errormsg p { @@ -333,21 +316,6 @@ footer a { } -/* Input -----------*/ -.btn-red { - background: #eebbaa; - border-color: #cc9988; -} -.btn-red:hover, .btn-red:focus { - border-color: #bb7766; - color: #000; -} -.btn-red:focus:not(:active) { - box-shadow: 0px 0px 0px 2px rgba(241, 70, 104, 0.25); -} - - /* Login box styles ---------------------*/ #login { diff --git a/templates/AlbumButtonBox.php b/templates/AlbumButtonBox.php index 4126b72..3956ffb 100644 --- a/templates/AlbumButtonBox.php +++ b/templates/AlbumButtonBox.php @@ -22,7 +22,7 @@ class AlbumButtonBox extends Template foreach ($this->buttons as $button) echo ' - ', $button['caption'], ''; + ', $button['caption'], ''; echo '
'; diff --git a/templates/PhotoPage.php b/templates/PhotoPage.php index ded285f..dc24134 100644 --- a/templates/PhotoPage.php +++ b/templates/PhotoPage.php @@ -242,7 +242,7 @@ class PhotoPage extends Template echo '

Actions

- Delete + Delete
'; } } From a9a2c64d81be6eea7f2050f43232cedd84e5083c Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 13:57:57 +0100 Subject: [PATCH 08/79] PhotoPage: replace custom sub-photo boxes with generic equivalents --- public/css/default.css | 26 -------------------------- templates/PhotoPage.php | 22 +++++++++++++++------- 2 files changed, 15 insertions(+), 33 deletions(-) diff --git a/public/css/default.css b/public/css/default.css index b92ea4b..541f3e6 100644 --- a/public/css/default.css +++ b/public/css/default.css @@ -421,14 +421,6 @@ a#previous_photo:hover, a#next_photo:hover { font-size: 16px; } -#sub_photo { - background: #fff; - box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); - float: left; - padding: 2%; - margin: 25px 3.5% 25px 0; - width: 68.5%; -} #sub_photo #tag_list { list-style: none; margin: 1em 0; @@ -446,15 +438,6 @@ a#previous_photo:hover, a#next_photo:hover { } -#photo_exif_box { - background: #fff; - box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); - margin: 25px 0 25px 0; - overflow: auto; - padding: 2%; - float: right; - width: 20%; -} #photo_exif_box dt { font-weight: bold; float: left; @@ -469,15 +452,6 @@ a#previous_photo:hover, a#next_photo:hover { margin: 0; } -#user_actions_box { - background: #fff; - box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); - float: left; - margin: 25px 0 25px 0; - overflow: auto; - padding: 2%; - width: 20%; -} /* Responsive: smartphone in portrait ---------------------------------------*/ diff --git a/templates/PhotoPage.php b/templates/PhotoPage.php index dc24134..1c0bd5c 100644 --- a/templates/PhotoPage.php +++ b/templates/PhotoPage.php @@ -40,21 +40,27 @@ class PhotoPage extends Template $this->photo(); echo ' -
-

', $this->photo->getTitle(), '

'; +
+
+
+

', $this->photo->getTitle(), '

'; $this->taggedPeople(); $this->linkNewTags(); echo ' -
'; +
+
+
'; $this->photoMeta(); - if($this->is_asset_owner) + if ($this->is_asset_owner) $this->addUserActions(); echo ' +
+
'; } @@ -94,7 +100,7 @@ class PhotoPage extends Template private function photoMeta() { echo ' -
+

EXIF

'; @@ -171,7 +177,9 @@ class PhotoPage extends Template echo '

Link tags

-

+

+ +

@@ -240,7 +248,7 @@ class PhotoPage extends Template public function addUserActions() { echo ' -
+

Actions

Delete
'; From 021df2df93a2172389927458567b44240a613424 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 14:12:56 +0100 Subject: [PATCH 09/79] Pagination: use larger page indices on photo and album index pages --- controllers/ViewPeople.php | 1 + controllers/ViewPhotoAlbum.php | 1 + controllers/ViewTimeline.php | 1 + 3 files changed, 3 insertions(+) diff --git a/controllers/ViewPeople.php b/controllers/ViewPeople.php index 3829327..014ebcb 100644 --- a/controllers/ViewPeople.php +++ b/controllers/ViewPeople.php @@ -52,6 +52,7 @@ class ViewPeople extends HTMLController 'start' => $start, 'base_url' => BASEURL . '/people/', 'page_slug' => 'page/%PAGE%/', + 'index_class' => 'pagination-lg justify-content-center', ]); $this->page->adopt(new PageIndexWidget($pagination)); diff --git a/controllers/ViewPhotoAlbum.php b/controllers/ViewPhotoAlbum.php index 73c84e2..aa3d632 100644 --- a/controllers/ViewPhotoAlbum.php +++ b/controllers/ViewPhotoAlbum.php @@ -111,6 +111,7 @@ class ViewPhotoAlbum extends HTMLController 'start' => (isset($_GET['page']) ? $_GET['page'] - 1 : 0) * self::PER_PAGE, 'base_url' => BASEURL . '/' . (isset($_GET['tag']) ? $_GET['tag'] . '/' : ''), 'page_slug' => 'page/%PAGE%/', + 'index_class' => 'pagination-lg justify-content-center', ]); $this->page->adopt(new PageIndexWidget($index)); } diff --git a/controllers/ViewTimeline.php b/controllers/ViewTimeline.php index bf9d56e..72dfd76 100644 --- a/controllers/ViewTimeline.php +++ b/controllers/ViewTimeline.php @@ -46,6 +46,7 @@ class ViewTimeline extends HTMLController 'start' => (isset($_GET['page']) ? $_GET['page'] - 1 : 0) * self::PER_PAGE, 'base_url' => BASEURL . '/timeline/', 'page_slug' => 'page/%PAGE%/', + 'index_class' => 'pagination-lg justify-content-center', ]); $this->page->adopt(new PageIndexWidget($index)); } From 812c7a4f205c6fbb6f7b8eb38af022809d47028d Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 14:13:29 +0100 Subject: [PATCH 10/79] PhotoPage: change previous/next icons --- public/css/default.css | 10 ++++------ templates/PhotoPage.php | 8 ++++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/public/css/default.css b/public/css/default.css index 541f3e6..e88e043 100644 --- a/public/css/default.css +++ b/public/css/default.css @@ -401,17 +401,15 @@ a#previous_photo:hover, a#next_photo:hover { color: #000; } #previous_photo { + border-top-right-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; left: 0; } -#previous_photo:before { - content: '←'; -} #next_photo { + border-top-left-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; right: 0; } -#next_photo:before { - content: '→'; -} #sub_photo h2, #sub_photo h3, #photo_exif_box h3, #user_actions_box h3 { font: 600 20px/30px "Open Sans", sans-serif; diff --git a/templates/PhotoPage.php b/templates/PhotoPage.php index 1c0bd5c..772a4f9 100644 --- a/templates/PhotoPage.php +++ b/templates/PhotoPage.php @@ -84,17 +84,17 @@ class PhotoPage extends Template { if ($this->previous_photo_url) echo ' - Previous photo'; + '; else echo ' - Previous photo'; + '; if ($this->next_photo_url) echo ' - Next photo'; + '; else echo ' - Next photo'; + '; } private function photoMeta() From b9bd2bf4994e3f12d7d54bd70b2e82ee708ca26c Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 14:17:38 +0100 Subject: [PATCH 11/79] AlbumHeaderBox: apply some border radius to tag headers --- public/css/default.css | 7 ++++++- templates/AlbumHeaderBox.php | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/public/css/default.css b/public/css/default.css index e88e043..450d770 100644 --- a/public/css/default.css +++ b/public/css/default.css @@ -107,6 +107,7 @@ ul#nav li a:hover { .tiled_header { background: #fff; box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); + border-radius: 0.5rem; color: #000; clear: both; float: left; @@ -248,15 +249,19 @@ ul#nav li a:hover { .album_title_box > a { background: #fff; box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); + border-top-left-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; display: inline-block; float: left; font-size: 2em; line-height: 1; - padding: 8px 10px 14px; + padding: 8px 10px; } .album_title_box > div { background: #fff; box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); + border-top-right-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; float: left; font: inherit; padding: 6px 22px; diff --git a/templates/AlbumHeaderBox.php b/templates/AlbumHeaderBox.php index 7dfccf1..1d82818 100644 --- a/templates/AlbumHeaderBox.php +++ b/templates/AlbumHeaderBox.php @@ -25,7 +25,9 @@ class AlbumHeaderBox extends Template { echo '
- + + +

', $this->title, '

'; From a6fd8d27644894f3ecc3750f0c3544ebab1d2f83 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 14:24:17 +0100 Subject: [PATCH 12/79] Admin controllers: apply new column classes --- controllers/ManageAlbums.php | 4 ++-- controllers/ManageAssets.php | 1 - controllers/ManageErrors.php | 5 +++-- controllers/ManageTags.php | 4 ++-- controllers/ManageUsers.php | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/controllers/ManageAlbums.php b/controllers/ManageAlbums.php index f989e6a..6f578b1 100644 --- a/controllers/ManageAlbums.php +++ b/controllers/ManageAlbums.php @@ -18,7 +18,7 @@ class ManageAlbums extends HTMLController 'form' => [ 'action' => BASEURL . '/editalbum/', 'method' => 'get', - 'class' => 'float-end', + 'class' => 'col-md-6 text-end', 'buttons' => [ 'add' => [ 'type' => 'submit', @@ -60,7 +60,7 @@ class ManageAlbums extends HTMLController 'title' => 'Manage albums', 'no_items_label' => 'No albums meet the requirements of the current filter.', 'items_per_page' => 9999, - 'index_class' => 'float-start', + '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'])) diff --git a/controllers/ManageAssets.php b/controllers/ManageAssets.php index 1afc85b..463ede5 100644 --- a/controllers/ManageAssets.php +++ b/controllers/ManageAssets.php @@ -67,7 +67,6 @@ class ManageAssets extends HTMLController 'title' => 'Manage assets', 'no_items_label' => 'No assets meet the requirements of the current filter.', 'items_per_page' => 30, - 'index_class' => 'pull_left', 'base_url' => BASEURL . '/manageassets/', 'get_data' => function($offset = 0, $limit = 30, $order = '', $direction = 'down') { if (!in_array($order, ['id_asset', 'title', 'subdir', 'filename'])) diff --git a/controllers/ManageErrors.php b/controllers/ManageErrors.php index 9619753..f510c7b 100644 --- a/controllers/ManageErrors.php +++ b/controllers/ManageErrors.php @@ -29,11 +29,12 @@ class ManageErrors extends HTMLController 'form' => [ 'action' => BASEURL . '/manageerrors/?' . Session::getSessionTokenKey() . '=' . Session::getSessionToken(), 'method' => 'post', - 'class' => 'float-end', + 'class' => 'col-md-6 text-end', 'buttons' => [ 'flush' => [ 'type' => 'submit', 'caption' => 'Delete all', + 'class' => 'btn-danger', ], ], ], @@ -99,7 +100,7 @@ class ManageErrors extends HTMLController 'sort_direction' => !empty($_GET['dir']) ? $_GET['dir'] : '', 'no_items_label' => "No errors to display -- we're all good!", 'items_per_page' => 20, - 'index_class' => 'float-start', + 'index_class' => 'col-md-6', 'base_url' => BASEURL . '/manageerrors/', 'get_count' => 'ErrorLog::getCount', 'get_data' => function($offset = 0, $limit = 20, $order = '', $direction = 'down') { diff --git a/controllers/ManageTags.php b/controllers/ManageTags.php index a49f9aa..ff49780 100644 --- a/controllers/ManageTags.php +++ b/controllers/ManageTags.php @@ -18,7 +18,7 @@ class ManageTags extends HTMLController 'form' => [ 'action' => BASEURL . '/edittag/', 'method' => 'get', - 'class' => 'float-end', + 'class' => 'col-md-6 text-end', 'buttons' => [ 'add' => [ 'type' => 'submit', @@ -65,7 +65,7 @@ class ManageTags extends HTMLController 'title' => 'Manage tags', 'no_items_label' => 'No tags meet the requirements of the current filter.', 'items_per_page' => 30, - 'index_class' => 'float-start', + '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'])) diff --git a/controllers/ManageUsers.php b/controllers/ManageUsers.php index 14ef97f..cf53e24 100644 --- a/controllers/ManageUsers.php +++ b/controllers/ManageUsers.php @@ -18,7 +18,7 @@ class ManageUsers extends HTMLController 'form' => [ 'action' => BASEURL . '/edituser/', 'method' => 'get', - 'class' => 'float-end', + 'class' => 'col-md-6 text-end', 'buttons' => [ 'add' => [ 'type' => 'submit', @@ -94,7 +94,7 @@ class ManageUsers extends HTMLController 'title' => 'Manage users', 'no_items_label' => 'No users meet the requirements of the current filter.', 'items_per_page' => 30, - 'index_class' => 'float-start', + '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'])) From 5bb8c020bd16a80f4815c6ac8e60ac989b871065 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 14:31:44 +0100 Subject: [PATCH 13/79] EditAssetForm: replace widget class with generic content box --- public/css/admin.css | 26 +------------------------- public/js/crop_editor.js | 10 +++++++--- templates/DummyBox.php | 6 +++--- templates/EditAssetForm.php | 30 ++++++++++++++++-------------- 4 files changed, 27 insertions(+), 45 deletions(-) diff --git a/public/css/admin.css b/public/css/admin.css index 93007cc..8dd510d 100644 --- a/public/css/admin.css +++ b/public/css/admin.css @@ -74,30 +74,6 @@ body { } -/* Admin widgets -------------------*/ -.widget { - background: #fff; - padding: 25px; - box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); -} -.widget h3 { - margin: 0 0 1em; - font: 400 18px "Raleway", sans-serif; -} -.widget p, .errormsg p { - margin: 0; -} -.widget ul { - margin: 0; - list-style: none; - padding: 0; -} -.widget li { - line-height: 1.7em; -} - - /* Edit icon on tiled grids -----------------------------*/ .tiled_grid div.landscape, .tiled_grid div.portrait, .tiled_grid div.panorama { @@ -134,7 +110,7 @@ body { color: #fff; } #crop_editor input[type=number] { - width: 50px; + width: 75px; background: #555; color: #fff; } diff --git a/public/js/crop_editor.js b/public/js/crop_editor.js index 555ec83..286f1da 100644 --- a/public/js/crop_editor.js +++ b/public/js/crop_editor.js @@ -3,7 +3,7 @@ class CropEditor { this.opt = opt; this.edit_crop_button = document.createElement("span"); - this.edit_crop_button.className = "btn"; + this.edit_crop_button.className = "btn btn-light"; this.edit_crop_button.textContent = "Edit crop"; this.edit_crop_button.addEventListener('click', this.show.bind(this)); @@ -34,6 +34,7 @@ class CropEditor { this.position.appendChild(source_x_label); this.source_x = document.createElement("input"); + this.source_x.className = 'form-control d-inline'; this.source_x.type = 'number'; this.source_x.addEventListener("change", this.positionBoundary.bind(this)); this.source_x.addEventListener("keyup", this.positionBoundary.bind(this)); @@ -43,6 +44,7 @@ class CropEditor { this.position.appendChild(source_y_label); this.source_y = document.createElement("input"); + this.source_y.className = 'form-control d-inline'; this.source_y.type = 'number'; this.source_y.addEventListener("change", this.positionBoundary.bind(this)); this.source_y.addEventListener("keyup", this.positionBoundary.bind(this)); @@ -52,6 +54,7 @@ class CropEditor { this.position.appendChild(crop_width_label); this.crop_width = document.createElement("input"); + this.crop_width.className = 'form-control d-inline'; this.crop_width.type = 'number'; this.crop_width.addEventListener("change", this.positionBoundary.bind(this)); this.crop_width.addEventListener("keyup", this.positionBoundary.bind(this)); @@ -61,6 +64,7 @@ class CropEditor { this.position.appendChild(crop_height_label); this.crop_height = document.createElement("input"); + this.crop_height.className = 'form-control d-inline'; this.crop_height.type = 'number'; this.crop_height.addEventListener("change", this.positionBoundary.bind(this)); this.crop_height.addEventListener("keyup", this.positionBoundary.bind(this)); @@ -78,13 +82,13 @@ class CropEditor { this.crop_constrain_label.appendChild(this.crop_constrain_text); this.save_button = document.createElement("span"); - this.save_button.className = "btn"; + this.save_button.className = "btn btn-light"; this.save_button.textContent = "Save"; this.save_button.addEventListener('click', this.save.bind(this)); this.position.appendChild(this.save_button); this.abort_button = document.createElement("span"); - this.abort_button.className = "btn btn-red"; + this.abort_button.className = "btn btn-danger"; this.abort_button.textContent = "Abort"; this.abort_button.addEventListener('click', this.hide.bind(this)); this.position.appendChild(this.abort_button); diff --git a/templates/DummyBox.php b/templates/DummyBox.php index 2632384..23dc086 100644 --- a/templates/DummyBox.php +++ b/templates/DummyBox.php @@ -8,9 +8,9 @@ class DummyBox extends SubTemplate { - private $_class; - private $_content; - private $_title; + protected $_class; + protected $_content; + protected $_title; public function __construct($title = '', $content = '', $class = '') { diff --git a/templates/EditAssetForm.php b/templates/EditAssetForm.php index 490b0d2..8bdcdaf 100644 --- a/templates/EditAssetForm.php +++ b/templates/EditAssetForm.php @@ -6,7 +6,7 @@ * Kabuki CMS (C) 2013-2015, Aaron van Geffen *****************************************************************************/ -class EditAssetForm extends SubTemplate +class EditAssetForm extends Template { private $asset; private $thumbs; @@ -17,14 +17,14 @@ class EditAssetForm extends SubTemplate $this->thumbs = $thumbs; } - protected function html_content() + public function html_main() { echo '
-
+

Edit asset \'', $this->asset->getTitle(), '\' (', $this->asset->getFilename(), ')

'; @@ -32,14 +32,15 @@ class EditAssetForm extends SubTemplate $this->section_replace(); echo ' -
'; +
+
'; $this->section_key_info(); $this->section_asset_meta(); echo ' -
-
'; +
+
'; if (!empty($this->thumbs)) $this->section_thumbnails(); @@ -47,11 +48,12 @@ class EditAssetForm extends SubTemplate $this->section_linked_tags(); echo ' -
'; +
'; $this->section_crop_editor(); echo ' +
'; } @@ -59,7 +61,7 @@ class EditAssetForm extends SubTemplate { $date_captured = $this->asset->getDateCaptured(); echo ' -
+

Key info

Title
@@ -81,7 +83,7 @@ class EditAssetForm extends SubTemplate protected function section_linked_tags() { echo ' -
+

Linked tags

    '; @@ -134,7 +136,7 @@ class EditAssetForm extends SubTemplate protected function section_thumbnails() { echo ' -
    +

    Thumbnails

    View: Target: currentThumbnailId == $image->getId() ? ' checked' : '', '> + + '; + } + + $this->assets->clean(); + + echo ' +
'; + } +} From cf0b9ebaf9decf8e9bd823fd760c48054b4f69b0 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 19:34:01 +0100 Subject: [PATCH 33/79] LogInForm: change title to something #RU-like --- templates/LogInForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/LogInForm.php b/templates/LogInForm.php index 0c28e70..cafb51e 100644 --- a/templates/LogInForm.php +++ b/templates/LogInForm.php @@ -27,7 +27,7 @@ class LogInForm extends SubTemplate { if (!empty($this->_title)) echo ' -

Log in to your account

'; +

Press #RU to continue

'; if (!empty($this->_subtemplates)) { From a06902335bb839e4909fb5d57d870a314f53c018 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 19:34:52 +0100 Subject: [PATCH 34/79] Manage{Tags,Users}: add call to resetSessionToken --- controllers/ManageTags.php | 2 ++ controllers/ManageUsers.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/controllers/ManageTags.php b/controllers/ManageTags.php index ff49780..26f2b49 100644 --- a/controllers/ManageTags.php +++ b/controllers/ManageTags.php @@ -14,6 +14,8 @@ class ManageTags extends HTMLController if (!Registry::get('user')->isAdmin()) throw new NotAllowedException(); + Session::resetSessionToken(); + $options = [ 'form' => [ 'action' => BASEURL . '/edittag/', diff --git a/controllers/ManageUsers.php b/controllers/ManageUsers.php index cf53e24..1be66a5 100644 --- a/controllers/ManageUsers.php +++ b/controllers/ManageUsers.php @@ -14,6 +14,8 @@ class ManageUsers extends HTMLController if (!Registry::get('user')->isAdmin()) throw new NotAllowedException(); + Session::resetSessionToken(); + $options = [ 'form' => [ 'action' => BASEURL . '/edituser/', From 6d0aef4df659b7eddbed9d9e98c7e53ddb51c6e1 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 19:49:17 +0100 Subject: [PATCH 35/79] EditTag: allow updating the thumbnail visually --- controllers/EditTag.php | 39 ++++++++++++++++++-------- models/Tag.php | 4 ++- templates/FeaturedThumbnailManager.php | 6 +++- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/controllers/EditTag.php b/controllers/EditTag.php index c3d1382..a26107f 100644 --- a/controllers/EditTag.php +++ b/controllers/EditTag.php @@ -10,14 +10,18 @@ class EditTag extends HTMLController { public function __construct() { - // Ensure it's just admins at this point. - if (!Registry::get('user')->isAdmin()) - throw new NotAllowedException(); - $id_tag = isset($_GET['id']) ? (int) $_GET['id'] : 0; if (empty($id_tag) && !isset($_GET['add'])) throw new UnexpectedValueException('Requested tag not found or not requesting a new tag.'); + if (!empty($id_tag)) + $tag = Tag::fromId($id_tag); + + // Are we allowed to edit this tag? + $user = Registry::get('user'); + if (!($user->isAdmin() || $user->getUserId() == $tag->id_user_owner)) + throw new NotAllowedException(); + // Adding an tag? if (isset($_GET['add'])) { @@ -29,7 +33,6 @@ class EditTag extends HTMLController elseif (isset($_GET['delete'])) { // So far so good? - $tag = Tag::fromId($id_tag); if (Session::validateSession('get') && $tag->kind !== 'Album' && $tag->delete()) { header('Location: ' . BASEURL . '/managetags/'); @@ -41,7 +44,6 @@ class EditTag extends HTMLController // Editing one, then, surely. else { - $tag = Tag::fromId($id_tag); if ($tag->kind === 'Album') trigger_error('Cannot edit tag: is actually an album.', E_USER_ERROR); @@ -65,11 +67,6 @@ class EditTag extends HTMLController 'request_url' => BASEURL . '/edittag/?' . ($id_tag ? 'id=' . $id_tag : 'add'), 'content_below' => $after_form, 'fields' => [ - 'id_asset_thumb' => [ - 'type' => 'numeric', - 'label' => 'Thumbnail asset ID', - 'is_optional' => true, - ], 'kind' => [ 'type' => 'select', 'label' => 'Kind of tag', @@ -116,6 +113,26 @@ class EditTag extends HTMLController $this->page->adopt(new FeaturedThumbnailManager($assets, $id_tag ? $tag->id_asset_thumb : 0)); } + if (isset($_POST['changeThumbnail'])) + $this->processThumbnail($tag); + elseif (!empty($_POST)) + $this->processTagDetails($form, $id_tag, $tag); + } + + private function processThumbnail($tag) + { + if (empty($_POST)) + return; + + $tag->id_asset_thumb = $_POST['featuredThumbnail']; + $tag->save(); + + header('Location: ' . BASEURL . '/edittag/?id=' . $tag->id_tag); + exit; + } + + private function processTagDetails($form, $id_tag, $tag) + { if (!empty($_POST)) { $form->verify($_POST); diff --git a/models/Tag.php b/models/Tag.php index e6b6f13..d5bbccc 100644 --- a/models/Tag.php +++ b/models/Tag.php @@ -11,6 +11,7 @@ class Tag public $id_tag; public $id_parent; public $id_asset_thumb; + public $id_user_owner; public $tag; public $slug; public $description; @@ -258,7 +259,8 @@ class Tag UPDATE tags SET id_parent = {int:id_parent}, - id_asset_thumb = {int:id_asset_thumb}, + id_asset_thumb = {int:id_asset_thumb},' . (isset($this->id_user_owner) ? ' + id_user_owner = {int:id_user_owner},' : '') . ' tag = {string:tag}, slug = {string:slug}, description = {string:description}, diff --git a/templates/FeaturedThumbnailManager.php b/templates/FeaturedThumbnailManager.php index 910ecdf..ffbe1c0 100644 --- a/templates/FeaturedThumbnailManager.php +++ b/templates/FeaturedThumbnailManager.php @@ -20,6 +20,8 @@ class FeaturedThumbnailManager extends SubTemplate protected function html_content() { echo ' +
+

Select thumbnail

    '; @@ -37,6 +39,8 @@ class FeaturedThumbnailManager extends SubTemplate $this->assets->clean(); echo ' -
'; + + +
'; } } From 59b1fa7a72a1bcbb16cd7f367573a5f93dec06f8 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 19:52:30 +0100 Subject: [PATCH 36/79] EditAlbum: allow updating the thumbnail visually --- controllers/EditAlbum.php | 41 ++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/controllers/EditAlbum.php b/controllers/EditAlbum.php index e2aa20c..76d549d 100644 --- a/controllers/EditAlbum.php +++ b/controllers/EditAlbum.php @@ -18,6 +18,9 @@ class EditAlbum extends HTMLController if (empty($id_tag) && !isset($_GET['add']) && $_GET['action'] !== 'addalbum') throw new UnexpectedValueException('Requested album not found or not requesting a new album.'); + if (!empty($id_tag)) + $album = Tag::fromId($id_tag); + // Adding an album? if (isset($_GET['add']) || $_GET['action'] === 'addalbum') { @@ -29,7 +32,6 @@ class EditAlbum extends HTMLController elseif (isset($_GET['delete'])) { // So far so good? - $album = Tag::fromId($id_tag); if (Session::validateSession('get') && $album->kind === 'Album' && $album->delete()) { header('Location: ' . BASEURL . '/managealbums/'); @@ -41,7 +43,6 @@ class EditAlbum extends HTMLController // Editing one, then, surely. else { - $album = Tag::fromId($id_tag); if ($album->kind !== 'Album') trigger_error('Cannot edit album: not an album.', E_USER_ERROR); @@ -80,11 +81,6 @@ class EditAlbum extends HTMLController 'label' => 'Parent album', 'options' => $parentChoices, ], - 'id_asset_thumb' => [ - 'type' => 'numeric', - 'label' => 'Thumbnail asset ID', - 'is_optional' => true, - ], 'tag' => [ 'type' => 'text', 'label' => 'Album title', @@ -128,6 +124,37 @@ class EditAlbum extends HTMLController $formview = new FormView($form, $form_title ?? ''); $this->page->adopt($formview); + if (!empty($id_tag)) + { + list($assets, $num_assets) = AssetIterator::getByOptions([ + 'direction' => 'desc', + 'limit' => 500, + 'id_tag' => $id_tag, + ], true); + + $this->page->adopt(new FeaturedThumbnailManager($assets, $id_tag ? $album->id_asset_thumb : 0)); + } + + if (isset($_POST['changeThumbnail'])) + $this->processThumbnail($album); + elseif (!empty($_POST)) + $this->processTagDetails($form, $id_tag, $album); + } + + private function processThumbnail($tag) + { + if (empty($_POST)) + return; + + $tag->id_asset_thumb = $_POST['featuredThumbnail']; + $tag->save(); + + header('Location: ' . BASEURL . '/editalbum/?id=' . $tag->id_tag); + exit; + } + + private function processTagDetails($form, $id_tag, $album) + { if (!empty($_POST)) { $form->verify($_POST); From ad816f10a36e6a6825d4fe89cc98c7c24866e867 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 19:57:19 +0100 Subject: [PATCH 37/79] EditTag: allow designating a tag owner --- controllers/EditTag.php | 5 +++++ models/Member.php | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/controllers/EditTag.php b/controllers/EditTag.php index a26107f..d752223 100644 --- a/controllers/EditTag.php +++ b/controllers/EditTag.php @@ -75,6 +75,11 @@ class EditTag extends HTMLController 'Person' => 'Person', ], ], + 'id_user_owner' => [ + 'type' => 'select', + 'label' => 'Owner', + 'options' => [0 => '(nobody)'] + Member::getMemberMap(), + ], 'tag' => [ 'type' => 'text', 'label' => 'Tag title', diff --git a/models/Member.php b/models/Member.php index 6675dac..84912a4 100644 --- a/models/Member.php +++ b/models/Member.php @@ -192,4 +192,15 @@ class Member extends User // We should probably phase out the use of this function, or refactor the access levels of member properties... return get_object_vars($this); } + + public static function getMemberMap() + { + return Registry::get('db')->queryPair(' + SELECT id_user, CONCAT(first_name, {string:blank}, surname) AS full_name + FROM users + ORDER BY first_name, surname', + [ + 'blank' => ' ', + ]); + } } From 27f69b0a74728f48ebceb9b62b679b58742755de Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 20:01:25 +0100 Subject: [PATCH 38/79] EditTag: disallow users to disown their own tags --- controllers/EditTag.php | 76 +++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/controllers/EditTag.php b/controllers/EditTag.php index d752223..12de092 100644 --- a/controllers/EditTag.php +++ b/controllers/EditTag.php @@ -63,43 +63,51 @@ class EditTag extends HTMLController elseif (!$id_tag) $after_form = ''; + $fields = [ + 'kind' => [ + 'type' => 'select', + 'label' => 'Kind of tag', + 'options' => [ + 'Location' => 'Location', + 'Person' => 'Person', + ], + ], + 'id_user_owner' => [ + 'type' => 'select', + 'label' => 'Owner', + 'options' => [0 => '(nobody)'] + Member::getMemberMap(), + ], + 'tag' => [ + 'type' => 'text', + 'label' => 'Tag title', + 'size' => 50, + 'maxlength' => 255, + ], + 'slug' => [ + 'type' => 'text', + 'label' => 'URL slug', + 'size' => 50, + 'maxlength' => 255, + ], + 'description' => [ + 'type' => 'textbox', + 'label' => 'Description', + 'size' => 50, + 'maxlength' => 255, + 'is_optional' => true, + ], + ]; + + if (!$user->isAdmin()) + { + unset($fields['kind']); + unset($fields['id_user_owner']); + } + $form = new Form([ 'request_url' => BASEURL . '/edittag/?' . ($id_tag ? 'id=' . $id_tag : 'add'), 'content_below' => $after_form, - 'fields' => [ - 'kind' => [ - 'type' => 'select', - 'label' => 'Kind of tag', - 'options' => [ - 'Location' => 'Location', - 'Person' => 'Person', - ], - ], - 'id_user_owner' => [ - 'type' => 'select', - 'label' => 'Owner', - 'options' => [0 => '(nobody)'] + Member::getMemberMap(), - ], - 'tag' => [ - 'type' => 'text', - 'label' => 'Tag title', - 'size' => 50, - 'maxlength' => 255, - ], - 'slug' => [ - 'type' => 'text', - 'label' => 'URL slug', - 'size' => 50, - 'maxlength' => 255, - ], - 'description' => [ - 'type' => 'textbox', - 'label' => 'Description', - 'size' => 50, - 'maxlength' => 255, - 'is_optional' => true, - ], - ], + 'fields' => $fields, ]); // Create the form, add in default values. From daa8b051c57c3853ad5ae3c51bdf959239ec97da Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 20:03:09 +0100 Subject: [PATCH 39/79] EditTag: on saving, redirect users to a page they can see --- controllers/EditTag.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/controllers/EditTag.php b/controllers/EditTag.php index 12de092..fcb0e96 100644 --- a/controllers/EditTag.php +++ b/controllers/EditTag.php @@ -182,8 +182,11 @@ class EditTag extends HTMLController $tag->save(); } - // Redirect to the tag management page. - header('Location: ' . BASEURL . '/managetags/'); + // Redirect to a clean page + if (Registry::get('user')->isAdmin()) + header('Location: ' . BASEURL . '/managetags/'); + else + header('Location: ' . BASEURL . '/edittag/?id=' . $id_tag); exit; } } From a76dde927b799dc5370601745d5bb583a2dfca86 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 20:27:09 +0100 Subject: [PATCH 40/79] AccountSettings: list tags owned by current user --- controllers/AccountSettings.php | 5 +++++ models/Tag.php | 19 +++++++++++++++++++ templates/MyTagsView.php | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 templates/MyTagsView.php diff --git a/controllers/AccountSettings.php b/controllers/AccountSettings.php index 4bbb056..4f4a431 100644 --- a/controllers/AccountSettings.php +++ b/controllers/AccountSettings.php @@ -73,6 +73,11 @@ class AccountSettings extends HTMLController $formview = new FormView($form, $form_title); $this->page->adopt($formview); + // Fetch user tags + $tags = Tag::getAllByOwner($user->getUserId()); + if (!empty($tags)) + $this->page->adopt(new MyTagsView($tags)); + // Left a message? if (isset($_SESSION['account_msg'])) { diff --git a/models/Tag.php b/models/Tag.php index d5bbccc..13af5c7 100644 --- a/models/Tag.php +++ b/models/Tag.php @@ -96,6 +96,25 @@ class Tag return $rows; } + public static function getAllByOwner($id_user_owner) + { + $db = Registry::get('db'); + $res = $db->query(' + SELECT * + FROM tags + WHERE id_user_owner = {int:id_user_owner} + ORDER BY tag', + [ + 'id_user_owner' => $id_user_owner, + ]); + + $objects = []; + while ($row = $db->fetch_assoc($res)) + $objects[$row['id_tag']] = new Tag($row); + + return $objects; + } + public static function getAlbums($id_parent = 0, $offset = 0, $limit = 24, $return_format = 'array') { $rows = Registry::get('db')->queryAssocs(' diff --git a/templates/MyTagsView.php b/templates/MyTagsView.php new file mode 100644 index 0000000..2b06fd9 --- /dev/null +++ b/templates/MyTagsView.php @@ -0,0 +1,32 @@ +tags = $tags; + } + + protected function html_content() + { + echo ' +

Tags you can edit

+

You can currently edit the tags below. Click a tag to edit it.

+
    '; + + foreach ($this->tags as $tag) + echo ' +
  • ', $tag->tag, '
  • '; + + echo ' +
'; + } +} From d9fd2ae20dc08b7e85dc8b4cde47c526684dd8fb Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 20:27:45 +0100 Subject: [PATCH 41/79] Add upgrade script for new tag ownership --- upgrade.sql | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 upgrade.sql diff --git a/upgrade.sql b/upgrade.sql new file mode 100644 index 0000000..7e07e9c --- /dev/null +++ b/upgrade.sql @@ -0,0 +1,15 @@ +/* 2023-03-11 Allow designating an owner for each tag */ +ALTER TABLE `tags` ADD `id_user_owner` INT NULL DEFAULT NULL AFTER `id_asset_thumb`; + +/* 2023-03-11 Try to assign tag owners automagically */ +UPDATE tags AS t +SET id_user_owner = ( + SELECT id_user + FROM users AS u + WHERE LOWER(u.first_name) = LOWER(t.slug) OR + LOWER(u.first_name) = LOWER(t.tag) OR + LOWER(u.slug) = LOWER(t.slug) OR + LOWER(u.slug) = LOWER(t.tag) + ) +WHERE t.kind = 'Person' AND + (t.id_user_owner = 0 OR t.id_user_owner IS NULL); From 167a50cb9281794d49df7bb4fb08ab3485279734 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 20:34:58 +0100 Subject: [PATCH 42/79] ViewPhotoAlbum: tweak album buttons to be more useful --- controllers/ViewPhotoAlbum.php | 75 ++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/controllers/ViewPhotoAlbum.php b/controllers/ViewPhotoAlbum.php index aa3d632..d45588b 100644 --- a/controllers/ViewPhotoAlbum.php +++ b/controllers/ViewPhotoAlbum.php @@ -60,27 +60,7 @@ class ViewPhotoAlbum extends HTMLController // Can we do fancy things here? // !!! TODO: permission system? - $buttons = []; - - if (Registry::get('user')->isLoggedIn()) - { - $buttons[] = [ - 'url' => BASEURL . '/download/?tag=' . $id_tag, - 'caption' => 'Download this album', - ]; - - $buttons[] = [ - 'url' => BASEURL . '/uploadmedia/?tag=' . $id_tag, - 'caption' => 'Upload new photos here', - ]; - } - if (Registry::get('user')->isAdmin()) - $buttons[] = [ - 'url' => BASEURL . '/addalbum/?tag=' . $id_tag, - 'caption' => 'Create new subalbum here', - ]; - - // Enough actions for a button box? + $buttons = $this->getAlbumButtons($id_tag, $tag ?? null); if (!empty($buttons)) $this->page->adopt(new AlbumButtonBox($buttons)); @@ -164,6 +144,59 @@ class ViewPhotoAlbum extends HTMLController return $albums; } + private function getAlbumButtons($id_tag, $tag) + { + $buttons = []; + $user = Registry::get('user'); + + if ($user->isLoggedIn()) + { + $buttons[] = [ + 'url' => BASEURL . '/download/?tag=' . $id_tag, + 'caption' => 'Download album', + ]; + } + + if (isset($tag)) + { + if ($tag->kind === 'Album') + { + $buttons[] = [ + 'url' => BASEURL . '/uploadmedia/?tag=' . $id_tag, + 'caption' => 'Upload photos here', + ]; + } + + if ($user->isAdmin()) + { + if ($tag->kind === 'Album') + { + $buttons[] = [ + 'url' => BASEURL . '/editalbum/?id=' . $id_tag, + 'caption' => 'Edit album', + ]; + } + elseif ($tag->kind === 'Person') + { + $buttons[] = [ + 'url' => BASEURL . '/edittag/?id=' . $id_tag, + 'caption' => 'Edit tag', + ]; + } + } + } + + if ($user->isAdmin() && (!isset($tag) || $tag->kind === 'Album')) + { + $buttons[] = [ + 'url' => BASEURL . '/addalbum/?tag=' . $id_tag, + 'caption' => 'Create subalbum', + ]; + } + + return $buttons; + } + public function __destruct() { if (isset($this->iterator)) From 310fe7c3d6ddc5d50bc5b2da165c5ebd13fb635e Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 20:37:39 +0100 Subject: [PATCH 43/79] Hide thumbnail selection when none available --- controllers/EditAlbum.php | 3 ++- controllers/EditTag.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/controllers/EditAlbum.php b/controllers/EditAlbum.php index 76d549d..c24787d 100644 --- a/controllers/EditAlbum.php +++ b/controllers/EditAlbum.php @@ -132,7 +132,8 @@ class EditAlbum extends HTMLController 'id_tag' => $id_tag, ], true); - $this->page->adopt(new FeaturedThumbnailManager($assets, $id_tag ? $album->id_asset_thumb : 0)); + if ($num_assets > 0) + $this->page->adopt(new FeaturedThumbnailManager($assets, $id_tag ? $album->id_asset_thumb : 0)); } if (isset($_POST['changeThumbnail'])) diff --git a/controllers/EditTag.php b/controllers/EditTag.php index fcb0e96..c96ef0e 100644 --- a/controllers/EditTag.php +++ b/controllers/EditTag.php @@ -123,7 +123,8 @@ class EditTag extends HTMLController 'id_tag' => $id_tag, ], true); - $this->page->adopt(new FeaturedThumbnailManager($assets, $id_tag ? $tag->id_asset_thumb : 0)); + if ($num_assets > 0) + $this->page->adopt(new FeaturedThumbnailManager($assets, $id_tag ? $tag->id_asset_thumb : 0)); } if (isset($_POST['changeThumbnail'])) From 5cff62836e28dca61079c5e43e7a7a0c8daea59d Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 20:39:55 +0100 Subject: [PATCH 44/79] ManageTags: display owning user in table --- controllers/ManageTags.php | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/controllers/ManageTags.php b/controllers/ManageTags.php index 26f2b49..cb1ffd2 100644 --- a/controllers/ManageTags.php +++ b/controllers/ManageTags.php @@ -50,10 +50,18 @@ class ManageTags extends HTMLController 'data' => 'slug', ], ], - 'kind' => [ - 'header' => 'Kind', + 'id_user_owner' => [ + 'header' => 'Owning user', 'is_sortable' => true, - 'value' => 'kind', + 'parse' => [ + 'type' => 'function', + 'data' => function($row) { + if (!empty($row['first_name'])) + return $row['first_name'] . ' ' . $row['surname']; + else + return 'n/a'; + }, + ], ], 'count' => [ 'header' => 'Cardinality', @@ -76,8 +84,9 @@ class ManageTags extends HTMLController $direction = 'up'; $data = Registry::get('db')->queryAssocs(' - SELECT * - FROM tags + 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}', From 5b8551a72676a08f1bdb698134600d6c0d65f786 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 20:46:31 +0100 Subject: [PATCH 45/79] EditAlbum: allow specifying a thumbnail ID manually if none are present --- controllers/EditAlbum.php | 87 ++++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/controllers/EditAlbum.php b/controllers/EditAlbum.php index c24787d..6d73428 100644 --- a/controllers/EditAlbum.php +++ b/controllers/EditAlbum.php @@ -72,37 +72,58 @@ class EditAlbum extends HTMLController $parentChoices[$parent['id_tag']] = $parent['tag']; } + $fields = [ + 'id_parent' => [ + 'type' => 'select', + 'label' => 'Parent album', + 'options' => $parentChoices, + ], + 'id_asset_thumb' => [ + 'type' => 'numeric', + 'label' => 'Thumbnail asset ID', + 'is_optional' => true, + ], + 'tag' => [ + 'type' => 'text', + 'label' => 'Album title', + 'size' => 50, + 'maxlength' => 255, + ], + 'slug' => [ + 'type' => 'text', + 'label' => 'URL slug', + 'size' => 50, + 'maxlength' => 255, + ], + 'description' => [ + 'type' => 'textbox', + 'label' => 'Description', + 'size' => 50, + 'maxlength' => 255, + 'is_optional' => true, + ], + ]; + + // Fetch image assets for this album + if (!empty($id_tag)) + { + list($assets, $num_assets) = AssetIterator::getByOptions([ + 'direction' => 'desc', + 'limit' => 500, + 'id_tag' => $id_tag, + ], true); + + if ($num_assets > 0) + unset($fields['id_asset_thumb']); + } + $form = new Form([ 'request_url' => BASEURL . '/editalbum/?' . ($id_tag ? 'id=' . $id_tag : 'add'), 'content_below' => $after_form, - 'fields' => [ - 'id_parent' => [ - 'type' => 'select', - 'label' => 'Parent album', - 'options' => $parentChoices, - ], - 'tag' => [ - 'type' => 'text', - 'label' => 'Album title', - 'size' => 50, - 'maxlength' => 255, - ], - 'slug' => [ - 'type' => 'text', - 'label' => 'URL slug', - 'size' => 50, - 'maxlength' => 255, - ], - 'description' => [ - 'type' => 'textbox', - 'label' => 'Description', - 'size' => 50, - 'maxlength' => 255, - 'is_optional' => true, - ], - ], + 'fields' => $fields, ]); + // Add defaults for album if none present if (empty($_POST) && isset($_GET['tag'])) { $parentTag = Tag::fromId($_GET['tag']); @@ -124,17 +145,9 @@ class EditAlbum extends HTMLController $formview = new FormView($form, $form_title ?? ''); $this->page->adopt($formview); - if (!empty($id_tag)) - { - list($assets, $num_assets) = AssetIterator::getByOptions([ - 'direction' => 'desc', - 'limit' => 500, - 'id_tag' => $id_tag, - ], true); - - if ($num_assets > 0) - $this->page->adopt(new FeaturedThumbnailManager($assets, $id_tag ? $album->id_asset_thumb : 0)); - } + // If we have asset images, show the thumbnail manager + if (!empty($id_tag) && $num_assets > 0) + $this->page->adopt(new FeaturedThumbnailManager($assets, $id_tag ? $album->id_asset_thumb : 0)); if (isset($_POST['changeThumbnail'])) $this->processThumbnail($album); From a4cc5289518524f95fdf73a49ab54ed12d8b1869 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 21:24:55 +0100 Subject: [PATCH 46/79] ManageAssets: allow batch deletion of assets --- controllers/ManageAssets.php | 50 ++++++++++++++++++++++++++-- templates/AssetManagementWrapper.php | 36 ++++++++++++++++++++ templates/TabularData.php | 19 +++++++++-- 3 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 templates/AssetManagementWrapper.php diff --git a/controllers/ManageAssets.php b/controllers/ManageAssets.php index 4598c8e..499b3c1 100644 --- a/controllers/ManageAssets.php +++ b/controllers/ManageAssets.php @@ -14,10 +14,37 @@ class ManageAssets extends HTMLController if (!Registry::get('user')->isAdmin()) throw new NotAllowedException(); + if (isset($_POST['deleteChecked'], $_POST['delete']) && Session::validateSession()) + $this->handleAssetDeletion(); + Session::resetSessionToken(); $options = [ + 'form' => [ + 'action' => BASEURL . '/manageassets/?' . Session::getSessionTokenKey() . '=' . Session::getSessionToken(), + 'method' => 'post', + 'class' => 'col-md-6 text-end', + 'is_embed' => true, + 'buttons' => [ + 'deleteChecked' => [ + 'type' => 'submit', + 'caption' => 'Delete checked', + 'class' => 'btn-danger', + 'onclick' => 'return confirm(\'Are you sure you want to delete these items?\')', + ], + ], + ], 'columns' => [ + 'checkbox' => [ + 'header' => '', + 'is_sortable' => false, + 'parse' => [ + 'type' => 'function', + 'data' => function($row) { + return ''; + }, + ], + ], 'id_asset' => [ 'value' => 'id_asset', 'header' => 'ID', @@ -71,6 +98,7 @@ class ManageAssets extends HTMLController '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'])) @@ -100,7 +128,25 @@ class ManageAssets extends HTMLController ]; $table = new GenericTable($options); - parent::__construct('Asset management - Page ' . $table->getCurrentPage() .''); - $this->page->adopt(new TabularData($table)); + parent::__construct('Asset management - Page ' . $table->getCurrentPage()); + + $wrapper = new AssetManagementWrapper(); + $this->page->adopt($wrapper); + $wrapper->adopt(new TabularData($table)); + } + + private function handleAssetDeletion() + { + if (!isset($_POST['delete']) || !is_array($_POST['delete'])) + throw new UnexpectedValueException(); + + foreach ($_POST['delete'] as $id_asset) + { + $asset = Asset::fromId($id_asset); + $asset->delete(); + } + + header('Location: ' . BASEURL . '/manageassets/'); + exit; } } diff --git a/templates/AssetManagementWrapper.php b/templates/AssetManagementWrapper.php new file mode 100644 index 0000000..545f7c4 --- /dev/null +++ b/templates/AssetManagementWrapper.php @@ -0,0 +1,36 @@ +'; + + foreach ($this->_subtemplates as $template) + $template->html_main(); + + echo ' + + '; + } +} diff --git a/templates/TabularData.php b/templates/TabularData.php index 86f17de..fdcfed3 100644 --- a/templates/TabularData.php +++ b/templates/TabularData.php @@ -145,8 +145,12 @@ class TabularData extends SubTemplate protected function showForm($form) { - echo ' + if (!isset($form['is_embed'])) + echo '
'; + else + echo ' +
'; if (!empty($form['is_group'])) echo ' @@ -206,7 +210,12 @@ class TabularData extends SubTemplate foreach ($form['buttons'] as $name => $button) { echo ' - '; + '; if (isset($button['html_after'])) echo $button['html_after']; @@ -216,7 +225,11 @@ class TabularData extends SubTemplate echo '
'; - echo ' + if (!isset($form['is_embed'])) + echo '
'; + else + echo ' +
'; } } From 0b0d47acb83309df8eb13ba51d6bab81c5269c15 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 21:36:32 +0100 Subject: [PATCH 47/79] UploadQueue: error out of HEIC files are presented --- public/js/upload_queue.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/public/js/upload_queue.js b/public/js/upload_queue.js index 75b237d..a0953ff 100644 --- a/public/js/upload_queue.js +++ b/public/js/upload_queue.js @@ -17,6 +17,14 @@ UploadQueue.prototype.addEvents = function() { that.hideSpinner(); that.submit.disabled = false; }; + + if (that.queue.files[0].name.includes(".HEIC")) { + alert('Sorry, the HEIC image format is not supported.\nPlease convert your photos to JPEG before uploading.'); + that.hideSpinner(); + that.submit.disabled = false; + break; + } + that.addPreviewBoxForQueueSlot(i); that.addPreviewForFile(that.queue.files[i], i, callback); }; From aa3a54f237cbf1907c8989fac34b5a2f2c016851 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 21:39:20 +0100 Subject: [PATCH 48/79] Asset: guard using property_exists in constructor --- models/Asset.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/models/Asset.php b/models/Asset.php index 483c799..bdf6b38 100644 --- a/models/Asset.php +++ b/models/Asset.php @@ -27,7 +27,10 @@ class Asset protected function __construct(array $data) { foreach ($data as $attribute => $value) - $this->$attribute = $value; + { + if (property_exists($this, $attribute)) + $this->$attribute = $value; + } if (!empty($data['date_captured']) && $data['date_captured'] !== 'NULL') $this->date_captured = new DateTime($data['date_captured']); From 1b7e745f1135d06054fefa80a3e1148c22410823 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 21:41:23 +0100 Subject: [PATCH 49/79] Clean up Tag::resetIdAsset --- models/Tag.php | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/models/Tag.php b/models/Tag.php index 13af5c7..14696f3 100644 --- a/models/Tag.php +++ b/models/Tag.php @@ -313,8 +313,7 @@ class Tag public function resetIdAsset() { $db = Registry::get('db'); - - $row = $db->query(' + $new_id = $db->queryValue(' SELECT MAX(id_asset) as new_id FROM assets_tags WHERE id_tag = {int:id_tag}', @@ -322,18 +321,12 @@ class Tag 'id_tag' => $this->id_tag, ]); - $new_id = 0; - if(!empty($row)) - { - $new_id = $row->fetch_assoc()['new_id']; - } - return $db->query(' UPDATE tags SET id_asset_thumb = {int:new_id} WHERE id_tag = {int:id_tag}', [ - 'new_id' => $new_id, + 'new_id' => $new_id ?? 0, 'id_tag' => $this->id_tag, ]); } From b8c53d7d4d52d494859554a5c16d3636ef315542 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 21:45:03 +0100 Subject: [PATCH 50/79] ViewPhotoAlbum: prevent undefined index due to missing thumb --- controllers/ViewPhotoAlbum.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/controllers/ViewPhotoAlbum.php b/controllers/ViewPhotoAlbum.php index d45588b..9f7c07e 100644 --- a/controllers/ViewPhotoAlbum.php +++ b/controllers/ViewPhotoAlbum.php @@ -137,7 +137,8 @@ class ViewPhotoAlbum extends HTMLController 'id_tag' => $album['id_tag'], 'caption' => $album['tag'], 'link' => BASEURL . '/' . $album['slug'] . '/', - 'thumbnail' => !empty($album['id_asset_thumb']) ? $assets[$album['id_asset_thumb']]->getImage() : null, + 'thumbnail' => !empty($album['id_asset_thumb']) && isset($assets[$album['id_asset_thumb']]) + ? $assets[$album['id_asset_thumb']]->getImage() : null, ]; } From 4c928af9add0338ac37e5cbc7af455957752f342 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 21:46:23 +0100 Subject: [PATCH 51/79] AlbumIndex: set thumbnail dimensions for 'no thumb' images too --- templates/AlbumIndex.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/AlbumIndex.php b/templates/AlbumIndex.php index 3205b81..d311645 100644 --- a/templates/AlbumIndex.php +++ b/templates/AlbumIndex.php @@ -60,7 +60,8 @@ class AlbumIndex extends Template } else echo ' - '; + '; if ($this->show_labels) echo ' From 16eda4cfe737af9595d7a4ee288ce2d643d606c4 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 21:50:08 +0100 Subject: [PATCH 52/79] Move autosuggest styles to default.css --- public/css/admin.css | 24 ------------------------ public/css/default.css | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/public/css/admin.css b/public/css/admin.css index 0316b80..d56938f 100644 --- a/public/css/admin.css +++ b/public/css/admin.css @@ -12,30 +12,6 @@ } -/* (Tag) autosuggest -----------------------*/ -#new_tag_container { - display: block; - position: relative; -} -.autosuggest { - background: #fff; - border: 1px solid #ccc; - position: absolute; - top: 29px; - margin: 0; - padding: 0; -} -.autosuggest li { - display: block !important; - padding: 3px; -} -.autosuggest li:hover, .autosuggest li.selected { - background: #CFECF7; - cursor: pointer; -} - - /* Edit icon on tiled grids -----------------------------*/ .tiled_grid div.landscape, .tiled_grid div.portrait, .tiled_grid div.panorama { diff --git a/public/css/default.css b/public/css/default.css index 1732722..7ebf113 100644 --- a/public/css/default.css +++ b/public/css/default.css @@ -341,6 +341,30 @@ i.space-invader.alt-7::before { } +/* (Tag) autosuggest +----------------------*/ +#new_tag_container { + display: block; + position: relative; +} +.autosuggest { + background: #fff; + border: 1px solid #ccc; + position: absolute; + top: 29px; + margin: 0; + padding: 0; +} +.autosuggest li { + display: block !important; + padding: 3px; +} +.autosuggest li:hover, .autosuggest li.selected { + background: #CFECF7; + cursor: pointer; +} + + /* Error pages ----------------*/ .errormsg p { From eb04e87085e6d7e373babedc6db0f87590ee53a0 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 21:52:44 +0100 Subject: [PATCH 53/79] Change autosuggest padding --- public/css/default.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/public/css/default.css b/public/css/default.css index 7ebf113..056e886 100644 --- a/public/css/default.css +++ b/public/css/default.css @@ -351,13 +351,14 @@ i.space-invader.alt-7::before { background: #fff; border: 1px solid #ccc; position: absolute; - top: 29px; + left: 2px; + top: 37px; margin: 0; padding: 0; } .autosuggest li { display: block !important; - padding: 3px; + padding: 3px 8px; } .autosuggest li:hover, .autosuggest li.selected { background: #CFECF7; From d83dd6ea6ede77b5b8a62e39b6b1bf6087854733 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 21:55:44 +0100 Subject: [PATCH 54/79] Remove more obsolete styling --- public/css/admin.css | 14 -------- public/css/default.css | 76 ------------------------------------------ 2 files changed, 90 deletions(-) diff --git a/public/css/admin.css b/public/css/admin.css index d56938f..92c2736 100644 --- a/public/css/admin.css +++ b/public/css/admin.css @@ -1,17 +1,3 @@ -.admin_box { - margin: 0; - padding: 20px; - background: #fff; - box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); - overflow: auto; -} - -.admin_box h2 { - font: 700 24px "Open Sans", sans-serif; - margin: 0 0 0.2em; -} - - /* Edit icon on tiled grids -----------------------------*/ .tiled_grid div.landscape, .tiled_grid div.portrait, .tiled_grid div.panorama { diff --git a/public/css/default.css b/public/css/default.css index 056e886..aee4d77 100644 --- a/public/css/default.css +++ b/public/css/default.css @@ -426,50 +426,6 @@ footer a { } -/* Login box styles ----------------------*/ -#login { - background: #fff; - border: 1px solid #aaa; - border-radius: 10px; - box-shadow: 2px 2px 4px rgba(0,0,0,0.1); - margin: 0 auto; - overflow: auto; - padding: 15px; - width: 300px; -} -#login dl *, #login button { - font-size: 15px; - line-height: 35px; -} -#login h3 { - font: 700 24px/36px "Open Sans", sans-serif; - margin: 0; -} -#login dd { - width: 96%; - margin: 0 0 10px; -} -#login input { - background: #eee; - border: 1px solid #aaa; - border-radius: 3px; - padding: 4px 5px; - width: 100%; -} -#login div.alert { - line-height: normal; - margin: 15px 0; -} -#login div.buttonstrip { - float: right; - padding: 0 0 5px; -} -#login button { - line-height: 20px; -} - - /* Styling for the photo pages --------------------------------*/ #photo_frame { @@ -565,38 +521,6 @@ a#previous_photo:hover, a#next_photo:hover { max-width: 100% !important; } - h1#logo { - font-size: 42px; - float: none; - margin: 1em 0 0.5em; - text-align: center; - } - h1#logo:before { - float: none; - font-size: 58px; - margin-right: 8px; - } - - ul#nav { - float: none; - padding: 0; - margin: 1em 0; - text-align: center; - overflow: hidden; - } - - ul#nav li, ul#nav li a { - display: inline-block; - float: none; - } - - ul#nav li a { - float: none; - font-size: 16px; - margin-left: 6px; - padding: 12px 4px; - } - .album_title_box { margin-left: 0; } From 1859a9ea2a7fb916f753046a55fd36707513e259 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 21:57:55 +0100 Subject: [PATCH 55/79] LogInForm: fix smartphone view --- templates/LogInForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/LogInForm.php b/templates/LogInForm.php index cafb51e..0e0c96a 100644 --- a/templates/LogInForm.php +++ b/templates/LogInForm.php @@ -11,7 +11,7 @@ class LogInForm extends SubTemplate private $redirect_url = ''; private $emailaddress = ''; - protected $_class = 'content-box container w-50'; + protected $_class = 'content-box container col-lg-6'; public function setRedirectUrl($url) { From e916489d007996d66271e13bc73121b2d62c9c0c Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 22:04:02 +0100 Subject: [PATCH 56/79] PhotoPage: only use columns on large displays --- templates/PhotoPage.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/PhotoPage.php b/templates/PhotoPage.php index 490b2cc..932abc0 100644 --- a/templates/PhotoPage.php +++ b/templates/PhotoPage.php @@ -41,7 +41,7 @@ class PhotoPage extends Template echo '
-
+

', $this->photo->getTitle(), '

'; @@ -51,7 +51,7 @@ class PhotoPage extends Template echo '
-
'; +
'; $this->photoMeta(); From 0b8c614191da2ba191c5c83db741d9fdbb409a40 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 22:07:00 +0100 Subject: [PATCH 57/79] Manage{Assets,Tags}: link user names to edituser --- controllers/ManageAssets.php | 5 +++-- controllers/ManageTags.php | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/controllers/ManageAssets.php b/controllers/ManageAssets.php index 499b3c1..2c2bfdc 100644 --- a/controllers/ManageAssets.php +++ b/controllers/ManageAssets.php @@ -71,8 +71,9 @@ class ManageAssets extends HTMLController 'parse' => [ 'type' => 'function', 'data' => function($row) { - if (!empty($row['first_name'])) - return $row['first_name'] . ' ' . $row['surname']; + if (!empty($row['id_user'])) + return sprintf('%s', BASEURL, $row['id_user'], + $row['first_name'] . ' ' . $row['surname']); else return 'n/a'; }, diff --git a/controllers/ManageTags.php b/controllers/ManageTags.php index cb1ffd2..ec12de0 100644 --- a/controllers/ManageTags.php +++ b/controllers/ManageTags.php @@ -56,8 +56,9 @@ class ManageTags extends HTMLController 'parse' => [ 'type' => 'function', 'data' => function($row) { - if (!empty($row['first_name'])) - return $row['first_name'] . ' ' . $row['surname']; + if (!empty($row['id_user'])) + return sprintf('%s', BASEURL, $row['id_user'], + $row['first_name'] . ' ' . $row['surname']); else return 'n/a'; }, From c7e4351375e51cfcb5aba803391b71c51fc55dea Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 22:13:55 +0100 Subject: [PATCH 58/79] Change album/tile label font to Coda, too --- public/css/default.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/css/default.css b/public/css/default.css index aee4d77..3e5b8c7 100644 --- a/public/css/default.css +++ b/public/css/default.css @@ -192,7 +192,7 @@ i.space-invader.alt-7::before { .tiled_grid div h4 { color: #000; margin: 0; - font: 400 18px "Open Sans", sans-serif; + font: 400 18px 'Coda', sans-serif; padding: 15px 5px; text-overflow: ellipsis; text-align: center; From 2c24a0a7e7018a8dcd6ceef7a72001cbfe4ee63a Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 22:15:17 +0100 Subject: [PATCH 59/79] MainTemplate: open vanity link in new tab --- templates/MainTemplate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/MainTemplate.php b/templates/MainTemplate.php index d23df69..b9e903b 100644 --- a/templates/MainTemplate.php +++ b/templates/MainTemplate.php @@ -74,7 +74,7 @@ class MainTemplate extends Template } else echo ' - Powered by Kabuki CMS'; + Powered by Kabuki CMS'; echo ' From 70fcd097cc0b3e1f1f099384d6ae1f358ab595e2 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 12 Mar 2023 00:39:15 +0100 Subject: [PATCH 60/79] EditAsset: remove reference to old admin bar --- controllers/EditAsset.php | 4 ---- controllers/HTMLController.php | 1 - templates/PhotoPage.php | 2 +- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/controllers/EditAsset.php b/controllers/EditAsset.php index 8fc7bf4..a6ca7f9 100644 --- a/controllers/EditAsset.php +++ b/controllers/EditAsset.php @@ -94,10 +94,6 @@ class EditAsset extends HTMLController $page = new EditAssetForm($asset, $thumbs); parent::__construct('Edit asset \'' . $asset->getTitle() . '\' (' . $asset->getFilename() . ') - ' . SITE_TITLE); $this->page->adopt($page); - - // Add a view button to the admin bar for photos. - if ($asset->isImage()) - $this->admin_bar->appendItem($asset->getImage()->getPageUrl(), 'View this photo'); } private function getThumbs(Asset $asset) diff --git a/controllers/HTMLController.php b/controllers/HTMLController.php index a5d5236..f141638 100644 --- a/controllers/HTMLController.php +++ b/controllers/HTMLController.php @@ -12,7 +12,6 @@ abstract class HTMLController { protected $page; - protected $admin_bar; public function __construct($title) { diff --git a/templates/PhotoPage.php b/templates/PhotoPage.php index 932abc0..b2d663f 100644 --- a/templates/PhotoPage.php +++ b/templates/PhotoPage.php @@ -250,7 +250,7 @@ class PhotoPage extends Template echo ' '; } From 0325a2ec9028fcd607719da46eb8c46518f46b5e Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 12 Mar 2023 00:53:47 +0100 Subject: [PATCH 61/79] EditAssetForm: make form look presentable --- templates/EditAssetForm.php | 85 +++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/templates/EditAssetForm.php b/templates/EditAssetForm.php index 8bdcdaf..d791dcd 100644 --- a/templates/EditAssetForm.php +++ b/templates/EditAssetForm.php @@ -22,9 +22,9 @@ class EditAssetForm extends Template echo '
-
+
Delete asset - +

Edit asset \'', $this->asset->getTitle(), '\' (', $this->asset->getFilename(), ')

'; @@ -63,20 +63,32 @@ class EditAssetForm extends Template echo '

Key info

-
-
Title
-
-
URL slug
-
- -
Date captured
-
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ - -
Display priority
-
-
+
+
+
+ +
+ +
+
'; } @@ -85,7 +97,7 @@ class EditAssetForm extends Template echo '

Linked tags

-
    '; +
      '; foreach ($this->asset->getTags() as $tag) echo ' @@ -95,7 +107,7 @@ class EditAssetForm extends Template '; echo ' -
    • +
@@ -138,7 +150,7 @@ class EditAssetForm extends Template echo '

Thumbnails

- View: '; $first = INF; foreach ($this->thumbs as $i => $thumb) @@ -220,38 +232,39 @@ class EditAssetForm extends Template protected function section_asset_meta() { echo ' -
-

Asset meta data

-
    '; +
    +

    Asset meta data

    '; - $i = -1; + $i = 0; foreach ($this->asset->getMeta() as $key => $meta) { - $i++; echo ' -
  • - - -
  • '; +
    + + +
    '; + $i++; } + echo ' -
  • - - -
  • -
-

+
+ + +
+
+ +
'; } protected function section_replace() { echo ' -
+

Replace asset

- File: - Target: + Target: - +
'; } } From 01822cdccffc71465e9bcf3fa751c38fd3bd66e5 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 12 Mar 2023 01:00:50 +0100 Subject: [PATCH 62/79] Fix Button, ConfirmDeletePage, WarningDialog templates --- controllers/ViewPhoto.php | 2 +- templates/Button.php | 4 ++-- templates/ConfirmDeletePage.php | 6 +++--- templates/PhotoPage.php | 2 +- templates/WarningDialog.php | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/controllers/ViewPhoto.php b/controllers/ViewPhoto.php index e46929d..5e29ea8 100644 --- a/controllers/ViewPhoto.php +++ b/controllers/ViewPhoto.php @@ -39,7 +39,7 @@ class ViewPhoto extends HTMLController $page = new ConfirmDeletePage($photo->getImage()); $this->page->adopt($page); } - else if (isset($_REQUEST['delete_confirmed'])) + elseif (isset($_REQUEST['delete_confirmed'])) { $album_url = $photo->getSubdir(); $photo->delete(); diff --git a/templates/Button.php b/templates/Button.php index 29024e1..c978c42 100644 --- a/templates/Button.php +++ b/templates/Button.php @@ -6,7 +6,7 @@ * Kabuki CMS (C) 2013-2015, Aaron van Geffen *****************************************************************************/ -class Button extends SubTemplate +class Button extends Template { private $content = ''; private $href = ''; @@ -19,7 +19,7 @@ class Button extends SubTemplate $this->class = $class; } - protected function html_content() + public function html_main() { echo ' ', $this->content, ''; diff --git a/templates/ConfirmDeletePage.php b/templates/ConfirmDeletePage.php index bdba97c..80075c5 100644 --- a/templates/ConfirmDeletePage.php +++ b/templates/ConfirmDeletePage.php @@ -13,7 +13,7 @@ class ConfirmDeletePage extends PhotoPage parent::__construct($photo); } - protected function html_content() + public function html_main() { $this->confirm(); $this->photo(); @@ -22,7 +22,7 @@ class ConfirmDeletePage extends PhotoPage private function confirm() { $buttons = []; - $buttons[] = new Button("Delete", BASEURL . '/' . $this->photo->getSlug() . '?delete_confirmed', "btn btn-red"); + $buttons[] = new Button("Delete", BASEURL . '/' . $this->photo->getSlug() . '/?delete_confirmed', "btn btn-danger"); $buttons[] = new Button("Cancel", $this->photo->getPageUrl(), "btn"); $alert = new WarningDialog( @@ -30,6 +30,6 @@ class ConfirmDeletePage extends PhotoPage "You are about to permanently delete the following photo.", $buttons ); - $alert->html_content(); + $alert->html_main(); } } diff --git a/templates/PhotoPage.php b/templates/PhotoPage.php index b2d663f..49ac58b 100644 --- a/templates/PhotoPage.php +++ b/templates/PhotoPage.php @@ -251,7 +251,7 @@ class PhotoPage extends Template '; } } diff --git a/templates/WarningDialog.php b/templates/WarningDialog.php index 198b7e0..6a9c93e 100644 --- a/templates/WarningDialog.php +++ b/templates/WarningDialog.php @@ -24,6 +24,6 @@ class WarningDialog extends Alert private function addButtons() { foreach ($this->buttons as $button) - $button->html_content(); + $button->html_main(); } } From 3cf281b24d357519c0e1fa9cdf553e18d9155f0a Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 12 Mar 2023 01:04:28 +0100 Subject: [PATCH 63/79] AdminMenu: add error count to badge iff count > 0 --- models/AdminMenu.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/models/AdminMenu.php b/models/AdminMenu.php index aae9e0d..e0515d0 100644 --- a/models/AdminMenu.php +++ b/models/AdminMenu.php @@ -15,9 +15,10 @@ class AdminMenu extends Menu if (!$user->isAdmin()) return; - $this->items[] = [ + $this->items[0] = [ 'label' => 'Admin', 'icon' => 'gear', + 'badge' => ErrorLog::getCount(), 'subs' => [ [ 'uri' => '/managealbums/', @@ -43,6 +44,9 @@ class AdminMenu extends Menu ], ]; + if ($this->items[0]['badge'] == 0) + unset($this->items[0]['badge']); + foreach ($this->items as $i => $item) { if (isset($item['uri'])) From 6087ebe2495615e8164a56a924446320d2f2ee5d Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 12 Mar 2023 01:19:43 +0100 Subject: [PATCH 64/79] AutoSuggest: fix click/append event Keyboard was fine, it was just mouse events that were broken ^^' --- public/js/autosuggest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/autosuggest.js b/public/js/autosuggest.js index 76ab820..e1bcb42 100644 --- a/public/js/autosuggest.js +++ b/public/js/autosuggest.js @@ -124,7 +124,7 @@ class AutoSuggest { node.innerHTML = this.highlightMatches(query_tokens, item.label); node.jsondata = item; node.addEventListener('click', event => { - this.appendCallback(event.target.jsondata); + this.appendCallback(node.jsondata); this.closeContainer(); this.clearInput(); }); From 544944a7f5073aad3a2422eba0940efffe7af173 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 12 Mar 2023 11:32:13 +0100 Subject: [PATCH 65/79] Edit{Album,Tag}: fix new tag creation --- controllers/EditAlbum.php | 2 +- controllers/EditTag.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/controllers/EditAlbum.php b/controllers/EditAlbum.php index 6d73428..9ac4e23 100644 --- a/controllers/EditAlbum.php +++ b/controllers/EditAlbum.php @@ -152,7 +152,7 @@ class EditAlbum extends HTMLController if (isset($_POST['changeThumbnail'])) $this->processThumbnail($album); elseif (!empty($_POST)) - $this->processTagDetails($form, $id_tag, $album); + $this->processTagDetails($form, $id_tag, $album ?? null); } private function processThumbnail($tag) diff --git a/controllers/EditTag.php b/controllers/EditTag.php index c96ef0e..364a6f4 100644 --- a/controllers/EditTag.php +++ b/controllers/EditTag.php @@ -130,7 +130,7 @@ class EditTag extends HTMLController if (isset($_POST['changeThumbnail'])) $this->processThumbnail($tag); elseif (!empty($_POST)) - $this->processTagDetails($form, $id_tag, $tag); + $this->processTagDetails($form, $id_tag, $tag ?? null); } private function processThumbnail($tag) From 54b69ecd11554d5aaaf1b302fe1774ee5395db33 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 12 Mar 2023 11:33:16 +0100 Subject: [PATCH 66/79] MediaUploader: simplify form control design --- templates/MediaUploader.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/templates/MediaUploader.php b/templates/MediaUploader.php index d577ec7..69aa083 100644 --- a/templates/MediaUploader.php +++ b/templates/MediaUploader.php @@ -20,12 +20,9 @@ class MediaUploader extends SubTemplate echo '

Upload new photos to "', $this->tag->tag, '"

-
-

Select files

- -
-
- +
+ +
From 229fb9e5bfc1bf863b6f3b8ac262ee13267f3f4b Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 12 Mar 2023 11:45:37 +0100 Subject: [PATCH 67/79] UploadQueue: refactor into proper ECMAScript class --- public/js/upload_queue.js | 382 +++++++++++++++++++------------------- 1 file changed, 189 insertions(+), 193 deletions(-) diff --git a/public/js/upload_queue.js b/public/js/upload_queue.js index a0953ff..54384f6 100644 --- a/public/js/upload_queue.js +++ b/public/js/upload_queue.js @@ -1,215 +1,211 @@ -function UploadQueue(options) { - this.queue = options.queue_element; - this.preview_area = options.preview_area; - this.upload_progress = []; - this.upload_url = options.upload_url; - this.submit = options.submit_button; - this.addEvents(); -} +class UploadQueue { + constructor(options) { + this.queue = options.queue_element; + this.preview_area = options.preview_area; + this.upload_progress = []; + this.upload_url = options.upload_url; + this.submit = options.submit_button; + this.addEvents(); + } -UploadQueue.prototype.addEvents = function() { - var that = this; - that.queue.addEventListener('change', function() { - that.showSpinner(that.queue, "Generating previews (not uploading yet!)"); - that.clearPreviews(); - for (var i = 0; i < that.queue.files.length; i++) { - var callback = (i !== that.queue.files.length - 1) ? null : function() { - that.hideSpinner(); - that.submit.disabled = false; + addEvents() { + this.queue.addEventListener('change', event => { + this.showSpinner(this.queue, "Generating previews (not uploading yet!)"); + this.clearPreviews(); + for (let i = 0; i < this.queue.files.length; i++) { + const callback = (i !== this.queue.files.length - 1) ? null : () => { + this.hideSpinner(); + this.submit.disabled = false; + }; + + if (this.queue.files[0].name.includes(".HEIC")) { + alert('Sorry, the HEIC image format is not supported.\nPlease convert your photos to JPEG before uploading.'); + this.hideSpinner(); + this.submit.disabled = false; + break; + } + + this.addPreviewBoxForQueueSlot(i); + this.addPreviewForFile(this.queue.files[i], i, callback); }; - - if (that.queue.files[0].name.includes(".HEIC")) { - alert('Sorry, the HEIC image format is not supported.\nPlease convert your photos to JPEG before uploading.'); - that.hideSpinner(); - that.submit.disabled = false; - break; - } - - that.addPreviewBoxForQueueSlot(i); - that.addPreviewForFile(that.queue.files[i], i, callback); - }; - }); - that.submit.addEventListener('click', function(e) { - e.preventDefault(); - that.process(); - }); - this.submit.disabled = true; -}; - -UploadQueue.prototype.clearPreviews = function() { - this.preview_area.innerHTML = ''; - this.submit.disabled = true; - this.current_upload_index = -1; -} - -UploadQueue.prototype.addPreviewBoxForQueueSlot = function(index) { - var preview_box = document.createElement('div'); - preview_box.id = 'upload_preview_' + index; - this.preview_area.appendChild(preview_box); -}; - -UploadQueue.prototype.addPreviewForFile = function(file, index, callback) { - if (!file) { - return false; - } - - var preview = document.createElement('canvas'); - preview.title = file.name; - - var preview_box = document.getElementById('upload_preview_' + index); - preview_box.appendChild(preview); - - var reader = new FileReader(); - var that = this; - reader.addEventListener('load', function() { - var original = document.createElement('img'); - original.src = reader.result; - - original.addEventListener('load', function() { - // Preparation: make canvas size proportional to the original image. - preview.height = 150; - preview.width = preview.height * (original.width / original.height); - - // First pass: resize to 50% on temp canvas. - var temp = document.createElement('canvas'), - tempCtx = temp.getContext('2d'); - - temp.width = original.width * 0.5; - temp.height = original.height * 0.5; - tempCtx.drawImage(original, 0, 0, temp.width, temp.height); - - // Second pass: resize again on temp canvas. - tempCtx.drawImage(temp, 0, 0, temp.width * 0.5, temp.height * 0.5); - - // Final pass: resize to desired size on preview canvas. - var context = preview.getContext('2d'); - context.drawImage(temp, 0, 0, temp.width * 0.5, temp.height * 0.5, - 0, 0, preview.width, preview.height); - - if (callback) { - callback(); - } }); - }, false); - reader.readAsDataURL(file); -}; - -UploadQueue.prototype.process = function() { - this.showSpinner(this.submit, "Preparing to upload files..."); - if (this.queue.files.length > 0) { + this.submit.addEventListener('click', event => { + event.preventDefault(); + this.process(); + }); this.submit.disabled = true; - this.nextFile(); } -}; -UploadQueue.prototype.nextFile = function() { - var files = this.queue.files; - var i = ++this.current_upload_index; - if (i === files.length) { - this.hideSpinner(); - } else { - this.setSpinnerLabel("Uploading file " + (i + 1) + " out of " + files.length); - this.sendFile(files[i], i, function() { + clearPreviews() { + this.preview_area.innerHTML = ''; + this.submit.disabled = true; + this.current_upload_index = -1; + } + + addPreviewBoxForQueueSlot(index) { + const preview_box = document.createElement('div'); + preview_box.id = 'upload_preview_' + index; + this.preview_area.appendChild(preview_box); + } + + addPreviewForFile(file, index, callback) { + if (!file) { + return false; + } + + const preview = document.createElement('canvas'); + preview.title = file.name; + + const preview_box = document.getElementById('upload_preview_' + index); + preview_box.appendChild(preview); + + const reader = new FileReader(); + reader.addEventListener('load', event => { + const original = document.createElement('img'); + original.src = reader.result; + + original.addEventListener('load', function() { + // Preparation: make canvas size proportional to the original image. + preview.height = 150; + preview.width = preview.height * (original.width / original.height); + + // First pass: resize to 50% on temp canvas. + const temp = document.createElement('canvas'), + tempCtx = temp.getContext('2d'); + + temp.width = original.width * 0.5; + temp.height = original.height * 0.5; + tempCtx.drawImage(original, 0, 0, temp.width, temp.height); + + // Second pass: resize again on temp canvas. + tempCtx.drawImage(temp, 0, 0, temp.width * 0.5, temp.height * 0.5); + + // Final pass: resize to desired size on preview canvas. + const context = preview.getContext('2d'); + context.drawImage(temp, 0, 0, temp.width * 0.5, temp.height * 0.5, + 0, 0, preview.width, preview.height); + + if (callback) { + callback(); + } + }); + }, false); + reader.readAsDataURL(file); + } + + process() { + this.showSpinner(this.submit, "Preparing to upload files..."); + if (this.queue.files.length > 0) { + this.submit.disabled = true; this.nextFile(); + } + } + + nextFile() { + const files = this.queue.files; + const i = ++this.current_upload_index; + if (i === files.length) { + this.hideSpinner(); + } else { + this.setSpinnerLabel("Uploading file " + (i + 1) + " out of " + files.length); + this.sendFile(files[i], i, this.nextFile); + } + } + + sendFile(file, index, callback) { + const request = new XMLHttpRequest(); + request.addEventListener('error', event => { + this.updateProgress(index, -1); }); - } -}; - -UploadQueue.prototype.sendFile = function(file, index, callback) { - // Prepare the request. - var that = this; - var request = new XMLHttpRequest(); - request.addEventListener('error', function(event) { - that.updateProgress(index, -1); - }); - request.addEventListener('progress', function(event) { - that.updateProgress(index, event.loaded / event.total); - }); - request.addEventListener('load', function(event) { - that.updateProgress(index, 1); - if (request.responseText !== null && request.status === 200) { - var obj = JSON.parse(request.responseText); - if (obj.error) { - alert(obj.error); - return; + request.addEventListener('progress', event => { + this.updateProgress(index, event.loaded / event.total); + }); + request.addEventListener('load', event => { + this.updateProgress(index, 1); + if (request.responseText !== null && request.status === 200) { + const obj = JSON.parse(request.responseText); + if (obj.error) { + alert(obj.error); + return; + } + else if (callback) { + callback.call(this, obj); + } } - else if (callback) { - callback.call(that, obj); + }); + + const data = new FormData(); + data.append('uploads', file, file.name); + + request.open('POST', this.upload_url, true); + request.send(data); + } + + addProgressBar(index) { + if (index in this.upload_progress) { + return; + } + + const progress_container = document.createElement('div'); + progress_container.className = 'progress'; + + const progress = document.createElement('div'); + progress_container.appendChild(progress); + + const preview_box = document.getElementById('upload_preview_' + index); + preview_box.appendChild(progress_container); + + this.upload_progress[index] = progress; + } + + updateProgress(index, progress) { + if (!(index in this.upload_progress)) { + this.addProgressBar(index); + } + + const bar = this.upload_progress[index]; + + if (progress >= 0) { + bar.style.width = Math.ceil(progress * 100) + '%'; + } else { + bar.style.width = ""; + if (progress === -1) { + bar.className = "error"; } } - }); - - var data = new FormData(); - data.append('uploads', file, file.name); - - request.open('POST', this.upload_url, true); - request.send(data); -}; - -UploadQueue.prototype.addProgressBar = function(index) { - if (index in this.upload_progress) { - return; } - var progress_container = document.createElement('div'); - progress_container.className = 'progress'; + showSpinner(sibling, label) { + if (this.spinner) { + return; + } - var progress = document.createElement('div'); - progress_container.appendChild(progress); + this.spinner = document.createElement('div'); + this.spinner.className = 'spinner'; + sibling.parentNode.appendChild(this.spinner); - var preview_box = document.getElementById('upload_preview_' + index); - preview_box.appendChild(progress_container); - - this.upload_progress[index] = progress; -}; - -UploadQueue.prototype.updateProgress = function(index, progress) { - if (!(index in this.upload_progress)) { - this.addProgressBar(index); - } - - var bar = this.upload_progress[index]; - - if (progress >= 0) { - bar.style.width = Math.ceil(progress * 100) + '%'; - } else { - bar.style.width = ""; - if (progress === -1) { - bar.className = "error"; + if (label) { + this.spinner_label = document.createElement('span'); + this.spinner_label.className = 'spinner_label'; + this.spinner_label.innerHTML = label; + sibling.parentNode.appendChild(this.spinner_label); } } -}; -UploadQueue.prototype.showSpinner = function(sibling, label) { - if (this.spinner) { - return; + setSpinnerLabel(label) { + if (this.spinner_label) { + this.spinner_label.innerHTML = label; + } } - this.spinner = document.createElement('div'); - this.spinner.className = 'spinner'; - sibling.parentNode.appendChild(this.spinner); - - if (label) { - this.spinner_label = document.createElement('span'); - this.spinner_label.className = 'spinner_label'; - this.spinner_label.innerHTML = label; - sibling.parentNode.appendChild(this.spinner_label); - } -}; - -UploadQueue.prototype.setSpinnerLabel = function(label) { - if (this.spinner_label) { - this.spinner_label.innerHTML = label; + hideSpinner() { + if (this.spinner) { + this.spinner.parentNode.removeChild(this.spinner); + this.spinner = null; + } + if (this.spinner_label) { + this.spinner_label.parentNode.removeChild(this.spinner_label); + this.spinner_label = null; + } } } - -UploadQueue.prototype.hideSpinner = function() { - if (this.spinner) { - this.spinner.parentNode.removeChild(this.spinner); - this.spinner = null; - } - if (this.spinner_label) { - this.spinner_label.parentNode.removeChild(this.spinner_label); - this.spinner_label = null; - } -}; From 3ed84eb4d5a8e0702a3204e165e12209ce492f77 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 12 Mar 2023 11:47:36 +0100 Subject: [PATCH 68/79] UploadQueue: more correct HEIC extension check --- public/js/upload_queue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/upload_queue.js b/public/js/upload_queue.js index 54384f6..506a64b 100644 --- a/public/js/upload_queue.js +++ b/public/js/upload_queue.js @@ -18,7 +18,7 @@ class UploadQueue { this.submit.disabled = false; }; - if (this.queue.files[0].name.includes(".HEIC")) { + if (this.queue.files[0].name.toUpperCase().endsWith(".HEIC")) { alert('Sorry, the HEIC image format is not supported.\nPlease convert your photos to JPEG before uploading.'); this.hideSpinner(); this.submit.disabled = false; From 244af88a9a9636ba92edc49d190fdb9c0710ac40 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 12 Mar 2023 12:02:21 +0100 Subject: [PATCH 69/79] Asset: cleaner handling of conflicting filenames --- controllers/UploadMedia.php | 4 ---- models/Asset.php | 12 ++++++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/controllers/UploadMedia.php b/controllers/UploadMedia.php index 571047b..1457131 100644 --- a/controllers/UploadMedia.php +++ b/controllers/UploadMedia.php @@ -33,14 +33,10 @@ class UploadMedia extends HTMLController if (empty($uploaded_file)) continue; - // DIY slug club. - $slug = $tag->slug . '/' . strtr($uploaded_file['name'], [' ' => '-', '--' => '-', '&' => 'and', '=>' => '', "'" => "", ":"=> "", '\\' => '-']); - $asset = Asset::createNew([ 'filename_to_copy' => $uploaded_file['tmp_name'], 'preferred_filename' => $uploaded_file['name'], 'preferred_subdir' => $tag->slug, - 'slug' => $slug, ]); $new_ids[] = $asset->getId(); diff --git a/models/Asset.php b/models/Asset.php index bdf6b38..8b31c0f 100644 --- a/models/Asset.php +++ b/models/Asset.php @@ -187,9 +187,10 @@ class Asset $new_filename = $preferred_filename; $destination = ASSETSDIR . '/' . $preferred_subdir . '/' . $preferred_filename; - while (file_exists($destination)) + for ($i = 1; file_exists($destination); $i++) { - $filename = pathinfo($preferred_filename, PATHINFO_FILENAME) . '_' . mt_rand(10, 99); + $suffix = $i; + $filename = pathinfo($preferred_filename, PATHINFO_FILENAME) . ' (' . $suffix . ')'; $extension = pathinfo($preferred_filename, PATHINFO_EXTENSION); $new_filename = $filename . '.' . $extension; $destination = dirname($destination) . '/' . $new_filename; @@ -206,11 +207,14 @@ class Asset $mimetype = finfo_file($finfo, $destination); finfo_close($finfo); + // We're going to need the base name a few times... + $basename = pathinfo($new_filename, PATHINFO_FILENAME); + // Do we have a title yet? Otherwise, use the filename. - $title = isset($data['title']) ? $data['title'] : pathinfo($preferred_filename, PATHINFO_FILENAME); + $title = $data['title'] ?? $basename; // Same with the slug. - $slug = isset($data['slug']) ? $data['slug'] : $preferred_subdir . '/' . pathinfo($preferred_filename, PATHINFO_FILENAME); + $slug = $data['slug'] ?? sprintf('%s/%s', $preferred_subdir, $basename); // Detected an image? if (substr($mimetype, 0, 5) == 'image') From 3f66fce2626a7864d43fb4eef171f010271c85a4 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 12 Mar 2023 12:07:17 +0100 Subject: [PATCH 70/79] MediaUploader: explicitly support image/jpeg only --- templates/MediaUploader.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/MediaUploader.php b/templates/MediaUploader.php index 69aa083..1a6aab4 100644 --- a/templates/MediaUploader.php +++ b/templates/MediaUploader.php @@ -21,7 +21,8 @@ class MediaUploader extends SubTemplate

Upload new photos to "', $this->tag->tag, '"

- +
From 29bf6af1f81d940978925c04b2f2bbba23e33cdf Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 12 Mar 2023 12:21:43 +0100 Subject: [PATCH 71/79] Asset: delete thumbnails when deleting an assets --- models/Asset.php | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/models/Asset.php b/models/Asset.php index 8b31c0f..f107b4b 100644 --- a/models/Asset.php +++ b/models/Asset.php @@ -490,9 +490,7 @@ class Asset { $db = Registry::get('db'); - if (!unlink(ASSETSDIR . '/' . $this->subdir . '/' . $this->filename)) - return false; - + // First: delete associated metadata $db->query(' DELETE FROM assets_meta WHERE id_asset = {int:id_asset}', @@ -500,6 +498,7 @@ class Asset 'id_asset' => $this->id_asset, ]); + // Second: figure out what tags to recount cardinality for $recount_tags = $db->queryValues(' SELECT id_tag FROM assets_tags @@ -517,13 +516,30 @@ class Asset Tag::recount($recount_tags); - $return = $db->query(' - DELETE FROM assets + // Third: figure out what associated thumbs to delete + $thumbs_to_delete = $db->queryValues(' + SELECT filename + FROM assets_thumbs WHERE id_asset = {int:id_asset}', [ 'id_asset' => $this->id_asset, ]); + foreach ($thumbs_to_delete as $filename) + { + $thumb_path = THUMBSDIR . '/' . $this->subdir . '/' . $filename; + if (is_file($thumb_path)) + unlink($thumb_path); + } + + $db->query(' + DELETE FROM assets_thumbs + WHERE id_asset = {int:id_asset}', + [ + 'id_asset' => $this->id_asset, + ]); + + // Reset asset ID for tags that use this asset for their thumbnail $rows = $db->query(' SELECT id_tag FROM tags @@ -541,6 +557,17 @@ class Asset } } + // Finally, delete the actual asset + if (!unlink(ASSETSDIR . '/' . $this->subdir . '/' . $this->filename)) + return false; + + $return = $db->query(' + DELETE FROM assets + WHERE id_asset = {int:id_asset}', + [ + 'id_asset' => $this->id_asset, + ]); + return $return; } From 41881594e94f8390fbdbeb22066921cb256b74bb Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 12 Mar 2023 12:34:47 +0100 Subject: [PATCH 72/79] PhotoMosaic: make photo order more intuitive --- models/PhotoMosaic.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/PhotoMosaic.php b/models/PhotoMosaic.php index 605bdb0..db86be7 100644 --- a/models/PhotoMosaic.php +++ b/models/PhotoMosaic.php @@ -79,7 +79,7 @@ class PhotoMosaic return -$priority_diff; // In other cases, we'll just show the newest first. - return $a->getDateCaptured() > $b->getDateCaptured() ? -1 : 1; + return $a->getDateCaptured() <=> $b->getDateCaptured(); } private static function daysApart(Image $a, Image $b) From c73564846890873f8f6866b4924194e35fcfb0d1 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 12 Mar 2023 12:37:57 +0100 Subject: [PATCH 73/79] ViewPhoto: improve image alignment in page --- public/css/default.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/public/css/default.css b/public/css/default.css index 3e5b8c7..ee6e69f 100644 --- a/public/css/default.css +++ b/public/css/default.css @@ -429,6 +429,7 @@ footer a { /* Styling for the photo pages --------------------------------*/ #photo_frame { + padding-top: 1.5vh; text-align: center; } #photo_frame a { @@ -439,8 +440,8 @@ footer a { #photo_frame a img { border: none; display: block; - height: auto; - width: 100%; + height: 97vh; + width: auto; } #previous_photo, #next_photo { From 85be093a3625ff5e368b1223e8885c4bfe429f2f Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 12 Mar 2023 12:42:43 +0100 Subject: [PATCH 74/79] ViewPhoto: improve vertical alignment of prev/next buttons --- public/css/default.css | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/public/css/default.css b/public/css/default.css index ee6e69f..d8294af 100644 --- a/public/css/default.css +++ b/public/css/default.css @@ -448,17 +448,12 @@ footer a { background: #fff; box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); color: #262626; - font-size: 3em; + font-size: 3rem; line-height: 0.5; - padding: 32px 8px; + padding: 2rem 0.5rem; position: fixed; text-decoration: none; - top: 45%; -} -#previous_photo em, #next_photo em { - position: absolute; - top: -1000em; - left: -1000em; + top: calc(50% - 5rem); } span#previous_photo, span#next_photo { opacity: 0.25; From 5c2eff09b85a10cff26eb298d1524381940e2732 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 12 Mar 2023 12:55:32 +0100 Subject: [PATCH 75/79] PhotoPage: apply #photo_frame anchor to clicks as well --- public/js/photonav.js | 4 ++-- templates/PhotoPage.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/js/photonav.js b/public/js/photonav.js index e9019f2..8d43dac 100644 --- a/public/js/photonav.js +++ b/public/js/photonav.js @@ -4,14 +4,14 @@ function enableKeyDownNavigation() { var target = document.getElementById("previous_photo").href; if (target) { event.preventDefault(); - document.location.href = target + '#photo_frame'; + document.location.href = target; } } else if (event.keyCode == 39) { var target = document.getElementById("next_photo").href; if (target) { event.preventDefault(); - document.location.href = target + '#photo_frame'; + document.location.href = target; } } }, false); diff --git a/templates/PhotoPage.php b/templates/PhotoPage.php index 49ac58b..52b6fe5 100644 --- a/templates/PhotoPage.php +++ b/templates/PhotoPage.php @@ -84,14 +84,14 @@ class PhotoPage extends Template { if ($this->previous_photo_url) echo ' - '; + '; else echo ' '; if ($this->next_photo_url) echo ' - '; + '; else echo ' '; From c991f05dd354f99d365893769740fc4610705a89 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 12 Mar 2023 12:58:58 +0100 Subject: [PATCH 76/79] ViewPhoto: rework solution to work for panoramas, too --- public/css/default.css | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/public/css/default.css b/public/css/default.css index d8294af..3aabe66 100644 --- a/public/css/default.css +++ b/public/css/default.css @@ -433,14 +433,16 @@ footer a { text-align: center; } #photo_frame a { - box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); - cursor: -moz-zoom-in; - display: inline-block; + } #photo_frame a img { border: none; - display: block; + box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); + cursor: -moz-zoom-in; + display: inline-block; height: 97vh; + max-width: 100%; + object-fit: contain; width: auto; } From e48f065c2540691a30b251cff0c3d9769dda7612 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Mon, 13 Mar 2023 01:33:10 +0100 Subject: [PATCH 77/79] PhotoIndex: fix inadvertent thumb stretching in rare cases --- public/css/default.css | 1 + 1 file changed, 1 insertion(+) diff --git a/public/css/default.css b/public/css/default.css index 3aabe66..93e7c58 100644 --- a/public/css/default.css +++ b/public/css/default.css @@ -187,6 +187,7 @@ i.space-invader.alt-7::before { .tiled_grid div img { background: url('../images/nothumb.svg') center no-repeat; border: none; + object-fit: cover; width: 100%; } .tiled_grid div h4 { From c6dc6bbac41d7166ca70def7b994b1ad38dc6563 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Mon, 13 Mar 2023 01:37:31 +0100 Subject: [PATCH 78/79] AlbumIndex: don't over-fit placeholder images --- templates/AlbumIndex.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/AlbumIndex.php b/templates/AlbumIndex.php index d311645..ebfe9c1 100644 --- a/templates/AlbumIndex.php +++ b/templates/AlbumIndex.php @@ -61,7 +61,7 @@ class AlbumIndex extends Template else echo ' '; + static::TILE_WIDTH, 'px; height: ', static::TILE_HEIGHT, 'px; object-fit: unset">'; if ($this->show_labels) echo ' From 65cea8ed8ac98eb6132c7e5bb40e66aea3c7ef8c Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Mon, 13 Mar 2023 16:30:24 +0100 Subject: [PATCH 79/79] UploadMedia: only set thumb asset id for tags that don't have one yet --- controllers/UploadMedia.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/controllers/UploadMedia.php b/controllers/UploadMedia.php index 1457131..194cb9e 100644 --- a/controllers/UploadMedia.php +++ b/controllers/UploadMedia.php @@ -42,8 +42,11 @@ class UploadMedia extends HTMLController $new_ids[] = $asset->getId(); $asset->linkTags([$tag->id_tag]); - $tag->id_asset_thumb = $asset->getId(); - $tag->save(); + if (empty($tag->id_asset_thumb)) + { + $tag->id_asset_thumb = $asset->getId(); + $tag->save(); + } } if (isset($_REQUEST['format']) && $_REQUEST['format'] === 'json')