initOrder($options); $this->initPagination($options); $data = $options['get_data']($this->start, $this->items_per_page, $this->sort_order, $this->sort_direction); // Okay, now for the column headers... $this->generateColumnHeaders($options); // Should we create a page index? if ($this->recordCount > $this->items_per_page) $this->generatePageIndex($options); // Process the data to be shown into rows. if (!empty($data)) $this->processAllRows($data, $options); 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'] ?? ''; // 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 initOrder($options) { assert(isset($options['default_sort_order'])); assert(isset($options['default_sort_direction'])); // Validate sort order (column) $this->sort_order = $options['sort_order']; if (empty($this->sort_order) || empty($options['columns'][$this->sort_order]['is_sortable'])) $this->sort_order = $options['default_sort_order']; // Validate sort direction $this->sort_direction = $options['sort_direction']; if (empty($this->sort_direction) || !in_array($this->sort_direction, ['up', 'down'])) $this->sort_direction = $options['default_sort_direction']; } private function initPagination(array $options) { assert(isset($options['base_url'])); assert(isset($options['items_per_page'])); $this->base_url = $options['base_url']; $this->recordCount = $options['get_count'](); $this->items_per_page = !empty($options['items_per_page']) ? $options['items_per_page'] : 30; $this->start = empty($options['start']) || !is_numeric($options['start']) || $options['start'] < 0 || $options['start'] > $this->recordCount ? 0 : $options['start']; $numPages = max(1, ceil($this->recordCount / $this->items_per_page)); $this->currentPage = min(ceil($this->start / $this->items_per_page) + 1, $numPages); } private function generateColumnHeaders($options) { foreach ($options['columns'] as $key => $column) { if (empty($column['header'])) continue; $isSortable = !empty($column['is_sortable']); $sortDirection = $key == $this->sort_order && $this->sort_direction === 'up' ? 'down' : 'up'; $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->getHeaderLink($this->start, $key, $sortDirection) : null, '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, 'linkBuilder' => [$this, 'getHeaderLink'], 'recordCount' => $this->recordCount, 'sort_direction' => $this->sort_direction, 'sort_order' => $this->sort_order, 'start' => $this->start, ]); } public function getHeaderLink($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 getTableClass() { return $this->table_class; } public function getTitle() { return $this->title; } public function getTitleClass() { return $this->title_class; } private function processAllRows($rows, $options) { foreach ($rows as $i => $row) { $newRow = [ 'cells' => [], ]; foreach ($options['columns'] as $column) { // Process formatting if (isset($column['format']) && is_callable($column['format'])) $value = $column['format']($row); elseif (isset($column['format'])) $value = self::processFormatting($column['format'], $row); else $value = $row[$column['value']]; // Turn value into a link? if (!empty($column['link'])) $value = $this->processLink($column['link'], $value, $row); // Append the cell to the row. $newRow['cells'][] = [ 'class' => $column['cell_class'] ?? '', 'value' => $value, ]; } // Append the new row in the body. $this->body[] = $newRow; } } private function processFormatting($options, $rowData) { if ($options['type'] === 'timestamp') { if (empty($options['pattern']) || $options['pattern'] === 'long') $pattern = 'Y-m-d H:i'; elseif ($options['pattern'] === 'short') $pattern = 'Y-m-d'; else $pattern = $options['pattern']; assert(isset($rowData[$options['value']])); if (!is_numeric($rowData[$options['value']])) $timestamp = strtotime($rowData[$options['value']]); else $timestamp = (int) $rowData[$options['value']]; if (isset($options['if_null']) && $timestamp == 0) $value = $options['if_null']; else $value = date($pattern, $timestamp); return $value; } else throw ValueError('Unexpected formatter type: ' . $options['type']); } private function processLink($template, $value, array $rowData) { $href = $this->rowReplacements($template, $rowData); return '' . $value . ''; } private function rowReplacements($template, array $rowData) { $keys = array_keys($rowData); $values = array_values($rowData); foreach ($keys as $keyKey => $keyValue) $keys[$keyKey] = '{' . strtoupper($keyValue) . '}'; return str_replace($keys, $values, $template); } }