tableIsSortable = !empty($options['base_url']); // How much data do we have? $this->recordCount = $options['get_count'](); // 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 = max(1, 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']]; // Okay, let's fetch the data! $data = $options['get_data'](...$parameters); // Extract data into local variables. $rawRowData = $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); // Process the data to be shown into rows. if (!empty($rawRowData)) $this->processAllRows($rawRowData, $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 generateColumnHeaders($options) { foreach ($options['columns'] as $key => $column) { 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'] : '', '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'], '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, '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) { 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 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'][] = [ 'class' => $column['cell_class'] ?? '', '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 = 'Y-m-d H:i'; elseif ($options['data']['pattern'] === 'short') $pattern = 'Y-m-d'; else $pattern = $options['data']['pattern']; if (!isset($rowData[$options['data']['timestamp']])) $timestamp = 0; elseif (!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 = date($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; } }