From f9eefe7b4192452834193064295eea7c5b13e5b3 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 11 Mar 2023 13:20:59 +0100 Subject: [PATCH] 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 ' -
'; + '; } }