'table table-bordered data_table', 'id' => $table_id]; } $this->table_id = $table_id; parent::__construct($parameters); $this->table_name = $table_name; $this->additional_parameters = []; $this->param_prefix = $table_name.'_'; $this->defaultColumn = (int) $default_column; $this->defaultItemsPerPage = $default_items_per_page; $this->hideItemSelector = false; $defaultRow = api_get_configuration_value('table_default_row'); if (!empty($defaultRow)) { $this->defaultItemsPerPage = $default_items_per_page = $defaultRow; } $cleanSessionData = Session::read('clean_sortable_table'); if ($cleanSessionData === true) { $this->cleanUrlSessionParams(); } // Allow to change paginate in multiples tabs //Session::erase($this->param_prefix.'per_page'); $this->per_page = Session::read($this->param_prefix.'per_page', $default_items_per_page); // If per page changed, then reset the page to 1 if (!empty($this->per_page) && isset($_GET[$this->param_prefix.'per_page']) && $this->per_page != $_GET[$this->param_prefix.'per_page']) { Session::erase($this->param_prefix.'page_nr'); $_GET[$this->param_prefix.'page_nr'] = 1; } $this->per_page = isset($_GET[$this->param_prefix.'per_page']) ? (int) $_GET[$this->param_prefix.'per_page'] : $this->per_page; if (isset($_GET[$this->param_prefix.'per_page'])) { Session::erase($this->param_prefix.'page_nr'); } $this->page_nr = Session::read($this->param_prefix.'page_nr', 1); $this->page_nr = isset($_GET[$this->param_prefix.'page_nr']) ? (int) $_GET[$this->param_prefix.'page_nr'] : $this->page_nr; $this->column = Session::read($this->param_prefix.'column', $default_column); $this->column = isset($_GET[$this->param_prefix.'column']) ? (int) $_GET[$this->param_prefix.'column'] : $this->column; // Default direction. if (in_array(strtoupper($default_order_direction), ['ASC', 'DESC'])) { $this->direction = $default_order_direction; } $my_session_direction = Session::read($this->param_prefix.'direction'); if (!empty($my_session_direction)) { if (!in_array($my_session_direction, ['ASC', 'DESC'])) { $this->direction = 'ASC'; } else { if ($my_session_direction === 'ASC') { $this->direction = 'ASC'; } elseif ($my_session_direction === 'DESC') { $this->direction = 'DESC'; } } } if (isset($_GET[$this->param_prefix.'direction'])) { $my_get_direction = $_GET[$this->param_prefix.'direction']; if (!in_array($my_get_direction, ['ASC', 'DESC'])) { $this->direction = 'ASC'; } else { if ($my_get_direction === 'ASC') { $this->direction = 'ASC'; } elseif ($my_get_direction === 'DESC') { $this->direction = 'DESC'; } } } Session::write($this->param_prefix.'per_page', $this->per_page); Session::write($this->param_prefix.'direction', $this->direction); Session::write($this->param_prefix.'page_nr', $this->page_nr); Session::write($this->param_prefix.'column', $this->column); $this->pager = null; $this->default_items_per_page = $default_items_per_page; $this->total_number_of_items = -1; $this->get_total_number_function = $get_total_number_function; $this->get_data_function = $get_data_function; $this->column_filters = []; $this->form_actions = []; $this->checkbox_name = null; $this->td_attributes = []; $this->th_attributes = []; $this->other_tables = []; $this->dataFunctionParams = []; } /** * Clean URL params when changing student view. */ public function cleanUrlSessionParams() { Session::erase('clean_sortable_table'); $prefix = $this->param_prefix; Session::erase($prefix.'page_nr'); Session::erase($prefix.'column'); Session::erase($prefix.'direction'); Session::erase($prefix.'per_page'); $_GET[$this->param_prefix.'per_page'] = $this->default_items_per_page; $_GET[$this->param_prefix.'page_nr'] = 1; $_GET[$this->param_prefix.'column'] = $this->defaultColumn; $_GET[$this->param_prefix.'direction'] = $this->direction; } /** * @return array */ public function getDataFunctionParams() { return $this->dataFunctionParams; } /** * @param array $dataFunctionParams * * @return $this */ public function setDataFunctionParams($dataFunctionParams) { $this->dataFunctionParams = $dataFunctionParams; return $this; } /** * Get the Pager object to split the showed data in several pages. * * @return Pager_Sliding */ public function get_pager() { if ($this->pager === null) { $params['mode'] = 'Sliding'; $params['perPage'] = $this->per_page; $params['totalItems'] = $this->get_total_number_of_items(); $params['urlVar'] = $this->param_prefix.'page_nr'; $params['currentPage'] = $this->page_nr; $icon_attributes = ['style' => 'vertical-align: middle;']; $params['prevImg'] = Display:: return_icon( 'action_prev.png', get_lang('Previous page'), $icon_attributes ); $params['nextImg'] = Display:: return_icon( 'action_next.png', get_lang('Next page'), $icon_attributes ); $params['firstPageText'] = Display:: return_icon( 'action_first.png', get_lang('First page'), $icon_attributes ); $params['lastPageText'] = Display:: return_icon( 'action_last.png', get_lang('Last page'), $icon_attributes ); $params['firstPagePre'] = ''; $params['lastPagePre'] = ''; $params['firstPagePost'] = ''; $params['lastPagePost'] = ''; $params['spacesBeforeSeparator'] = ''; $params['spacesAfterSeparator'] = ''; $query_vars = array_keys($_GET); $query_vars_needed = [ $this->param_prefix.'column', $this->param_prefix.'direction', $this->param_prefix.'per_page', ]; if (!empty($this->additional_parameters) && count($this->additional_parameters) > 0) { $query_vars_needed = array_merge( $query_vars_needed, array_keys($this->additional_parameters) ); } $query_vars_exclude = array_diff($query_vars, $query_vars_needed); $params['excludeVars'] = $query_vars_exclude; $this->pager = &Pager::factory($params); } return $this->pager; } /** * Display the table. */ public function display() { echo $this->return_table(); } /** * Displays the table, complete with navigation buttons to browse through * the data-pages. */ public function return_table() { $empty_table = false; $content = $this->get_table_html(); if ($this->get_total_number_of_items() == 0) { $cols = $this->getColCount(); $this->setCellAttributes( 1, 0, 'style="font-style: italic;text-align:center;" colspan='.$cols ); $message_empty = api_xml_http_response_encode(get_lang('Empty')); $this->setCellContents(1, 0, $message_empty); $empty_table = true; } $html = ''; if (!$empty_table) { $table_id = 'form_'.$this->table_name.'_id'; $form = $this->get_page_select_form(); $nav = $this->get_navigation_html(); // Only show pagination info when there are items to paginate if ($this->get_total_number_of_items() > $this->default_items_per_page) { $html = '
'; $html .= '
'; $html .= '
'; $html .= '
'.$form.'
'; $html .= '
'; $html .= '
'; $html .= '
'.$this->get_table_title().'
'; $html .= '
'; $html .= '
'; $html .= ''; $html .= '
'; $html .= '
'; $html .= '
'; } if (count($this->form_actions) > 0) { $params = $this->get_sortable_table_param_string().'&'.$this->get_additional_url_paramstring(); $html .= ''; } } return $html; } /** * This function shows the content of a table in a grid. * Should not be use to edit information (edit/delete rows) only. */ public function display_grid() { $empty_table = false; if ($this->get_total_number_of_items() == 0) { $message_empty = api_xml_http_response_encode(get_lang('Empty')); $this->setCellContents(1, 0, $message_empty); $empty_table = true; } $html = ''; if (!$empty_table) { $form = $this->get_page_select_form(); $nav = $this->get_navigation_html(); // @todo This style css must be moved to default.css only for dev echo ''; // @todo This also must be moved // Show only navigations if there are more than 1 page $my_pager = $this->get_pager(); $html .= '
'; if ($my_pager->numPages() > 1) { $html .= '
'; $html .= '
'.$form.'
'; $html .= '
'.$this->get_table_title().'
'; $html .= '
'.$nav.'
'; $html .= '
'; } $html .= '
'; if (count($this->form_actions) > 0) { $params = $this->get_sortable_table_param_string().'&'.$this->get_additional_url_paramstring(); $html .= '
'; } } // Getting the items of the table $items = $this->get_clean_html(false); //no sort // Generation of style classes must be improved. Maybe we need a a table name to create style on the fly: // i.e: .whoisonline_table_grid_container instead of .grid_container // where whoisonline is the table's name like drupal's template engine $html .= '
'; if (is_array($items) && count($items) > 0) { foreach ($items as &$row) { $html .= '
'; $i = 0; foreach ($row as &$element) { $html .= '
'.$element.'
'; $i++; } $html .= '
'; } } $html .= '
'; //close grid_container $html .= '
'; //close main grid $html .= '
'; echo $html; } /** * This function returns the content of a table in a grid * Should not be use to edit information (edit/delete rows) only. * * @param array options of visibility * @param bool hide navigation optionally * @param int content per page when show navigation (optional) * @param bool sort data optionally * * @return string grid html */ public function display_simple_grid( $visibility_options, $hide_navigation = true, $per_page = 20, $sort_data = true, $grid_class = [] ) { $empty_table = false; if ($this->get_total_number_of_items() == 0) { $message_empty = api_xml_http_response_encode(get_lang('Empty')); $this->setCellContents(1, 0, $message_empty); $empty_table = true; } $html = ''; if (!$empty_table) { // If we show the pagination if (!$hide_navigation) { $form = ' '; if ($this->get_total_number_of_items() > $per_page) { if ($per_page > 10) { $form = $this->get_page_select_form(); } $nav = $this->get_navigation_html(); // This also must be moved $html = '
'; $html .= '
'.$form.'
'; $html .= '
'.$this->get_table_title().'
'; $html .= '
'.$nav.'
'; $html .= '
'; } } $html .= '
'; if (count($this->form_actions) > 0) { $params = $this->get_sortable_table_param_string().'&'.$this->get_additional_url_paramstring(); $html .= ''; } } if ($hide_navigation) { $items = $this->table_data; // This is a faster way to get what we want } else { // The normal way $items = $this->get_clean_html($sort_data); // Getting the items of the table } // Generation of style classes must be improved. Maybe we need a a table name to create style on the fly: // i.e: .whoisonline_table_grid_container instead of .grid_container // where whoisonline is the table's name like drupal's template engine if (is_array($visibility_options)) { $filter = false; // The 2nd condition of the if will be loaded } else { $filter = $visibility_options !== false; } $item_css_class = $item_css_style = $grid_css_class = $grid_css_style = ''; if (!empty($grid_class)) { $grid_css_class = $grid_class['main']['class']; $item_css_class = $grid_class['item']['class']; $grid_css_style = isset($grid_class['main']['style']) ? $grid_class['main']['style'] : null; $item_css_style = isset($grid_class['item']['style']) ? $grid_class['item']['style'] : null; } $div = ''; if (is_array($items) && count($items) > 0) { foreach ($items as &$row) { $i = 0; $rows = ''; foreach ($row as &$element) { if ($filter || isset($visibility_options[$i]) && $visibility_options[$i] ) { $rows .= '
'.$element.'
'; } $i++; } $div .= Display::div( $rows, [ 'class' => $item_css_class.' '.$this->table_name.'_grid_item', 'style' => $item_css_style, ] ); } } $html .= Display::div( $div, [ 'class' => $grid_css_class.' '.$this->table_name.'_grid_container', 'style' => $grid_css_style, ] ); $html .= '
'; return $html; } /** * Get the HTML-code with the navigational buttons to browse through the * data-pages. */ public function get_navigation_html() { $pager = $this->get_pager(); $pager_links = $pager->getLinks(); $nav = $pager_links['first'].' '.$pager_links['back']; $nav .= '
'.$pager->getCurrentPageId().' / '.$pager->numPages().'
'; $nav .= $pager_links['next'].' '.$pager_links['last']; $html = Display::tag('div', $nav, ['class' => 'btn-group btn-group-sm', 'role' => 'group']); return $html; } /** * Get the HTML-code with the data-table. */ public function get_table_html() { $pager = $this->get_pager(); $offset = $pager->getOffsetByPageId(); $from = $offset[0] - 1; $table_data = $this->get_table_data($from); $this->processHeaders(); if (is_array($table_data)) { $count = 1; foreach ($table_data as &$row) { $row = $this->filter_data($row); $newRow = []; if (!empty($this->columnsToHide)) { $counter = 0; foreach ($row as $index => $rowInfo) { if (!isset($this->columnsToHide[$index])) { $newRow[$counter] = $rowInfo; $counter++; } } $row = $newRow; } $this->addRow($row); if (isset($row['child_of'])) { $this->setRowAttributes( $count, ['class' => 'hidden hidden_'.$row['child_of']], true ); } $count++; } } if ($this->odd_even_rows_enabled == true) { $this->altRowAttributes( 0, ['class' => 'row_odd'], ['class' => 'row_even'], true ); } foreach ($this->th_attributes as $column => $attributes) { $this->setCellAttributes(0, $column, $attributes); } foreach ($this->td_attributes as $column => $attributes) { $this->setColAttributes($column, $attributes); } return $this->toHTML(); } /** * This function return the items of the table. * * @param bool true for sorting table data or false otherwise * * @return array table row items */ public function get_clean_html($sort = true) { $pager = $this->get_pager(); $offset = $pager->getOffsetByPageId(); $from = $offset[0] - 1; $table_data = $this->get_table_data($from, null, null, null, $sort); $new_table_data = []; if (is_array($table_data)) { foreach ($table_data as $index => &$row) { $row = $this->filter_data($row); $new_table_data[] = $row; } } return $new_table_data; } /** * Get the HTML-code which represents a form to select how many items a page * should contain. */ public function get_page_select_form() { $total_number_of_items = $this->get_total_number_of_items(); if ($total_number_of_items <= $this->default_items_per_page) { return ''; } if ($this->hideItemSelector === true) { return ''; } $result[] = ''; $param[$this->param_prefix.'direction'] = $this->direction; $param[$this->param_prefix.'page_nr'] = $this->page_nr; $param[$this->param_prefix.'column'] = $this->column; if (is_array($this->additional_parameters)) { $param = array_merge($param, $this->additional_parameters); } foreach ($param as $key => &$value) { $result[] = ''; } $result[] = ''; $result[] = ''; $result[] = ''; $result = implode("\n", $result); return $result; } /** * Get the table title. */ public function get_table_title() { $pager = $this->get_pager(); $showed_items = $pager->getOffsetByPageId(); return $showed_items[0].' - '.$showed_items[1].' / '.$this->get_total_number_of_items(); } /** * @return array */ public function getHeaders() { return $this->headers; } /** * Process headers. */ public function processHeaders() { $counter = 0; foreach ($this->headers as $column => $columnInfo) { $label = $columnInfo['label']; $sortable = $columnInfo['sortable']; $th_attributes = $columnInfo['th_attributes']; $td_attributes = $columnInfo['td_attributes']; if (!empty($this->columnsToHide)) { if (isset($this->columnsToHide[$column])) { continue; } } $column = $counter; $param['direction'] = 'ASC'; if ($this->column == $column && $this->direction == 'ASC') { $param['direction'] = 'DESC'; } $param['page_nr'] = $this->page_nr; $param['per_page'] = $this->per_page; $param['column'] = $column; $link = $label; if ($sortable) { $link = ''.$label.''; if ($this->column == $column) { $link .= $this->direction == 'ASC' ? ' ↓' : ' ↑'; } } $this->setHeaderContents(0, $column, $link); if (!is_null($td_attributes)) { $this->td_attributes[$column] = $td_attributes; } if (!is_null($th_attributes)) { $this->th_attributes[$column] = $th_attributes; } $counter++; } } /** * Set the header-label. * * @param int $column The column number * @param string $label The label * @param bool $sortable Is the table sortable by this column? (defatult * = true) * @param string $th_attributes Additional attributes for the th-tag of the * table header * @param string $td_attributes Additional attributes for the td-tags of the * column */ public function set_header( $column, $label, $sortable = true, $th_attributes = ['class' => 'th-header'], $td_attributes = null ) { $this->headers[$column] = [ 'label' => $label, 'sortable' => $sortable, 'th_attributes' => $th_attributes, 'td_attributes' => $td_attributes, ]; } /** * Get the parameter-string with additional parameters to use in the URLs * generated by this SortableTable. */ public function get_additional_url_paramstring() { $param_string_parts = []; if (is_array($this->additional_parameters) && count($this->additional_parameters) > 0) { foreach ($this->additional_parameters as $key => &$value) { $param_string_parts[] = urlencode($key).'='.urlencode($value); } } $result = implode('&', $param_string_parts); foreach ($this->other_tables as $index => &$tablename) { $param = []; if (isset($_GET[$tablename.'_direction'])) { //$param[$tablename.'_direction'] = $_GET[$tablename.'_direction']; $my_get_direction = $_GET[$tablename.'_direction']; if (!in_array($my_get_direction, ['ASC', 'DESC'])) { $param[$tablename.'_direction'] = 'ASC'; } else { $param[$tablename.'_direction'] = $my_get_direction; } } if (isset($_GET[$tablename.'_page_nr'])) { $param[$tablename.'_page_nr'] = intval($_GET[$tablename.'_page_nr']); } if (isset($_GET[$tablename.'_per_page'])) { $param[$tablename.'_per_page'] = intval($_GET[$tablename.'_per_page']); } if (isset($_GET[$tablename.'_column'])) { $param[$tablename.'_column'] = intval($_GET[$tablename.'_column']); } $param_string_parts = []; foreach ($param as $key => &$value) { $param_string_parts[] = urlencode($key).'='.urlencode($value); } if (count($param_string_parts) > 0) { $result .= '&'.implode('&', $param_string_parts); } } return $result; } /** * Get the parameter-string with the SortableTable-related parameters to use * in URLs. */ public function get_sortable_table_param_string() { $param[$this->param_prefix.'direction'] = $this->direction; $param[$this->param_prefix.'page_nr'] = $this->page_nr; $param[$this->param_prefix.'per_page'] = $this->per_page; $param[$this->param_prefix.'column'] = $this->column; $param_string_parts = []; foreach ($param as $key => &$value) { $param_string_parts[] = urlencode($key).'='.urlencode($value); } $res = implode('&', $param_string_parts); return $res; } /** * Add a filter to a column. If another filter was already defined for the * given column, it will be overwritten. * * @param int $column The number of the column * @param string $function The name of the filter-function. This should be a * function wich requires 1 parameter and returns the filtered value. */ public function set_column_filter($column, $function) { $this->column_filters[$column] = $function; } /** * List of columns to hide. * * @param int $column */ public function setHideColumn($column) { $this->columnsToHide[$column] = $column; } /** * Define a list of actions which can be performed on the table-date. * If you define a list of actions, the first column of the table will be * converted into checkboxes. * * @param array $actions A list of actions. The key is the name of the * action. The value is the label to show in the select-box * @param string $checkbox_name The name of the generated checkboxes. The * value of the checkbox will be the value of the first column. */ public function set_form_actions($actions, $checkbox_name = 'id') { $this->form_actions = $actions; $this->checkbox_name = $checkbox_name; } /** * Define a list of additional parameters to use in the generated URLs * $parameters['action'] = 'test'; will be convert in * . * * @param array $parameters */ public function set_additional_parameters($parameters) { $this->additional_parameters = $parameters; } /** * Set other tables on the same page. * If you have other sortable tables on the page displaying this sortable * tables, you can define those other tables with this function. If you * don't define the other tables, there sorting and pagination will return * to their default state when sorting this table. * * @param array $tablenames an array of table names */ public function set_other_tables($tablenames) { $this->other_tables = $tablenames; } /** * Transform all data in a table-row, using the filters defined by the * function set_column_filter(...) defined elsewhere in this class. * If you've defined actions, the first element of the given row will be * converted into a checkbox. * * @param array $row a row from the table * * @return array */ public function filter_data($row) { $url_params = $this->get_sortable_table_param_string().'&'.$this->get_additional_url_paramstring(); foreach ($this->column_filters as $column => &$function) { $firstParam = isset($row[$column]) ? $row[$column] : 0; $row[$column] = call_user_func($function, $firstParam, $url_params, $row); } if (count($this->form_actions) > 0) { if (strlen($row[0]) > 0) { $row[0] = '
'; } } if (is_array($row)) { foreach ($row as &$value) { if (empty($value)) { $value = '-'; } } } return $row; } /** * Get the total number of items. This function calls the function given as * 2nd argument in the constructor of a SortableTable. Make sure your * function has the same parameters as defined here. */ public function get_total_number_of_items() { if ($this->total_number_of_items == -1 && !is_null($this->get_total_number_function)) { $this->total_number_of_items = call_user_func( $this->get_total_number_function, $this->getDataFunctionParams() ); } return $this->total_number_of_items; } /** * @param int $value */ public function setTotalNumberOfItems($value) { $this->total_number_of_items = (int) $value; } /** * Get the data to display. This function calls the function given as * 2nd argument in the constructor of a SortableTable. Make sure your * function has the same parameters as defined here. * * @param int $from index of the first item to return * @param int $per_page The number of items to return * @param int $column The number of the column on which the data should be * @param bool $sort Whether to sort or not * sorted * @param string $direction In which order should the data be sorted (ASC * or DESC) * * @return array */ public function get_table_data( $from = null, $per_page = null, $column = null, $direction = null, $sort = null ) { $data = []; if ($this->get_data_function !== null) { $data = call_user_func( $this->get_data_function, $from, $this->per_page, $this->column, $this->direction, $this->dataFunctionParams ); } return $data; } /** * @param array $data */ public function setTableData($data) { $this->table_data = $data; } }