pics/models/GenericTable.php
Aaron van Geffen cba42a9129 Refactor GenericTable to use PageIndex rather than inherit from it
This has on my todo list for years... I'm glad to finally get around to it.
2021-02-17 20:43:30 +01:00

270 lines
7.9 KiB
PHP

<?php
/*****************************************************************************
* GenericTable.php
* Contains key class GenericTable.
*
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
*****************************************************************************/
class GenericTable
{
protected $header = [];
protected $body = [];
protected $pageIndex = null;
protected $currentPage = 1;
protected $title;
protected $title_class;
protected $tableIsSortable = false;
protected $recordCount;
protected $needsPageIndex = false;
protected $current_page;
protected $num_pages;
public $form_above;
public $form_below;
public function __construct($options)
{
// Make sure we're actually sorting on something sortable.
if (!isset($options['sort_order']) || (!empty($options['sort_order']) && empty($options['columns'][$options['sort_order']]['is_sortable'])))
$options['sort_order'] = '';
// Order in which direction?
if (!empty($options['sort_direction']) && !in_array($options['sort_direction'], ['up', 'down']))
$options['sort_direction'] = 'up';
// Make sure we know whether we can actually sort on something.
$this->tableIsSortable = !empty($options['base_url']);
// How much data do we have?
$this->recordCount = $options['get_count'](...(!empty($options['get_count_params']) ? $options['get_count_params'] : []));
// How much data do we need to retrieve?
$this->items_per_page = !empty($options['items_per_page']) ? $options['items_per_page'] : 30;
// Figure out where to start.
$this->start = empty($options['start']) || !is_numeric($options['start']) || $options['start'] < 0 || $options['start'] > $this->recordCount ? 0 : $options['start'];
// Figure out where we are on the whole, too.
$numPages = ceil($this->recordCount / $this->items_per_page);
$this->currentPage = min(ceil($this->start / $this->items_per_page) + 1, $numPages);
// Let's bear a few things in mind...
$this->base_url = $options['base_url'];
// Gather parameters for the data gather function first.
$parameters = [$this->start, $this->items_per_page, $options['sort_order'], $options['sort_direction']];
if (!empty($options['get_data_params']) && is_array($options['get_data_params']))
$parameters = array_merge($parameters, $options['get_data_params']);
// Okay, let's fetch the data!
$data = $options['get_data'](...$parameters);
// Clean up a bit.
$rows = $data['rows'];
$this->sort_order = $data['order'];
$this->sort_direction = $data['direction'];
unset($data);
// Okay, now for the column headers...
$this->generateColumnHeaders($options);
// Should we create a page index?
$needsPageIndex = !empty($this->items_per_page) && $this->recordCount > $this->items_per_page;
if ($needsPageIndex)
$this->generatePageIndex($options);
// Not a single row in sight?
if (empty($rows))
$this->body = $options['no_items_label'] ?? '';
// Otherwise, parse it all!
else
$this->parseAllRows($rows, $options);
// Got a title?
$this->title = $options['title'] ?? '';
$this->title_class = $options['title_class'] ?? '';
// Maybe even a form or two?
$this->form_above = $options['form_above'] ?? $options['form'] ?? null;
$this->form_below = $options['form_below'] ?? $options['form'] ?? null;
}
private function generateColumnHeaders($options)
{
foreach ($options['columns'] as $key => $column)
{
if (empty($column['header']))
continue;
$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'),
'label' => $column['header'],
'scope' => 'col',
'sort_mode' => $key == $this->sort_order ? $this->sort_direction : null,
'width' => !empty($column['header_width']) && is_int($column['header_width']) ? $column['header_width'] : null,
];
$this->header[] = $header;
}
}
private function generatePageIndex($options)
{
$this->pageIndex = new PageIndex([
'base_url' => $this->base_url,
'index_class' => $options['index_class'] ?? '',
'items_per_page' => $this->items_per_page,
'recordCount' => $this->recordCount,
'sort_direction' => $this->sort_direction,
'sort_order' => $this->sort_order,
'start' => $this->start,
]);
}
private function parseAllRows($rows, $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 = 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 = '<a href="' . str_replace($keys, $values, $column['parse']['link']) . '">' . $value . '</a>';
}
}
// 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;
}
}
public function getLink($start = null, $order = null, $dir = null)
{
if ($start === null)
$start = $this->start;
if ($order === null)
$order = $this->sort_order;
if ($dir === null)
$dir = $this->sort_direction;
return $this->base_url . (strpos($this->base_url, '?') ? '&' : '?') . 'start=' . $start . '&order=' . $order. '&dir=' . $dir;
}
public function getOffset()
{
return $this->start;
}
public function getHeader()
{
return $this->header;
}
public function getBody()
{
return $this->body;
}
public function getCurrentPage()
{
return $this->currentPage;
}
public function getPageIndex()
{
return $this->pageIndex;
}
public function getTitle()
{
return $this->title;
}
public function getTitleClass()
{
return $this->title_class;
}
}