diff --git a/models/GenericTable.php b/models/GenericTable.php index 8ce49f05..9356ccec 100644 --- a/models/GenericTable.php +++ b/models/GenericTable.php @@ -6,19 +6,16 @@ * Kabuki CMS (C) 2013-2015, Aaron van Geffen *****************************************************************************/ -class GenericTable extends PageIndex +class GenericTable { - protected $header = []; - protected $body = []; - protected $page_index = []; + private $header = []; + private $body = []; + private $pageIndex = null; + private $currentPage = 1; - protected $title; - protected $title_class; - protected $tableIsSortable = false; - protected $recordCount; - protected $needsPageIndex = false; - protected $current_page; - protected $num_pages; + private $title; + private $title_class; + private $tableIsSortable = false; public $form_above; public $form_below; @@ -36,34 +33,32 @@ class GenericTable extends PageIndex // Make sure we know whether we can actually sort on something. $this->tableIsSortable = !empty($options['base_url']); - // How much stuff do we have? - $this->recordCount = call_user_func_array($options['get_count'], !empty($options['get_count_params']) ? $options['get_count_params'] : []); + // How much data do we have? + $this->recordCount = $options['get_count'](...(!empty($options['get_count_params']) ? $options['get_count_params'] : [])); - // Should we create a page index? + // How much data do we need to retrieve? $this->items_per_page = !empty($options['items_per_page']) ? $options['items_per_page'] : 30; - $this->needsPageIndex = !empty($this->items_per_page) && $this->recordCount > $this->items_per_page; - $this->index_class = isset($options['index_class']) ? $options['index_class'] : ''; // Figure out where to start. $this->start = empty($options['start']) || !is_numeric($options['start']) || $options['start'] < 0 || $options['start'] > $this->recordCount ? 0 : $options['start']; + // Figure out where we are on the whole, too. + $numPages = ceil($this->recordCount / $this->items_per_page); + $this->currentPage = min(ceil($this->start / $this->items_per_page) + 1, $numPages); + // Let's bear a few things in mind... $this->base_url = $options['base_url']; - // This should be set at all times, too. - if (empty($options['no_items_label'])) - $options['no_items_label'] = ''; - // Gather parameters for the data gather function first. $parameters = [$this->start, $this->items_per_page, $options['sort_order'], $options['sort_direction']]; if (!empty($options['get_data_params']) && is_array($options['get_data_params'])) $parameters = array_merge($parameters, $options['get_data_params']); // Okay, let's fetch the data! - $data = call_user_func_array($options['get_data'], $parameters); + $data = $options['get_data'](...$parameters); - // Clean up a bit. - $rows = $data['rows']; + // Extract data into local variables. + $rawRowData = $data['rows']; $this->sort_order = $data['order']; $this->sort_direction = $data['direction']; unset($data); @@ -71,24 +66,24 @@ class GenericTable extends PageIndex // Okay, now for the column headers... $this->generateColumnHeaders($options); - // Generate a pagination if requested - if ($this->needsPageIndex) - $this->generatePageIndex(); + // Should we create a page index? + $needsPageIndex = !empty($this->items_per_page) && $this->recordCount > $this->items_per_page; + if ($needsPageIndex) + $this->generatePageIndex($options); - // Not a single row in sight? - if (empty($rows)) - $this->body = $options['no_items_label']; - // Otherwise, parse it all! + // Process the data to be shown into rows. + if (!empty($rawRowData)) + $this->processAllRows($rawRowData, $options); else - $this->parseAllRows($rows, $options); + $this->body = $options['no_items_label'] ?? ''; // Got a title? - $this->title = isset($options['title']) ? htmlentities($options['title']) : ''; - $this->title_class = isset($options['title_class']) ? $options['title_class'] : ''; + $this->title = $options['title'] ?? ''; + $this->title_class = $options['title_class'] ?? ''; // Maybe even a form or two? - $this->form_above = isset($options['form_above']) ? $options['form_above'] : (isset($options['form']) ? $options['form'] : null); - $this->form_below = isset($options['form_below']) ? $options['form_below'] : (isset($options['form']) ? $options['form'] : null); + $this->form_above = $options['form_above'] ?? $options['form'] ?? null; + $this->form_below = $options['form_below'] ?? $options['form'] ?? null; } private function generateColumnHeaders($options) @@ -98,10 +93,13 @@ class GenericTable extends PageIndex if (empty($column['header'])) continue; + $isSortable = $this->tableIsSortable && !empty($column['is_sortable']); + $sortDirection = $key == $this->sort_order && $this->sort_direction === 'up' ? 'down' : 'up'; + $header = [ 'class' => isset($column['class']) ? $column['class'] : '', 'colspan' => !empty($column['header_colspan']) ? $column['header_colspan'] : 1, - 'href' => !$this->tableIsSortable || empty($column['is_sortable']) ? '' : $this->getLink($this->start, $key, $key == $this->sort_order && $this->sort_direction == 'up' ? 'down' : 'up'), + 'href' => $isSortable ? $this->getLink($this->start, $key, $sortDirection) : null, 'label' => $column['header'], 'scope' => 'col', 'sort_mode' => $key == $this->sort_order ? $this->sort_direction : null, @@ -112,99 +110,18 @@ class GenericTable extends PageIndex } } - private function parseAllRows($rows, $options) + private function generatePageIndex($options) { - foreach ($rows as $i => $row) - { - $className = $i & 1 ? 'even' : 'odd'; - if (isset($options['row_classifier'])) - $className .= $options['row_classifier']($row); - - $newRow = [ - 'class' => $className, - 'cells' => [], - ]; - - foreach ($options['columns'] as $column) - { - if (isset($column['enabled']) && $column['enabled'] == false) - continue; - - // The hard way? - if (isset($column['parse'])) - { - if (!isset($column['parse']['type'])) - $column['parse']['type'] = 'value'; - - // Parse the basic value first. - switch ($column['parse']['type']) - { - // value: easy as pie. - default: - case 'value': - $value = $row[$column['parse']['data']]; - break; - - // sprintf: filling the gaps! - case 'sprintf': - $parameters = [$column['parse']['data']['pattern']]; - foreach ($column['parse']['data']['arguments'] as $identifier) - $parameters[] = $row[$identifier]; - $value = call_user_func_array('sprintf', $parameters); - break; - - // timestamps: let's make them readable! - case 'timestamp': - if (empty($column['parse']['data']['pattern']) || $column['parse']['data']['pattern'] === 'long') - $pattern = '%F %H:%M'; - elseif ($column['parse']['data']['pattern'] === 'short') - $pattern = '%F'; - else - $pattern = $column['parse']['data']['pattern']; - - if (!is_numeric($row[$column['parse']['data']['timestamp']])) - $timestamp = strtotime($row[$column['parse']['data']['timestamp']]); - else - $timestamp = (int) $row[$column['parse']['data']['timestamp']]; - - if (isset($column['parse']['data']['if_null']) && $timestamp == 0) - $value = $column['parse']['data']['if_null']; - else - $value = strftime($pattern, $timestamp); - break; - - // function: the flexible way! - case 'function': - $value = $column['parse']['data']($row); - break; - } - - // Generate a link, if requested. - if (!empty($column['parse']['link'])) - { - // First, generate the replacement variables. - $keys = array_keys($row); - $values = array_values($row); - foreach ($keys as $keyKey => $keyValue) - $keys[$keyKey] = '{' . strtoupper($keyValue) . '}'; - - $value = '' . $value . ''; - } - } - // The easy way! - else - $value = $row[$column['value']]; - - // Append the cell to the row. - $newRow['cells'][] = [ - 'width' => !empty($column['cell_width']) && is_int($column['cell_width']) ? $column['cell_width'] : null, - 'value' => $value, - ]; - } - - // Append the new row in the body. - $this->body[] = $newRow; - } + $this->pageIndex = new PageIndex([ + 'base_url' => $this->base_url, + 'index_class' => $options['index_class'] ?? '', + 'items_per_page' => $this->items_per_page, + 'linkBuilder' => [$this, 'getLink'], + 'recordCount' => $this->recordCount, + 'sort_direction' => $this->sort_direction, + 'sort_order' => $this->sort_order, + 'start' => $this->start, + ]); } public function getLink($start = null, $order = null, $dir = null) @@ -224,12 +141,6 @@ class GenericTable extends PageIndex return $this->start; } - public function getArray() - { - // Makes no sense to call it for a table, but inherits from PageIndex due to poor design, sorry. - throw new Exception('Function call is ambiguous.'); - } - public function getHeader() { return $this->header; @@ -240,6 +151,16 @@ class GenericTable extends PageIndex return $this->body; } + public function getCurrentPage() + { + return $this->currentPage; + } + + public function getPageIndex() + { + return $this->pageIndex; + } + public function getTitle() { return $this->title; @@ -249,4 +170,94 @@ class GenericTable extends PageIndex { return $this->title_class; } + + private function processAllRows($rows, $options) + { + foreach ($rows as $i => $row) + { + $newRow = [ + 'cells' => [], + ]; + + foreach ($options['columns'] as $column) + { + // Process data for this particular cell. + if (isset($column['parse'])) + $value = self::processCell($column['parse'], $row); + else + $value = $row[$column['value']]; + + // Append the cell to the row. + $newRow['cells'][] = [ + 'value' => $value, + ]; + } + + // Append the new row in the body. + $this->body[] = $newRow; + } + } + + private function processCell($options, $rowData) + { + if (!isset($options['type'])) + $options['type'] = 'value'; + + // Parse the basic value first. + switch ($options['type']) + { + // Basic option: simply take a use a particular data property. + case 'value': + $value = htmlspecialchars($rowData[$options['data']]); + break; + + // Processing via a lambda function. + case 'function': + $value = $options['data']($rowData); + break; + + // Using sprintf to fill out a particular pattern. + case 'sprintf': + $parameters = [$options['data']['pattern']]; + foreach ($options['data']['arguments'] as $identifier) + $parameters[] = $rowData[$identifier]; + + $value = sprintf(...$parameters); + break; + + // Timestamps get custom treatment. + case 'timestamp': + if (empty($options['data']['pattern']) || $options['data']['pattern'] === 'long') + $pattern = '%F %H:%M'; + elseif ($options['data']['pattern'] === 'short') + $pattern = '%F'; + else + $pattern = $options['data']['pattern']; + + if (!is_numeric($rowData[$options['data']['timestamp']])) + $timestamp = strtotime($rowData[$options['data']['timestamp']]); + else + $timestamp = (int) $rowData[$options['data']['timestamp']]; + + if (isset($options['data']['if_null']) && $timestamp == 0) + $value = $options['data']['if_null']; + else + $value = strftime($pattern, $timestamp); + break; + } + + // Generate a link, if requested. + if (!empty($options['link'])) + { + // First, generate the replacement variables. + $keys = array_keys($rowData); + $values = array_values($rowData); + foreach ($keys as $keyKey => $keyValue) + $keys[$keyKey] = '{' . strtoupper($keyValue) . '}'; + + $value = '' . $value . ''; + } + + return $value; + } } diff --git a/models/PageIndex.php b/models/PageIndex.php index 0f17c0e5..6033c9a5 100644 --- a/models/PageIndex.php +++ b/models/PageIndex.php @@ -8,26 +8,47 @@ class PageIndex { - protected $page_index = []; - protected $current_page = 1; - protected $items_per_page = 0; - protected $needsPageIndex = false; - protected $num_pages = 1; - protected $recordCount = 0; - protected $start = 0; - protected $sort_order = null; - protected $sort_direction = null; - protected $base_url; - protected $index_class = 'pagination'; - protected $page_slug = '%AMP%page=%PAGE%'; + private $base_url; + private $current_page = 1; + private $index_class = 'pagination'; + private $items_per_page = 0; + private $linkBuilder; + private $needsPageIndex = false; + private $num_pages = 1; + private $page_index = []; + private $page_slug = '%AMP%page=%PAGE%'; + private $recordCount = 0; + private $sort_direction = null; + private $sort_order = null; + private $start = 0; public function __construct($options) { - foreach ($options as $key => $value) - $this->$key = $value; + static $neededKeys = ['base_url', 'items_per_page', 'recordCount']; + foreach ($neededKeys as $key) + { + if (!isset($options[$key])) + throw new Exception('PageIndex: argument ' . $key . ' missing in options'); + + $this->$key = $options[$key]; + } + + static $optionalKeys = ['index_class', 'linkBuilder', 'page_slug', 'sort_direction', 'sort_order', 'start']; + foreach ($optionalKeys as $key) + if (isset($options[$key])) + $this->$key = $options[$key]; + $this->generatePageIndex(); } + private function buildLink($start = null, $order = null, $dir = null) + { + if (isset($this->linkBuilder)) + return call_user_func($this->linkBuilder, $start, $order, $dir); + else + return $this->getLink($start, $order, $dir); + } + protected function generatePageIndex() { /* @@ -68,7 +89,7 @@ class PageIndex $this->page_index[$p] = [ 'index' => $p, 'is_selected' => $this->current_page == $p, - 'href'=> $this->getLink(($p - 1) * $this->items_per_page, $this->sort_order, $this->sort_direction), + 'href'=> $this->buildLink(($p - 1) * $this->items_per_page, $this->sort_order, $this->sort_direction), ]; // The center of the page index. @@ -81,7 +102,7 @@ class PageIndex $this->page_index[$center] = [ 'index' => $center, 'is_selected' => $this->current_page == $center, - 'href'=> $this->getLink(($center - 1) * $this->items_per_page, $this->sort_order, $this->sort_direction), + 'href'=> $this->buildLink(($center - 1) * $this->items_per_page, $this->sort_order, $this->sort_direction), ]; } @@ -94,7 +115,7 @@ class PageIndex $this->page_index[$p] = [ 'index' => $p, 'is_selected' => $this->current_page == $p, - 'href'=> $this->getLink(($p - 1) * $this->items_per_page, $this->sort_order, $this->sort_direction), + 'href'=> $this->buildLink(($p - 1) * $this->items_per_page, $this->sort_order, $this->sort_direction), ]; // The center of the page index. @@ -107,7 +128,7 @@ class PageIndex $this->page_index[$center] = [ 'index' => $center, 'is_selected' => $this->current_page == $center, - 'href'=> $this->getLink(($center - 1) * $this->items_per_page, $this->sort_order, $this->sort_direction), + 'href'=> $this->buildLink(($center - 1) * $this->items_per_page, $this->sort_order, $this->sort_direction), ]; } @@ -120,7 +141,7 @@ class PageIndex $this->page_index[$p] = [ 'index' => $p, 'is_selected' => $this->current_page == $p, - 'href'=> $this->getLink(($p - 1) * $this->items_per_page, $this->sort_order, $this->sort_direction), + 'href'=> $this->buildLink(($p - 1) * $this->items_per_page, $this->sort_order, $this->sort_direction), ]; // Previous page? @@ -157,11 +178,6 @@ class PageIndex return $url; } - public function getArray() - { - return $this->page_index; - } - public function getPageIndex() { return $this->page_index; diff --git a/templates/FormView.php b/templates/FormView.php index 7ab40ff7..47c52ec1 100644 --- a/templates/FormView.php +++ b/templates/FormView.php @@ -25,7 +25,7 @@ class FormView extends SubTemplate if (!empty($this->title)) echo '