yf_table2 class

Table2 high-level generator, mostly using bootstrap html/css framework.

Fileplugins/table2/classes/yf_table2.class.php
Use_class('table2')

Public properties

A site sets them in its config with $PROJECT_CONF['table2']['NAME'] = ...;

PropertyDefaultDescription
$BS5_CLASSES
array
['CLASS_TOTAL_RECORDS' => ['label label-info', 'badge text-bg-info'], 'CLASS_CONDENSED' => ['table-condensed', 'table-sm'], 'CLASS_HIGHLIGHT' => ['badge-warning', 'text-bg-warning'], 'CLASS_TPL_BADGE' => ['badge badge-%name', 'badge text-bg-%name'], 'CLASS_TPL_LABEL' => ['label label-%name', 'badge text-bg-%name'], 'CLASS_BTN_MINI' => ['btn btn-default btn-mini btn-xs', 'btn btn-outline-secondary btn-sm'], ... (9 items)]
array (
  'CLASS_TOTAL_RECORDS' => 
  array (
    0 => 'label label-info',
    1 => 'badge text-bg-info',
  ),
  'CLASS_CONDENSED' => 
  array (
    0 => 'table-condensed',
    1 => 'table-sm',
  ),
  'CLASS_HIGHLIGHT' => 
  array (
    0 => 'badge-warning',
    1 => 'text-bg-warning',
  ),
  'CLASS_TPL_BADGE' => 
  array (
    0 => 'badge badge-%name',
    1 => 'badge text-bg-%name',
  ),
  'CLASS_TPL_LABEL' => 
  array (
    0 => 'label label-%name',
    1 => 'badge text-bg-%name',
  ),
  'CLASS_BTN_MINI' => 
  array (
    0 => 'btn btn-default btn-mini btn-xs',
    1 => 'btn btn-outline-secondary btn-sm',
  ),
  'CLASS_TPL_TR_VARIANT' => 
  array (
    0 => '%name',
    1 => 'table-%name',
  ),
  'CLASS_DROPDOWN_MENU' => 
  array (
    0 => 'dropdown-menu dropdown-menu-right',
    1 => 'dropdown-menu dropdown-menu-end',
  ),
  'CLASS_DROPDOWN_ITEM' => 
  array (
    0 => '',
    1 => 'dropdown-item',
  ),
)
property => [bs3 default, Bootstrap 5 value]: switched as in yf_html when the table renders under css_framework bs5
$BS5_COLORS
array
['default' => 'secondary', 'important' => 'danger', 'error' => 'danger', 'inverse' => 'dark'] Bootstrap 2 / 3 badge and label colour names => Bootstrap 5 colour, for the `badge` / `label` options under bs5
$CLASS_ACTIVE_SWITCH
string
'form-check form-switch d-inline-block mb-0 align-middle' wrapper and input classes of btn_active(['renderer' => 'switch']) under bs5
$CLASS_ACTIVE_SWITCH_INPUT 'form-check-input yf-active-switch'
$CLASS_AJAX_ADD 'ajax_add'
$CLASS_AJAX_CLONE 'ajax_clone'
$CLASS_AJAX_DELETE 'ajax_delete btn-danger'
$CLASS_AJAX_EDIT 'ajax_edit'
$CLASS_AJAX_VIEW 'ajax_view'
$CLASS_BTN_GROUP
string
'btn-group' wrapper of the row buttons with `buttons_group`, and of the overflow menu of `buttons_dropdown_after`
$CLASS_BTN_MINI 'btn btn-default btn-mini btn-xs'
$CLASS_BTN_MORE_HTML
string
'⋯' inner HTML of the overflow menu toggle
$CLASS_CHANGE_ACTIVE 'change_active'
$CLASS_CONDENSED 'table-condensed'
$CLASS_DROPDOWN_ITEM
string
'' class of a button moved into the overflow menu (its btn* classes leave it, btn-danger becomes text-danger)
$CLASS_DROPDOWN_ITEM_BS4
string
'dropdown-item' class of a button moved into the overflow menu under bs4, which styles only .dropdown-item entries
$CLASS_DROPDOWN_MENU 'dropdown-menu dropdown-menu-right'
$CLASS_HIGHLIGHT 'badge-warning'
$CLASS_ICON_ADD 'icon-plus fa fa-plus'
$CLASS_ICON_BTN 'icon-tasks fa fa-tasks'
$CLASS_ICON_CLONE 'icon-code-fork fa fa-code-fork'
$CLASS_ICON_DELETE 'icon-trash fa fa-trash'
$CLASS_ICON_EDIT 'icon-edit fa fa-edit'
$CLASS_ICON_SAVE 'icon-save fa fa-save'
$CLASS_ICON_STAR 'icon-star fa fa-star'
$CLASS_ICON_VIEW 'icon-eye-open fa fa-eye'
$CLASS_NO_AJAX 'no_ajax'
$CLASS_NO_RECORDS 'alert alert-info'
$CLASS_RESPONSIVE
string
'table-responsive' wrapper of `responsive`; under bs5 a breakpoint (`'md'`) makes it table-responsive-md
$CLASS_STAR_KO 'star-ko'
$CLASS_STAR_OK 'star-ok'
$CLASS_STICKY_HEADER
string
'sticky-top' thead class of `sticky_header` under bs5
$CLASS_STICKY_HEADER_BS3
string
'yf-sticky-header' thead class and inline style of `sticky_header` under Bootstrap 3, which has no sticky utility
$CLASS_STICKY_HEADER_BS3_CSS
string
'thead.yf-sticky-header > tr > th { background-color: #fff'... CSS added once with a Bootstrap 3 sticky header: its header cells are transparent, so the rows would show through
$CLASS_STICKY_HEADER_BS3_STYLE 'position: sticky; top: 0; z-index: 2;'
$CLASS_TABLE_MAIN 'table table-bordered table-striped table-hover'
$CLASS_TOTAL_RECORDS 'label label-info'
$CLASS_TPL_BADGE 'badge badge-%name'
$CLASS_TPL_ICON 'icon icon-%name fa fa-%name'
$CLASS_TPL_LABEL 'label label-%name'
$CLASS_TPL_TR_VARIANT
string
the variant its callback returns) */ Property [ public $CLASS_TPL_TR_VARIANT = '%name' row class of `tr_variant` (%name = the variant its callback returns)
$CLASS_TPL_TR_VARIANT_BS4
string
'table-%name' row class of `tr_variant` under bs4, whose contextual row classes carry the table- prefix
$CLASS_VERY_CONDENSED 'table-very-condensed'
$ESCAPE_FILTER_VALUES
bool
false SECURITY: filter values of between and in / in_array are escaped and quoted in the SQL, and an array condition without 'and' takes it from the "<field>__and" filter key; default false keeps the raw values (4.0 flips)
$ESCAPE_TEXT
bool
false SECURITY: text() cells are HTML-escaped unless the column passes ['raw' => true]; default false prints DB values as they are (4.0 flips)
$_auto_names []
$_bs5_switched
array
[] properties of BS5_CLASSES switched to their bs5 value
$_buttons []
$_callbacks []
$_chained_mode false
$_data_sql_names []
$_extend []
$_fields []
$_filter_data []
$_filter_params []
$_form null
$_form_params []
$_ids []
$_on []
$_pager null
$_pages ''
$_pair_allow_deny null
$_pair_yes_no null
$_params []
$_rownum_counter 0
$_rowspan []
$_sql null
$_sql_with_filter null
$_total 0
$lang_def_country null

Public methods 81

admin

public function admin($name = '', $link = '', $data = '', $extra = [])

Currently designed only for admin usage.

    public function admin($name = '', $link = '', $data = '', $extra = [])
    {
        if (is_array($link)) {
            $extra = (array) $extra + $link;
            $link = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if ( ! $name) {
            $name = ($extra['name'] ?? null) ?: 'user_id';
        }
        if ($link) {
            $extra['link'] = $link;
        }
        if ( ! $extra['link']) {
            $extra['link'] = url_admin('/admin/edit/%d');
        }
        if ( ! $extra['link_field_name']) {
            $extra['link_field_name'] = $name;
        }
        $extra['data'] = $data ?: $extra['data'];
        $_name = 'user';
        $this->_params['custom_fields'][$_name] = ['SELECT id, CONCAT(id, IF(STRCMP(login,""), CONCAT("; ",login), "")) AS user_name FROM ' . db('admin') . ' WHERE id IN(%ids)', $name];
        return $this->text($_name, '', $extra);
    }

allow_deny

public function allow_deny($name, $extra = [])
    public function allow_deny($name, $extra = [])
    {
        if ( ! isset($this->_pair_allow_deny)) {
            $this->_pair_allow_deny = str_replace('class="', 'disabled class="', $this->_bs5_pair(main()->get_data('pair_allow_deny')));
        }
        $extra['data'] = $this->_pair_allow_deny;
        return $this->func($name, function ($field, $params, $row) {
            $extra = (array) $params['extra'];
            $extra['data'] = (array) $extra['data'];
            return $extra['data'][$field];
        }, $extra);
    }

auto

public function auto($params = [])

Enabling automatic fields parsing mode.

    public function auto($params = [])
    {
        $this->_params['auto'] = true;
        foreach ((array) $params as $k => $v) {
            $this->_params[$k] = $v;
        }
        return $this;
    }

btn

public function btn($name, $link, $extra = [])
    public function btn($name, $link, $extra = [])
    {
        if (is_array($link)) {
            $extra = $link;
            $link = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $this->_buttons[] = [
            'type' => __FUNCTION__,
            'name' => $name,
            'extra' => $extra,
            'link' => $link,
            'func' => function ($row, $params, $instance_params, $table) {
                $extra = $params['extra'];
                $override_id = '';
                if (isset($extra['id'])) {
                    $override_id = $extra['id'];
                }
                if (isset($instance_params['id'])) {
                    $override_id = $instance_params['id'];
                }
                if (($instance_params['btn_no_text'] ?? null) || ($extra['btn_no_text'] ?? null)) {
                    $no_text = 1;
                }
                $id = $override_id ? $override_id : 'id';
                $icon = (($extra['icon'] ?? null) ? ' ' . $extra['icon'] : $table->CLASS_ICON_BTN);
                $link = trim(($params['link'] ?? null) . ($instance_params['links_add'] ?? null));
                if (strlen($link)) {
                    $link = $table->_process_link_params($link, $row, $extra + ['id' => $id]);
                    if ( ! $table->_is_link_allowed($link)) {
                        return '';
                    }
                }
                if ($extra['rewrite'] ?? null) {
                    if ($extra['rewrite'] === 'user') {
                        $link = url_user($link);
                    } elseif ($extra['rewrite'] === 'admin') {
                        $link = url_admin($link);
                    } else {
                        $link = url($link);
                    }
                }
                $extra['href'] = $link;
                $class = (($extra['class'] ?? null) ?: ($extra['a_class'] ?? null)) ?: $table->CLASS_BTN_MINI;
                if ($extra['class_add'] ?? null) {
                    $class = $table->_bs5_btn_class($class, $extra['class_add']);
                }
                if (($extra['no_ajax'] ?? null) || ($instance_params['no_ajax'] ?? null)) {
                    $class .= ' ' . $table->CLASS_NO_AJAX;
                }
                $extra['class'] = $class;
                if ($extra['hidden_toggle'] ?? null) {
                    $extra['data-hidden-toggle'] = $extra['hidden_toggle'];
                }
                $title = ($params['name'] ?? null) ?: ($extra['title'] ?? null) ?: $extra['desc'];
                if ($title) {
                    $extra['title'] = $title;
                }
                $renderer = ($extra['renderer'] ?? null) ?: 'a';
                $body = null;
                if ($renderer == 'a') {
                    $body = '<a' . _attrs($extra, ['href', 'class', 'target', 'title']) . '><i class="' . trim($icon) . '"></i>' . (empty($no_text) ? ' ' . t($params['name']) : '') . '</a> ';
                } elseif ($renderer == 'button') {
                    $body = '<button' . _attrs($extra, ['class', 'target', 'title']) . '><i class="' . trim($icon) . '"></i>' . (empty($no_text) ? ' ' . t($params['name']) : '') . '</button> ';
                }

                $body .= ($extra['hidden_data'] ?? null) ? $table->_hidden_data_container($row, $params, $instance_params) : '';
                return $body;
            },
        ];
        return $this;
    }

btn_active

public function btn_active($name = '', $link = '', $extra = [])
    public function btn_active($name = '', $link = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = $name;
            $name = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if ( ! $name) {
            $name = ($extra['name'] ?? null) ?: 'Active';
        }
        if ( ! $link) {
            $link = ($extra['link'] ?? null) ?: url('/@object/active/%d');
        }
        $this->_buttons[] = [
            'type' => __FUNCTION__,
            'name' => $name,
            'extra' => $extra,
            'link' => $link,
            'func' => function ($row, $params, $instance_params, $table) {
                $extra = $params['extra'];
                $override_id = '';
                if (isset($extra['id'])) {
                    $override_id = $extra['id'];
                }
                if (isset($instance_params['id'])) {
                    $override_id = $instance_params['id'];
                }
                $id = $override_id ? $override_id : 'id';
                $link = $params['link'] . ($instance_params['links_add'] ?? '');
                if (strlen($link)) {
                    $link = $table->_process_link_params($link, $row, $extra + ['id' => $id]);
                    if ( ! $table->_is_link_allowed($link)) {
                        return '';
                    }
                }
                if ($extra['rewrite'] ?? null) {
                    if ($extra['rewrite'] === 'user') {
                        $link = url_user($link);
                    } elseif ($extra['rewrite'] === 'admin') {
                        $link = url_admin($link);
                    } else {
                        $link = url($link);
                    }
                }
                $extra['href'] = $link;
                $extra['title'] = $params['name'];
                if ( ! ($extra['no_ajax'] ?? null)) {
                    $extra['class'] = $table->CLASS_CHANGE_ACTIVE;
                }
                if ( ! isset($extra['data-test'])) {
                    $extra['data-test'] = 'activate';
                }
                if ( ! isset($table->_pair_active)) {
                    $table->_pair_active = $table->_bs5_pair(main()->get_data('pair_active'));
                }
                $values = $table->_pair_active;
                $is_active = (int) ((bool) ($row[strtolower($params['name'])] ?? null));
                $val = $values[$is_active];
                if (($extra['renderer'] ?? null) === 'switch' && $table->_is_bs5()) {
                    return $table->_active_switch($link, $is_active, $extra, $params['name']);
                }
                if ($extra['short'] ?? null) {
                    return html()->btn_active($link, $is_active, $extra);
                }
                return ! ($extra['disabled'] ?? null) ? '<a' . _attrs($extra, ['href', 'class', 'title']) . '>' . $val . '</a> ' : $val;
            },
        ];
        return $this;
    }

btn_clone

public function btn_clone($name = '', $link = '', $extra = [])
    public function btn_clone($name = '', $link = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = $name;
            $name = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if ( ! $name) {
            $name = ($extra['name'] ?? null) ?: 'Clone';
        }
        if ( ! $link) {
            $link = ($extra['link'] ?? null) ?: url('/@object/clone_item/%d');
        }
        if ( ! ($extra['no_ajax'] ?? null)) {
            $extra['class_add'] = ($extra['class_add'] ?? '') . ' ' . $this->CLASS_AJAX_CLONE;
        }
        if ( ! isset($extra['icon'])) {
            $extra['icon'] = $this->CLASS_ICON_CLONE;
        }
        if ( ! isset($extra['data-test'])) {
            $extra['data-test'] = 'clone';
        }
        return $this->btn($name, $link, $extra);
    }

btn_delete

public function btn_delete($name = '', $link = '', $extra = [])
    public function btn_delete($name = '', $link = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = $name;
            $name = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if ( ! $name) {
            $name = ($extra['name'] ?? null) ?: 'Delete';
        }
        if ( ! $link) {
            $link = ($extra['link'] ?? null) ?: url('/@object/delete/%d');
        }
        if ( ! ($extra['no_ajax'] ?? null)) {
            $extra['class_add'] = ($extra['class_add'] ?? '') . ' ' . $this->CLASS_AJAX_DELETE;
        }
        if ( ! isset($extra['icon'])) {
            $extra['icon'] = $this->CLASS_ICON_DELETE;
        }
        if ( ! isset($extra['data-test'])) {
            $extra['data-test'] = 'delete';
        }
        return $this->btn($name, $link, $extra);
    }

btn_edit

public function btn_edit($name = '', $link = '', $extra = [])
    public function btn_edit($name = '', $link = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = $name;
            $name = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if ( ! $name) {
            $name = ($extra['name'] ?? null) ?: 'Edit';
        }
        if ( ! $link) {
            $link = ($extra['link'] ?? null) ?: url('/@object/edit/%d');
        }
        if ( ! ($extra['no_ajax'] ?? null)) {
            $extra['class_add'] = ($extra['class_add'] ?? '') . ' ' . $this->CLASS_AJAX_EDIT;
        }
        if ( ! isset($extra['icon'])) {
            $extra['icon'] = $this->CLASS_ICON_EDIT;
        }
        if ( ! isset($extra['data-test'])) {
            $extra['data-test'] = 'edit';
        }
        return $this->btn($name, $link, $extra);
    }

btn_func

public function btn_func($name, $func, $extra = [])

Callback function will be populated with these params: function($row, $params, $instance_params) {}.

    public function btn_func($name, $func, $extra = [])
    {
        $desc = $extra['desc'] ?? '';
        if ( ! $desc) {
            $desc = ucfirst(str_replace('_', ' ', $name));
        }
        $this->_buttons[] = [
            'type' => __FUNCTION__,
            'name' => $name,
            'extra' => $extra,
            'desc' => $desc,
            'func' => $func,
        ];
        return $this;
    }

btn_view

public function btn_view($name = '', $link = '', $extra = [])
    public function btn_view($name = '', $link = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = $name;
            $name = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if ( ! $name) {
            $name = ($extra['name'] ?? null) ?: 'View';
        }
        if ( ! $link) {
            $link = ($extra['link'] ?? null) ?: url('/@object/view/%d');
        }
        if ( ! ($extra['no_ajax'] ?? null)) {
            $extra['class_add'] = ($extra['class_add'] ?? '') . ' ' . $this->CLASS_AJAX_VIEW;
        }
        if ( ! isset($extra['icon'])) {
            $extra['icon'] = $this->CLASS_ICON_VIEW;
        }
        if ( ! isset($extra['data-test'])) {
            $extra['data-test'] = 'view';
        }
        return $this->btn($name, $link, $extra);
    }
public function chained_wrapper($sql = '', $params = [])

Wrapper for chained mode call from common()->table2().

    public function chained_wrapper($sql = '', $params = [])
    {
        $this->_chained_mode = true;
        $this->_sql = $sql;
        $this->_params = (array) $params;
        html()->_bs5_switch_classes($this, $this->_is_bs5());
        return $this;
    }

check_box

public function check_box($name, $extra = [])
    public function check_box($name, $extra = [])
    {
        $this->form();
        return $this->func($name, function ($field, $params, $row) {
            $extra = $params['extra'];
            if ( ! is_array($extra)) {
                $extra = [];
            }
            if ( ! $extra['name']) {
                $extra['name'] = $params['name'];
            }
            if (false === strpos($extra['name'], '[')) {
                $extra['name'] .= '[' . $field . ']';
            }
            $extra['desc'] = ($extra['desc'] ?? null) ?: '';
            $extra['id'] = 'checkbox_' . $field;
            return _class('html')->check_box($extra);
        }, $extra);
    }

data

public function data($name, $data = [], $extra = [])

Show multiple selected data items Example of data: $this->_params['custom_fields'][$_name] = array('SELECT id, CONCAT(login," ",email) AS user_name FROM '.db('user').' WHERE id IN(%ids)', $name);.

    public function data($name, $data = [], $extra = [])
    {
        $this->form();
        $extra['data'] = $data;
        return $this->func($name, function ($field, $params, $row) {
            $extra = $params['extra'];
            $out = [];
            foreach (explode(',', trim(trim(str_replace(',' . PHP_EOL, ',', $field), ','))) as $k => $v) {
                $v = trim($v);
                if ( ! strlen($v)) {
                    continue;
                }
                if ( ! empty($extra['data'][$v])) {
                    $out[$v] = trim($extra['data'][$v]);
                }
            }
            $body = $out ? implode('<br>', $out) : t('--All--');
            return '<small>' . str_replace([' ', "\t"], ' ', $body) . '</small>';
        }, $extra);
    }

date

public function date($name, $desc = '', $extra = [])
    public function date($name, $desc = '', $extra = [])
    {
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if ( ! $desc) {
            $desc = ucfirst(str_replace('_', ' ', ($extra['desc'] ?? null) ?: $name));
        }
        $this->_fields[] = [
            'type' => __FUNCTION__,
            'name' => $name,
            'extra' => $extra,
            'desc' => $desc,
            'func' => function ($field, $params, $row, $instance_params, $table) {
                $extra = $params['extra'];
                $text = str_replace(' ', ' ', _format_date($field, $extra['format']));
                return $table->_apply_badges($text, $extra, $field);
            },
        ];
        return $this;
    }

form

public function form($action = '', $method = '', $extra = [])

Simply tells that current table should consist of form inside.

    public function form($action = '', $method = '', $extra = [])
    {
        if (isset($this->_form_params) && ! $extra['force']) {
            return $this;
        }
        if (is_array($action)) {
            $extra = $action;
            $action = '';
        }
        if (is_array($method)) {
            $extra = $method;
            $method = '';
        }
        $this->_form_params = [
            'action' => $action ?: url('/@object/@action/@id'),
            'method' => $method ?: 'POST',
            'extra' => (array) $extra,
        ];
        return $this;
    }

func

public function func($name, $func = null, $extra = [])

Callback function will be populated with these params: function($field, $params, $row, $instance_params) {}.

    public function func($name, $func = null, $extra = [])
    {
        if ( ! is_string($name) && is_callable($name)) {
            if (is_array($func)) {
                $extra = (array) $extra + $func;
            }
            $func = $name;
            if (isset($extra['name'])) {
                $name = $extra['name'];
            } else {
                $name = __FUNCTION__ . '_' . ++$this->_auto_names[__FUNCTION__];
                $extra['desc'] = ($extra['desc'] ?? null) ?: ''; // Prevent auto-generated id desc
            }
            $this->_params['custom_fields'][$name] = $func;
        }
        $desc = isset($extra['desc']) ? $extra['desc'] : ucfirst(str_replace('_', ' ', $name));
        $this->_fields[] = [
            'type' => __FUNCTION__,
            'name' => $name,
            'extra' => $extra,
            'desc' => $desc,
            'func' => $func,
        ];
        return $this;
    }

header_add

public function header_add($name = '', $link = '', $extra = [])
    public function header_add($name = '', $link = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = $name;
            $name = $extra['name'];
        }
        $extra['display_in'] = 'header';
        return $this->footer_add($name, $link, $extra);
    }

header_submit

public function header_submit($name = '', $extra = [])
    public function header_submit($name = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = $name;
            $name = $extra['name'];
        }
        $extra['display_in'] = 'header';
        return $this->footer_submit($name, $extra);
    }

icon

public function icon($name, $extra = [])
    public function icon($name, $extra = [])
    {
        $this->form();
        return $this->func($name, function ($field, $params, $row) {
            $icon = trim($field);
            if ( ! $icon) {
                return '';
            }
            // Icon class from bootstrap icon class names
            if (preg_match('/^icon\-[a-z0-9_-]+$/i', $icon)) {
                return '<i class="' . $icon . '"></i>';
            }
            $_icon_path = PROJECT_PATH . 'uploads/icons/' . $icon;
            $icon_src = null;
            if (file_exists(INCLUDE_PATH . $_icon_path)) {
                $icon_src = WEB_PATH . $_icon_path;
            }
            if ($icon_src) {
                return '<img src="' . _prepare_html($icon_src) . '" />';
            }

            return '';
        }, $extra);
    }

image

public function image($name, $path, $link = '', $extra = [])
    public function image($name, $path, $link = '', $extra = [])
    {
        return _class('table2_image', 'classes/table2/')->image($name, $path, $link = '', $extra, $this);
    }

input

public function input($name, $extra = [])
    public function input($name, $extra = [])
    {
        $this->form();
        return $this->func($name, function ($field, $params, $row) {
            $extra = $params['extra'];
            $padding = null;
            if ($extra['padding'] && $row['level']) {
                $padding = '<span style="padding-left:' . ($row['level'] * 20) . 'px; padding-right:5px; float: left; font-size: 25px; line-height: 30px;">└</span>';
            }
            $value = $field;
            if ($extra['propose_url_from'] && ! strlen($value)) {
                $value = common()->_propose_url_from_name($row[$extra['propose_url_from']]);
            }
            return $padding . _class('html')->input([
                'id' => 'input_' . $params['name'] . '_' . $row['id'],
                'name' => $params['name'] . '[' . $row['id'] . ']',
                'desc' => $params['name'],
                'value' => $value,
                'style' => $extra['padding'] && $row['level'] ? 'width:calc(100% - ' . ($row['level'] * 20 + 30) . 'px)' : '',
            ] + (array) $extra);
        }, $extra);
    }

input_padded

public function input_padded($name, $extra = [])
    public function input_padded($name, $extra = [])
    {
        $extra['padding'] = true;
        return $this->input($name, $extra);
    }

lang

public function lang($name, $data = [], $extra = [])

Display language with default country flag for language.

    public function lang($name, $data = [], $extra = [])
    {
        $_this = $this;
        return $this->func($name, function ($lang) use ($_this) {
            asset('bfh-select');
            if ( ! isset($_this->lang_def_country)) {
                $_this->lang_def_country = main()->get_data('lang_def_country');
            }
            return html()->icon('bfh-flag-' . $_this->lang_def_country[$lang], strtoupper($lang));
        }, $extra);
    }
public function on_after_render($func)
    public function on_after_render($func)
    {
        $this->_on[__FUNCTION__][] = $func;
        return $this;
    }
public function on_before_render($func)
    public function on_before_render($func)
    {
        $this->_on[__FUNCTION__][] = $func;
        return $this;
    }

on_post

public function on_post($func)
    public function on_post($func)
    {
        $this->_on[__FUNCTION__][] = $func;
        return $this;
    }

radio_box

public function radio_box($name, $extra = [])
    public function radio_box($name, $extra = [])
    {
        $this->form();
        return $this->func($name, function ($field, $params, $row) {
            $extra = $params['extra'];
            if ( ! is_array($extra)) {
                $extra = [];
            }
            if ( ! $extra['name']) {
                $extra['name'] = $params['name'];
            }
            if (false === strpos($extra['name'], '[')) {
                $extra['name'] .= '[' . $field . ']';
            }
            $extra['id'] = 'radiobox_' . $field;
            return _class('html')->radio_box($extra);
        }, $extra);
    }

render

public function render($params = [])

Render result table html, gathered by row functions.

    public function render($params = [])
    {
        $ts = null;
        if (DEBUG_MODE) {
            $ts = microtime(true);
        }
        // Merge params passed to table2() and params passed here, with params here have more priority:
        foreach ((array) $params as $k => $v) {
            $this->_params[$k] = $v;
        }
        $params = &$this->_params;

        if (isset($params['data-postload-url'])) {
            _class('table2_postload', 'classes/table2/')->postload($params['postload_params'], $this);
            $params['table_attr'] = trim($params['table_attr'] . ' data-postload-url="' . _prepare_html($params['data-postload-url']) . '"');
        }
        if (isset($params['filter']) && ! is_array($params['filter']) && is_numeric($params['filter']) || is_bool($params['filter'] ?? null) && ! empty($params['filter'])) {
            $filter_name = ($params['filter_name'] ?? null) ?: $_GET['object'] . '__' . $_GET['action'];
            $params['filter'] = $_SESSION[$filter_name];
        }

        $a = $this->_render_get_data($params);
        $data = &$a['data'];
        $ids = &$a['ids'];
        if (main()->is_post()) {
            $on_post = isset($params['on_post']) ? $params['on_post'] : ($this->_on['on_post'] ?? null);
            if ( ! is_array($on_post)) {
                $on_post = [$on_post];
            }
            foreach ((array) $on_post as $func_on_post) {
                if (is_callable($func_on_post)) {
                    $func_on_post($params, $a, $this);
                }
            }
        }
        // Automatically get fields from results
        if (($params['auto'] ?? null) && $data) {
            $this->_render_auto($params, $data);
        }
        // Fill data array with custom fields, also fitting slots with empty strings where no custom data.
        if (($params['custom_fields'] ?? null) && $data && $ids) {
            $this->_render_add_custom_fields($params, $data, $ids);
        }
        $to_hide = [];
        if ($data && ($params['hide_empty'] ?? null)) {
            foreach ((array) current($data) as $k => $v) {
                $to_hide[$k] = $k;
            }
            foreach ((array) $data as $_id => $row) {
                foreach ((array) $row as $k => $v) {
                    if (strlen($v ?? '')) {
                        unset($to_hide[$k]);
                    }
                }
            }
        }
        if (($params['group_by'] ?? null) && $data) {
            if ( ! is_array($params['group_by'])) {
                $params['group_by'] = [$params['group_by']];
            }
            foreach ((array) $params['group_by'] as $group_by) {
                $this->_rowspan[$group_by] = $this->_data_group_by($data, $group_by);
            }
        }

        $on_before_render = $params['on_before_render'] ?? $this->_on['on_before_render'] ?? null;
        if ( ! is_array($on_before_render)) {
            $on_before_render = [$on_before_render];
        }
        foreach ((array) $on_before_render as $func_on_before_render) {
            if (is_callable($func_on_before_render)) {
                $func_on_before_render($params, $data, $this);
            }
        }
        _class('core_events')->fire('table.before_render', ['this' => $this]);

        if ($params['as_json'] ?? null) {
            $body = $this->_render_as_json($params, $a, $to_hide);
        } else {
            $body = $this->_render_as_html($params, $a, $to_hide);
        }

        $on_after_render = $params['on_after_render'] ?? $this->_on['on_after_render'] ?? null;
        if ( ! is_array($on_after_render)) {
            $on_after_render = [$on_after_render];
        }
        foreach ((array) $on_after_render as $func_on_after_render) {
            if (is_callable($func_on_after_render)) {
                $func_on_after_render($params, $a, $body, $this);
            }
        }
        if (array_key_exists('feedback', $params)) {
            $params['feedback']['total'] = count((array) $data);
        }
        if (DEBUG_MODE) {
            $this->_render_debug_info($params, $ts, main()->trace_string());
        }
        _class('core_events')->fire('table.after_render', ['this' => $this]);
        return $body;
    }

rownum

public function rownum($extra = [])

Column counter, knows about pagination.

    public function rownum($extra = [])
    {
        $table = $this;
        $func = function ($val, $extra, $row) use ($table) {
            $pager = $table->_pager;
            $first = $pager['out'][3];
            $cur = ++$table->_rownum_counter;
            return $first + $cur;
        };
        return $this->func($func, $extra);
    }

select_box

public function select_box($name, $extra = [])
    public function select_box($name, $extra = [])
    {
        $this->form();
        return $this->func($name, function ($field, $params, $row) {
            $extra = $params['extra'];
            if ( ! is_array($extra)) {
                $extra = [];
            }
            if ( ! $extra['name']) {
                $extra['name'] = $params['name'];
            }
            if (false === strpos($extra['name'], '[')) {
                $extra['name'] .= '[' . $field . ']';
            }
            $extra['id'] = 'selectbox_' . $field;
            return _class('html')->select_box($extra);
        }, $extra);
    }

stars

public function stars($name, $desc = '', $extra = [])
    public function stars($name, $desc = '', $extra = [])
    {
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if ( ! $desc) {
            $desc = ucfirst(str_replace('_', ' ', ($extra['desc'] ?? null) ?: $name));
        }
        $this->_fields[] = [
            'type' => __FUNCTION__,
            'name' => $name,
            'extra' => $extra,
            'desc' => $desc,
            'func' => function ($field, $params, $row, $instance_params, $table) {
                $extra = $params['extra'];
                $extra['id'] = $extra['name'];
                $color_ok = ($extra['color_ok'] ?? null) ?: 'yellow';
                $color_ko = ($extra['color_ko'] ?? null) ?: '';
                $class = ($extra['class'] ?? null) ?: $table->CLASS_ICON_STAR;
                $class_ok = ($extra['class_ok'] ?? null) ?: $table->CLASS_STAR_OK;
                $class_ko = ($extra['class_ko'] ?? null) ?: $table->CLASS_STAR_KO;
                $max = ($extra['max'] ?? null) ?: 5;
                $stars = ($extra['stars'] ?? null) ?: 5;
                // as yf_form2_stars: a float name is the int key PHP truncated it to, without the deprecation
                $name = $extra['name'] ?? '';
                $key = is_float($name) ? (int) $name : $name;
                $input = isset($row[$key]) ? $row[$key] : $field;
                foreach (range(1, $stars) as $num) {
                    $is_ok = $input >= ($num * $max / $stars) ? 1 : 0;
                    $body[] = '<i class="' . $class . ' ' . ($is_ok ? $class_ok : $class_ko) . '" style="color:' . ($is_ok ? $color_ok : $color_ko) . ';" title="' . $input . '"></i>';
                }
                return implode(PHP_EOL, $body);
            },
        ];
        return $this;
    }

td

public function td($func, $extra = [])

Register callback for TD element.

    public function td($func, $extra = [])
    {
        $this->_callbacks['td'][] = [$func, $extra];
        return $this;
    }

text

public function text($name, $desc = '', $extra = [])
    public function text($name, $desc = '', $extra = [])
    {
        // Shortcut: use second param as $extra
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if ( ! $desc) {
            $desc = ucfirst(str_replace('_', ' ', ($extra['desc'] ?? null) ?: $name));
        }
        $this->_fields[] = [
            'type' => __FUNCTION__,
            'name' => $name,
            'extra' => $extra,
            'desc' => $desc,
            'link' => ($extra['link'] ?? null),
            'data' => ($extra['translate'] ?? null) ? t($extra['data'] ?? null) : ($extra['data'] ?? null),
            'func' => function ($field, $params, $row, $instance_params, $table) {
                $name = $params['name'];
                $extra = $params['extra'];
                $orig_extra = $extra;
                $body = '';
                $link = null;
                if (($extra['padding'] ?? null) && $row['level']) {
                    $body = '<span style="padding-left:' . ($row['level'] * 20) . 'px; padding-right:5px;">└</span>';
                }
                if ( ! ($params['data'] ?? null) && ($extra['data_name'] ?? null)) {
                    $params['data'] = $instance_params['data_sql_names'][$extra['data_name']];
                }
                if ( ! $params['data']) {
                    $text = $field;
                } else {
                    if (is_string($params['data'])) {
                        $text = $params['data'];
                    } else {
                        $text = (isset($params['data'][$field]) ? $params['data'][$field] : $field);
                    }
                }
                $text = (string) $text;
                // Example of overriding data using field from same row: text('id', array('link' => '/shop/products/%d', 'rewrite' => 1, 'data' => '@name'));
                if (substr($text, 0, 1) == '@') {
                    $text = $row[substr($text, 1)] ?? null;
                }
                if ($extra['translate'] ?? null) {
                    $text = t($text);
                }
                if (($params['max_length'] ?? null) && strlen($text) > $params['max_length']) {
                    $text = substr($text, 0, $params['max_length']);
                }
                $is_link_allowed = true;
                if ($params['link']) {
                    $link = $params['link'] . $instance_params['links_add'];
                }
                if ($link) {
                    $link_field_name = $extra['link_field_name'];
                    $link_id = $link_field_name ?: $name;
                    $link = $table->_process_link_params($link, $row, $extra + ['id' => $link_id]);
                    $is_link_allowed = $table->_is_link_allowed($link);
                }
                $icon = ($extra['icon'] ?? null) ? '<i class="' . trim($extra['icon']) . '"></i> ' : '';
                if ($link && $is_link_allowed) {
                    if ($extra['rewrite']) {
                        if ($extra['rewrite'] === 'user') {
                            $link = url_user($link);
                        } elseif ($extra['rewrite'] === 'admin') {
                            $link = url_admin($link);
                        } else {
                            $link = url($link);
                        }
                    }
                    $extra['href'] = _escape_tpl_braces($link);
                    if ( ! isset($extra['nowrap']) || $extra['nowrap']) {
                        $text = str_replace(' ', ' ', $text);
                    }
                    $class = (($extra['class'] ?? null) ?: $extra['a_class']) ?: $table->CLASS_BTN_MINI;
                    if ($extra['class_add']) {
                        $class = $table->_bs5_btn_class($class, $extra['class_add']);
                    }
                    $extra['class'] = $class;
                    $link_trim_width = conf('link_trim_width') ?: 100;
                    if (isset($extra['link_trim_width'])) {
                        $link_trim_width = $extra['link_trim_width'];
                    }
                    $link_text = strlen($text) ? mb_strimwidth($text, 0, $link_trim_width, '...') : t('link');
                    if ($link_text === 'link') {
                        $link_text = '';
                    }
                    $extra['title'] = _escape_tpl_braces(trim($extra['link_title'] ?? '') ?: trim($text));
                    if ($extra['hidden_toggle']) {
                        $extra['data-hidden-toggle'] = $extra['hidden_toggle'];
                    }
                    $body .= strlen($link_text) ? '<a' . _attrs($extra, ['href', 'class', 'title']) . '>' . $icon . _prepare_html($link_text) . '</a>' : '';
                } else {
                    // __clone() nulls every non-CLASS_ property, so an unset instance value falls back to the configured singleton
                    if (($table->ESCAPE_TEXT ?? _class('table2')->ESCAPE_TEXT) && empty($extra['raw'])) {
                        $text = htmlspecialchars((string) $text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8', false);
                    }
                    // the page template parses the table again: a stored nick or comment must not run as a tag; ['raw' => true]
                    // keeps braces, for a column that prints tags on purpose
                    if (empty($extra['raw'])) {
                        $text = _escape_tpl_braces($text);
                    }
                    if (isset($extra['nowrap']) && $extra['nowrap']) {
                        $text = str_replace(' ', ' ', $text);
                    }
                    $body .= $icon . $text;
                }
                $body .= ($extra['hidden_data'] ?? null) ? $table->_hidden_data_container($row, $params, $instance_params) : '';
                return $table->_apply_badges($body, $orig_extra, $field);
            },
        ];
        return $this;
    }

text_padded

public function text_padded($name, $extra = [])
    public function text_padded($name, $extra = [])
    {
        $extra['padding'] = true;
        return $this->text($name, $extra);
    }

tpl_row

public function tpl_row($type = 'input', $name = '', $desc = '', $extra = [])

Wrapper for template engine.

    public function tpl_row($type = 'input', $name = '', $desc = '', $extra = [])
    {
        return $this->$type($name, $desc, $extra);
    }

tr

public function tr($func, $extra = [])

Register callback for TR element.

    public function tr($func, $extra = [])
    {
        $this->_callbacks['tr'][] = [$func, $extra];
        return $this;
    }

user

public function user($name = '', $extra = [])

Currently designed only for admin usage.

    public function user($name = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $name = ($extra['name'] ?? null) ?: $name;
        if ( ! $extra['link']) {
            $extra['link'] = url_admin('/members/edit/%d');
        }
        $func_name = __FUNCTION__;
        $this->on_before_render(function ($p, $data, $table) use ($name, $func_name) {
            if ( ! $data) {
                return false;
            }
            $field = $name;
            $user_ids = [];
            foreach ((array) $data as $a) {
                $id = $a[$field];
                if ( ! $id) {
                    continue;
                }
                if (is_numeric($id)) {
                    $id && $user_ids[$id] = $id;
                } elseif (is_array($id)) {
                    foreach ((array) $id as $_id) {
                        $_id && $user_ids[$_id] = $_id;
                    }
                } elseif (false !== strpos($id, ',')) {
                    foreach (explode(',', $id) as $_id) {
                        $_id = (int) trim($_id);
                        $_id && $user_ids[$_id] = $_id;
                    }
                }
            }
            if ( ! $user_ids) {
                return false;
            }
            foreach ((array) from('user')->whereid(array_keys($user_ids))->all('id,name,login,email,phone,active') as $a) {
                $table->_data_for_func[$func_name][$a['id']] = $a;
            }
        });
        return $this->func($name, function ($id, $e, $a, $p, $table) use ($func_name) {
            if ( ! $id) {
                return false;
            }
            $out = [];
            $user_ids = [];
            if (is_numeric($id)) {
                $user_ids[$id] = $id;
            } elseif (is_array($id)) {
                foreach ((array) $id as $_id) {
                    $_id && $user_ids[$_id] = $_id;
                }
            } elseif (false !== strpos($id, ',')) {
                foreach (explode(',', $id) as $_id) {
                    $_id = (int) trim($_id);
                    $_id && $user_ids[$_id] = $_id;
                }
            }
            foreach ((array) $user_ids as $id) {
                if ( ! isset($table->_data_for_func[$func_name][$id])) {
                    $out[$id] = $id;
                    continue;
                }
                $u = $table->_data_for_func[$func_name][$id];
                $uname = (($u['login'] ?? null) ?: ($u['name'] ?? null) ?: ($u['email'] ?? null) ?: $u['phone']);
                $out[$id] = a(str_replace('%d', $id, $e['extra']['link']), _truncate($uname, 50) . ' [' . $id . ']', 'fa fa-user');
            }
            return implode(PHP_EOL, $out);
        }, $extra);
    }

yes_no

public function yes_no($name = '', $extra = [])
    public function yes_no($name = '', $extra = [])
    {
        if ( ! isset($this->_pair_yes_no)) {
            $this->_pair_yes_no = str_replace('class="', 'disabled class="', $this->_bs5_pair(main()->get_data('pair_yes_no')));
        }
        $extra['data'] = $this->_pair_yes_no;
        return $this->func($name, function ($field, $params, $row) {
            $extra = (array) $params['extra'];
            $extra['data'] = (array) $extra['data'];
            return $extra['data'][$field];
        }, $extra);
    }

Methods starting with _ hooks and helpers; not routable as page actions

__call

public function __call($name, $args)

Catch missing method call.

    public function __call($name, $args)
    {
        return main()->extend_call($this, $name, $args, $return_obj = true);
    }

__clone

public function __clone()

We cleanup object properties when cloning.

    public function __clone()
    {
        $keep_prefix = 'CLASS_';
        $keep_len = strlen($keep_prefix);
        foreach ((array) get_object_vars($this) as $k => $v) {
            if (substr($k, 0, $keep_len) === $keep_prefix) {
                continue;
            }
            // the class map state goes with the CLASS_ values, so a clone can switch them back
            if (in_array($k, ['BS5_CLASSES', 'BS5_COLORS', '_bs5_switched'], true)) {
                continue;
            }
            $this->$k = null;
        }
    }

__toString

public function __toString(): string

Need to avoid calling render() without params.

    public function __toString()
    {
        try {
            return (string) $this->render();
        } catch (Exception $e) {
            return '';
        }
    }
public function _active_switch($link, $is_active, $extra, $name)

btn_active(['renderer' => 'switch']) under bs5: a form-switch whose change goes to the active-toggle URL (asset yf_active_switch: an ajax POST like the `.change_active` handler, or a plain visit with no_ajax).

  • $link string
  • $is_active int
  • $extra array
  • $name string
    public function _active_switch($link, $is_active, $extra, $name)
    {
        asset('yf_active_switch');
        $title = t($name);
        $input = [
            'type' => 'checkbox',
            'role' => 'switch',
            'class' => $this->CLASS_ACTIVE_SWITCH_INPUT,
            'title' => $title,
            'aria-label' => $title,
            'checked' => $is_active ? 'checked' : '',
            'disabled' => ($extra['disabled'] ?? null) ? 'disabled' : '',
            'data-yf-active-url' => $link,
            'data-yf-no-ajax' => ($extra['no_ajax'] ?? null) ? '1' : '',
            'data-test' => $extra['data-test'] ?? '',
        ];
        return '<div class="' . $this->CLASS_ACTIVE_SWITCH . '"><input' . _attrs($input, ['type', 'role', 'class', 'title', 'aria-label', 'checked', 'disabled']) . '></div> ';
    }

_apply_badges

public function _apply_badges($text, $extra = [], $field = null)

Supported: success, warning, important, info, inverse Also support array of badges/labels/classes where will try to find match for a field value.

    public function _apply_badges($text, $extra = [], $field = null)
    {
        if ($extra['badge'] ?? null) {
            $badge = is_array($extra['badge']) && isset($extra['badge'][$field]) ? $extra['badge'][$field] : $extra['badge'];
            if ($badge) {
                $text = '<span class="' . str_replace('%name', $this->_bs5_color($badge), $this->CLASS_TPL_BADGE) . '">' . $text . '</span>';
            }
        } elseif ($extra['label'] ?? null) {
            $label = is_array($extra['label']) && isset($extra['label'][$field]) ? $extra['label'][$field] : $extra['label'];
            if ($label) {
                $text = '<span class="' . str_replace('%name', $this->_bs5_color($label), $this->CLASS_TPL_LABEL) . '">' . $text . '</span>';
            }
        } elseif ($extra['class'] ?? null) {
            $css_class = is_array($extra['class']) && isset($extra['class'][$field]) ? $extra['class'][$field] : $extra['class'];
            if ($css_class) {
                $text = '<span class="' . $css_class . '">' . $text . '</span>';
            }
        }
        return $text;
    }
public function _apply_transform($text, $trans)

Custom transformation function (one or several, also can be callback). Idea get from form validation rules.

    public function _apply_transform($text, $trans)
    {
        if (is_string($trans) && strpos($trans, '|') !== false) {
            $trans = explode('|', $trans);
        }
        if ( ! is_array($trans)) {
            $trans = [$trans];
        }
        foreach ($trans as $fname) {
            if (is_callable($fname)) {
                $text = $fname($text);
            } elseif (is_string($fname) && function_exists($fname)) {
                $text = $fname($text);
            }
        }
        return $text;
    }

_attrs_add

public function _attrs_add($attrs, $class = '', $style = '')

$attrs (an HTML attribute string) with $class added to its class attribute and $style to its style attribute (or as new ones); a quoted or unquoted value (class=a) is merged, written back double-quoted when it was unquoted.

  • $attrs string
  • $class string
  • $style string
    public function _attrs_add($attrs, $class = '', $style = '')
    {
        $attrs = (string) $attrs;
        foreach (['class' => (string) $class, 'style' => (string) $style] as $name => $add) {
            if ( ! strlen($add)) {
                continue;
            }
            if (preg_match('~(^|\s)' . $name . '=(?:(["\'])(.*?)\2|([^\s"\'=<>`]+))~is', $attrs, $m, PREG_OFFSET_CAPTURE)) {
                $quote = strlen($m[2][0]) ? $m[2][0] : '"';
                $old = trim(strlen($m[2][0]) ? $m[3][0] : $m[4][0]);
                $new = $name === 'class' ? trim($old . ' ' . $add) : (strlen($old) ? rtrim($old, '; ') . '; ' : '') . $add;
                $attrs = substr_replace($attrs, $m[1][0] . $name . '=' . $quote . $new . $quote, $m[0][1], strlen($m[0][0]));
            } else {
                $attrs .= ' ' . $name . '="' . $add . '"';
            }
        }
        return $attrs;
    }
public function _bs5_btn_class($class, $add)

$class . ' ' . $add; under bs5 the neutral colour of CLASS_BTN_MINI leaves $class when $add brings another one (btn-danger).

    public function _bs5_btn_class($class, $add)
    {
        return $this->_is_bs5() ? html()->_bs5_btn_class($class, $add) : $class . ' ' . $add;
    }

_bs5_color

public function _bs5_color($name)

Bootstrap 5 colour of a badge / label name under bs5, the name itself otherwise.

    public function _bs5_color($name)
    {
        return $this->_is_bs5() && is_string($name) && isset($this->BS5_COLORS[$name]) ? $this->BS5_COLORS[$name] : $name;
    }

_bs5_pair

public function _bs5_pair($data)

pair_* data handler HTML with the bs5 button classes under bs5 (get_data() caches it for every framework).

    public function _bs5_pair($data)
    {
        return $this->_is_bs5() ? html()->_bs5_btn_html($data) : $data;
    }
public function _data_group_by($data, $group_by)
    public function _data_group_by($data, $group_by)
    {
        $index = [];
        $inverse = [];
        foreach ($data as $id => $row) {
            if ( ! isset($row[$group_by])) {
                continue;
            }
            $val = $row[$group_by];
            $index[$id] = $val;
            if ( ! isset($inverse[$val])) {
                $inverse[$val] = $id;
            }
        }
        $rowspan = [];
        foreach (array_count_values($index) as $val => $num) {
            $id = $inverse[$val];
            $rowspan[$id] = $num;
        }
        return $rowspan;
    }

_extend

public function _extend($name, $func)
    public function _extend($name, $func)
    {
        $this->_extend[$name] = $func;
    }

_filter_array

public function _filter_array(&$data, $filter = [], $filter_params = [])

Simple filtering of the given array. Need to support table() raw array data with filtering.

    public function _filter_array(&$data, $filter = [], $filter_params = [])
    {
        if ( ! $data || ! $filter) {
            return false;
        }
        return _class('table2_filter', 'classes/table2/')->_filter_array($data, $filter, $filter_params, $this);
    }
public function _filter_sql_prepare($filter_data = [], $filter_params = [], $__sql = '', $table = null)
    public function _filter_sql_prepare($filter_data = [], $filter_params = [], $__sql = '', $table = null)
    {
        if ( ! $filter_data) {
            return '';
        }
        return _class('table2_filter', 'classes/table2/')->_filter_sql_prepare($filter_data, $filter_params, $__sql, $table);
    }
public function _get_attrs_string_from_params($params, $_id, $row, $row_id = null)
    public function _get_attrs_string_from_params($params, $_id, $row, $row_id = null)
    {
        if ( ! $params) {
            return '';
        }
        $attrs = '';
        if (is_callable($params)) {
            $attrs = $params($row, $_id, $row_id);
        } elseif (is_array($params)) {
            $attrs = $params[$_id];
        } elseif (is_string($params)) {
            $attrs = $params;
        }
        if (is_array($attrs)) {
            $attrs = _attrs($attrs, ['class', 'style']);
        }
        return $attrs ? ' ' . $attrs : '';
    }
public function _hidden_data_container($row, $params, $instance_params)

Used to embed hidden data blocks, that can be later displayed.

    public function _hidden_data_container($row, $params, $instance_params)
    {
        $extra = $params['extra'];
        $hidden_data = $extra['hidden_data'];
        if (empty($hidden_data)) {
            return '';
        }
        if ( ! is_array($hidden_data)) {
            $hidden_data = [$hidden_data];
        }
        $body = '';
        foreach ((array) $hidden_data as $data) {
            if ( ! $data) {
                continue;
            }
            // Linking data from row element, example: %explain
            if (substr($data, 0, 1) == '%') {
                $name = substr($data, 1);
                $data = isset($row[$name]) ? $row[$name] : $data;
            } else {
                $name = $params['name'];
            }
            if ($data) {
                $body .= '<div style="display:none;" data-hidden-name="' . $name . '">' . $data . '</div>';
            }
        }
        return $body;
    }

_init

public function _init()
    public function _init()
    {
        html()->_bs5_switch_classes($this, $this->_is_bs5());
    }

_init_form

public function _init_form()

Setup form2 class instance to share its methods for form-related components like checkbox, input, etc.

    public function _init_form()
    {
        if ( ! isset($this->_form)) {
            $this->_form = clone _class('form2');
            $this->_form->_chained_mode = false;
        }
        return $this->_form;
    }

_is_bs5

public function _is_bs5()

Whether the table renders Bootstrap 5 (conf('css_framework')).

    public function _is_bs5()
    {
        return conf('css_framework') === 'bs5';
    }
public function _render_add_custom_fields(&$params, &$data, &$ids)
    public function _render_add_custom_fields(&$params, &$data, &$ids)
    {
        if ( ! $data || ! $ids || ! $params['custom_fields']) {
            return false;
        }
        $db = is_object($params['db']) ? $params['db'] : db();
        $ids_sql = implode(',', $ids);
        $custom_foreign_fields = [];
        $db_func_orig = 'get_2d';
        foreach ((array) $params['custom_fields'] as $custom_name => $custom_sql) {
            $foreign_field = $this->_params['custom_fields_id'];
            $_ids_sql = $ids_sql;
            $db_func = $db_func_orig;
            if (is_array($custom_sql)) {
                list($custom_sql, $param2, $param3) = $custom_sql;
                // Check if second param is name of db method
                if (in_array($param2, ['get', 'get_2d', 'get_all'])) {
                    $db_func = $param2;
                    $param3 && $foreign_field = $param3;
                } else {
                    $foreign_field = $param2;
                }
            }
            // In this case we can override name of the field used in virtual foreign key, used for custom field.
            // good example is 'user_id' instead of 'id'
            if ($foreign_field) {
                if ($foreign_field != 'id') {
                    $_ids = [];
                    foreach ((array) $data as $k => $v) {
                        $_ids[$v[$foreign_field]] = $v[$foreign_field];
                    }
                    $_ids_sql = implode(',', $_ids);
                }
                $custom_foreign_fields[$custom_name] = $foreign_field;
            }
            if (is_object($custom_sql) && $custom_sql instanceof yf_db_query_builder_driver) {
                $custom_sql = $custom_sql->sql();
            } elseif (is_callable($custom_sql)) {
                $custom_sql = $custom_sql($_ids_sql, $db_func, $this, $data, $ids);
            }
            if ($custom_sql) {
                $custom_sql = str_replace('%ids', $_ids_sql, $custom_sql);
                $this->_data_sql_names[$custom_name] = $db->$db_func($custom_sql);
            }
        }
        foreach ((array) $data as $_id => $row) {
            foreach ((array) $this->_data_sql_names as $custom_name => $custom_data) {
                if ($custom_foreign_fields[$custom_name]) {
                    $_custom_id = $row[$custom_foreign_fields[$custom_name]];
                } else {
                    $_custom_id = $_id;
                }
                $data[$_id][$custom_name] = isset($custom_data[$_custom_id]) ? (is_array($custom_data[$_custom_id]) ? $custom_data[$_custom_id] : (string) $custom_data[$_custom_id]) : '';
            }
        }
        // Needed to correctly pass inside $instance_params to each function
        $params['data_sql_names'] = $this->_data_sql_names;
    }
public function _render_as_html(&$params, &$a, &$to_hide)

Render table as HTML string.

    public function _render_as_html(&$params, &$a, &$to_hide)
    {
        $body = '';
        $header_links = [];
        if (MAIN_TYPE_ADMIN && ! $params['no_pages'] && ! $params['no_total'] && $a['total']) {
            $header_links[] = '<div class="' . $this->CLASS_TOTAL_RECORDS . '" style="margin: 0 5px;">' . t('Total') . ': ' . $a['total'] . '</div>' . PHP_EOL;
        }
        $body .= ( ! ($params['no_pages'] ?? null) && ($params['pages_on_top'] ?? null) ? $a['pages'] : '') . PHP_EOL;

        foreach ((array) $this->_header_links as $info) {
            $name = $info['name'];
            $func = &$info['func'];
            $header_links[] = $func($info, $params, $this) . PHP_EOL;
        }
        $data = &$a['data'];
        if ($data) {
            if ($this->_form_params) {
                $body .= $this->_init_form()->form_begin($this->_form_params['name'], $this->_form_params['method'], $this->_form_params, $this->_form_params['replace']);
            }
            if ($header_links) {
                $body .= '<div class="' . $this->CLASS_HEADER_LINKS . '">' . implode(PHP_EOL, $header_links) . '</div>';
            }
            if ($params['condensed'] ?? null) {
                $params['table_class'] = ($params['table_class'] ?? '') . ' ' . $this->CLASS_CONDENSED;
            } elseif ($params['very_condensed'] ?? null) {
                $params['table_class'] = ($params['table_class'] ?? '') . ' ' . $this->CLASS_VERY_CONDENSED;
            }
            $table_class = isset($params['force_class']) ? ($params['force_class'] ?? null) : trim(trim($this->CLASS_TABLE_MAIN . ' ' . ($params['table_class'] ?? null)) . ' ' . ($params['table_class_add'] ?? null));
            $table_attrs = (isset($params['table_attr']) ? ' ' . $params['table_attr'] : '');
            $wrapper = $this->_table_wrapper($params);
            $body .= $wrapper[0];
            $body .= '<table class="' . trim($table_class) . '"' . $table_attrs . '>' . PHP_EOL;
            if ( ! ($params['no_header'] ?? null) && ! ($params['rotate_table'] ?? null)) {
                $body .= $this->_render_thead($params, $a, $to_hide);
            }
            $sortable_url = ($params['sortable'] ?? null);
            if ($sortable_url && strlen($sortable_url) <= 5) {
                $sortable_url = url('/@object/sortable');
            }
            if ($params['rotate_table'] ?? null) {
                $body .= $this->_render_table_contents_rotated($data, $params, $to_hide);
            } else {
                $body .= $this->_render_table_contents($data, $params, $to_hide);
            }
            if (($params['totals'] ?? null) && ! ($params['no_header'] ?? null) && ! ($params['rotate_table'] ?? null)) {
                $body .= $this->_render_totals($params, $a, $to_hide);
            }
            if ($params['show_total'] ?? null) {
                $params['caption'] .= PHP_EOL . ' ' . t('Total records:') . ':' . $a['total'] . PHP_EOL;
            }
            if ($params['caption'] ?? null) {
                $body .= '<caption>' . $params['caption'] . '</caption>' . PHP_EOL;
            }
            $body .= '</table>' . PHP_EOL;
            $body .= $wrapper[1];
        } else {
            if ($header_links) {
                $body .= '<div class="' . $this->CLASS_HEADER_LINKS . '">' . implode(PHP_EOL, $header_links) . '</div>';
            }
            if (isset($params['no_records_callback'])) {
                $func = $params['no_records_callback'];
                $body .= $func($params, $a, $this);
            } else {
                if (isset($params['no_records_html'])) {
                    $body .= $params['no_records_html'] . PHP_EOL;
                } else {
                    $body .= (($params['no_records_simple'] ?? null) ? t('No records') : '<div class="' . $this->CLASS_NO_RECORDS . '">' . t('No records') . '</div>') . PHP_EOL;
                }
            }
        }
        $footer_links = [];
        foreach ((array) $this->_footer_links as $info) {
            $name = $info['name'];
            $func = &$info['func'];
            $footer_links[] = $func($info, $params, $this) . PHP_EOL;
        }
        if ($footer_links) {
            $body .= '<div class="' . $this->CLASS_FOOTER_LINKS . '">' . implode(PHP_EOL, $footer_links) . '</div>';
        }
        if ($data && $this->_form_params) {
            $body .= '</form>';
        }
        if ( ! isset($params['pages_on_bottom'])) {
            $params['pages_on_bottom'] = true;
        }
        $body .= ( ! ($params['no_pages'] ?? null) && ($params['pages_on_bottom'] ?? null) ? $a['pages'] : '') . PHP_EOL;
        return trim($body);
    }
public function _render_as_json(&$params, &$a, &$to_hide)

Render table as JSON-encoded string.

    public function _render_as_json(&$params, &$a, &$to_hide)
    {
        $header_links = [];
        foreach ((array) $this->_header_links as $info) {
            $func = &$info['func'];
            $header_links[] = $func($info, $params, $this) . PHP_EOL;
        }
        $footer_links = [];
        foreach ((array) $this->_footer_links as $info) {
            $func = &$info['func'];
            $footer_links[] = $func($info, $params, $this) . PHP_EOL;
        }
        return json_encode([
            'data' => &$a['data'],
            'pages' => &$a['pages'],
            'total' => &$a['total'],
            'header_links' => $header_links,
            'footer_links' => $footer_links,
        ]);
    }

_render_auto

public function _render_auto(&$params, &$data)

Automatically get fields from results.

    public function _render_auto(&$params, &$data)
    {
        if ($params['auto'] && $data) {
            $field_names = array_keys((array) current((array) $data));
            $skip_fields = [];
            foreach ((array) ($this->_params['hidden_map'] ?? null) as $field => $container) {
                $skip_fields[$field] = $field;
            }
            $counter = 0;
            foreach ((array) $field_names as $f) {
                if (isset($skip_fields[$f])) {
                    continue;
                }
                $_extra = [];
                if (++$counter == 1 && ($this->_params['first_col_width'] ?? null)) {
                    $_extra['width'] = $this->_params['first_col_width'];
                }
                foreach ((array) ($this->_params['hidden_map'] ?? null) as $field => $container) {
                    if ($container != $f) {
                        continue;
                    }
                    $_extra['hidden_data'][] = '%' . $field;
                }
                $this->text($f, $_extra);
            }
            if ( ! ($params['auto_no_buttons'] ?? null)) {
                $this->btn_edit();
                $this->btn_delete();
                $this->footer_add();
            }
        }
    }
public function _render_buttons_dropdown($buttons)

The "⋯" overflow menu of `buttons_dropdown_after`: a button group with a toggle and one menu item per button.

  • $buttons array rendered buttons
    public function _render_buttons_dropdown($buttons)
    {
        $items = '';
        foreach ($buttons as $html) {
            $items .= '<li>' . $this->_dropdown_item($html) . '</li>' . PHP_EOL;
        }
        $title = _prepare_html(t('More'));
        // fixed positioning keeps the menu out of the clipping of a table-responsive / scroll box wrapper
        $group_class = $this->CLASS_BTN_GROUP;
        if ($this->_is_bs5()) {
            $toggle_data = ' data-bs-toggle="dropdown" data-bs-popper-config="{"strategy":"fixed"}"';
        } else {
            asset('yf_dropdown_fixed');
            $group_class .= ' yf-dropdown-fixed';
            $toggle_data = ' data-toggle="dropdown" data-display="static" aria-haspopup="true"';
        }
        return '<div class="' . $group_class . '">'
            . '<button type="button" class="' . $this->CLASS_BTN_MINI . '"' . $toggle_data . ' aria-expanded="false" title="' . $title . '" aria-label="' . $title . '">'
            . $this->CLASS_BTN_MORE_HTML . '</button>' . PHP_EOL
            . '<ul class="' . $this->CLASS_DROPDOWN_MENU . '">' . PHP_EOL . $items . '</ul>'
            . '</div>' . PHP_EOL;
    }
public function _render_buttons_grouped($buttons, $params)

Row buttons for the `buttons_group` (one btn-group) and `buttons_dropdown_after` (N buttons stay, the rest go into a "⋯" menu) options. Buttons that rights checks or display_func left empty do not count.

  • $buttons array rendered buttons
  • $params array
    public function _render_buttons_grouped($buttons, $params)
    {
        $buttons = array_values(array_filter($buttons, function ($html) {
            return strlen(trim((string) $html)) > 0;
        }));
        $after = $params['buttons_dropdown_after'] ?? null;
        $menu = '';
        if (is_numeric($after) && count($buttons) > max(0, (int) $after)) {
            $menu = $this->_render_buttons_dropdown(array_splice($buttons, max(0, (int) $after)));
        }
        $body = '';
        foreach ($buttons as $html) {
            $body .= $html . PHP_EOL;
        }
        $body .= $menu;
        if ($params['buttons_group'] ?? null) {
            $body = '<div class="' . $this->CLASS_BTN_GROUP . '" role="group">' . $body . '</div>' . PHP_EOL;
        }
        return $body;
    }
public function _render_debug_info(&$params, $ts = 0, $trace = '')
    public function _render_debug_info(&$params, $ts = 0, $trace = '')
    {
        if ( ! DEBUG_MODE) {
            return false;
        }
        $_fields = [];
        foreach ((array) $this->_fields as $k => $v) {
            $_fields[$k] = ['func' => '%lambda%', 'data' => '%data%'] + $v;
        }
        $_header_links = [];
        foreach ((array) $this->_header_links as $k => $v) {
            $_header_links[$k] = ['func' => '%lambda%', 'data' => '%data%'] + $v;
        }
        $_footer_links = [];
        foreach ((array) $this->_footer_links as $k => $v) {
            $_footer_links[$k] = ['func' => '%lambda%', 'data' => '%data%'] + $v;
        }
        $_buttons = [];
        foreach ((array) $this->_buttons as $k => $v) {
            $_buttons[$k] = ['func' => '%lambda%', 'data' => '%data%'] + $v;
        }
        debug('table2[]', [
            'params' => $params,
            'fields' => $_fields,
            'buttons' => $_buttons,
            'header_links' => $_header_links,
            'footer_links' => $_footer_links,
            'time' => round(microtime(true) - $ts, 5),
            'trace' => $trace ?: main()->trace_string(),
        ]);
    }
public function _render_get_data(&$params)
    public function _render_get_data(&$params)
    {
        $default_per_page = MAIN_TYPE_USER ? conf('user_per_page') : conf('admin_per_page');
        if ($params['rotate_table'] ?? null) {
            $default_per_page = 10;
        }
        $pager = [
            'path' => ($params['pager_path'] ?? null),
            'type' => ($params['pager_type'] ?? null),
            'records_on_page' => ($params['pager_records_on_page'] ?? null) ?: $default_per_page,
            'num_records' => ($params['pager_num_records'] ?? null) ?: 0,
            'stpl_path' => ($params['pager_stpl_path'] ?? null) ?: '',
            'add_get_vars' => ($params['pager_add_get_vars'] ?? null) ?: 1,
            'sql_callback' => ($params['pager_sql_callback'] ?? null) ?: null,
            'extra' => $params['extra'] ?? [],
        ];
        if (isset($params['pager_size'])) {
            $pager['extra']['size'] = $params['pager_size'];
        }
        if (isset($params['pager_align'])) {
            $pager['extra']['align'] = $params['pager_align'];
        }
        if ( ! ($pager['extra']['sql_callback'] ?? null) && $pager['sql_callback']) {
            $pager['extra']['sql_callback'] = &$pager['sql_callback'];
        }
        $sql = $this->_sql;
        $ids = [];
        $data = null;
        $pages = null;
        $total = null;
        $sql_is_array = $sql_is_object = $sql_is_callable = $sql_is_query_builder = false;
        if (is_array($sql)) {
            $sql_is_array = true;
        } elseif (is_callable($sql)) {
            $sql_is_callable = true;
        } elseif (is_object($sql)) {
            if ($sql instanceof yf_db_query_builder_driver) {
                $sql_is_query_builder = true;
            } else {
                $sql_is_object = true;
            }
        }
        if ($sql_is_query_builder) {
            $sql = $sql->sql();
        }
        if ($sql_is_array || $sql_is_object || $sql_is_callable) {
            if ($sql_is_object) {
                $data = obj2arr($sql);
            } elseif ($sql_is_callable) {
                $data = (array) $sql($params);
            } else {
                $data = $sql;
            }
            unset($sql);
            if ($params['filter'] ?? null) {
                $this->_filter_array($data, $params['filter'], $params['filter_params']);
            }
            $pager['out'] = common()->divide_pages(null, null, null, $pager['records_on_page'], count((array) $data), $pager['stpl_path'], $pager['add_get_vars'], $pager['extra']);
            $pages = $pager['out'][1];
            if (count((array) $data) > $pager['records_on_page']) {
                $slice_start = (empty($_GET['page']) ? 0 : (int) ($_GET['page'] ?? null) - 1) * $pager['records_on_page'];
                $slice_end = $pager['records_on_page'];
                $data = array_slice($data, $slice_start, $slice_end, $preserve_keys = true);
            }
            $total = count((array) $data);
            $ids = array_keys($data);
        } elseif (strlen($sql ?? '')) {
            if (is_object($params['db'])) {
                $db = $params['db'];
                $pager['extra']['db'] = $db;
            } else {
                $db = db();
            }
            $filter_sql = null;
            $order_sql = null;
            // TODO: add direct support for query builder and filter
            if ($params['filter']) {
                list($filter_sql, $order_sql) = $this->_filter_sql_prepare($params['filter'], $params['filter_params'], $sql);
                // These 2 arrays needed to be able to use filter parts somehow inside methods
                $this->_filter_data = $params['filter'];
                $this->_filter_params = $params['filter_params'];
            }
            if ( ! $filter_sql && isset($params['filter_params']['__default_filter'])) {
                $filter_sql = $params['filter_params']['__default_filter'];
            }
            if ( ! $order_sql && isset($params['filter_params']['__default_order'])) {
                $order_sql = $params['filter_params']['__default_order'];
            }
            if ($filter_sql || $order_sql) {
                $sql_upper = strtoupper($sql);
                if (strpos($sql, '/*FILTER*/') !== false) {
                    $sql = str_replace('/*FILTER*/', ' ' . $filter_sql . ' ', $sql);
                } elseif (strpos($sql_upper, 'WHERE') === false) {
                    if (strpos($sql_upper, 'GROUP BY') === false) {
                        $sql .= ' WHERE 1 ' . $filter_sql;
                    } else {
                        $sql = str_replace('GROUP BY', ' WHERE 1 ' . $filter_sql . ' GROUP BY', $sql);
                    }
                } else {
                    // update to proper order while filtering with existing filter and groupping
                    if (strpos($sql_upper, 'GROUP BY') === false) {
                        $sql .= ' ' . $filter_sql;
                    } else {
                        $sql = str_replace('WHERE', ' WHERE 1 ' . $filter_sql . ' AND ', $sql);
                    }
                }
                if ($order_sql) {
                    if (strpos($order_sql, 'ORDER BY') === false) {
                        $order_sql = 'ORDER BY ' . $order_sql;
                    }
                    if (strpos($sql, '/*ORDER*/') !== false) {
                        $sql = str_replace('/*ORDER*/', ' ' . $order_sql . ' ', $sql);
                    } else {
                        $sql .= ' ' . $order_sql;
                    }
                }
                $this->_sql_with_filter = $sql;
            }
            $pager['out'] = common()->divide_pages($sql, $pager['path'], $pager['type'], $pager['records_on_page'], $pager['num_records'], $pager['stpl_path'], $pager['add_get_vars'], $pager['extra']);
            $add_sql = $pager['out'][0];
            $pages = $pager['out'][1];
            $total = $pager['out'][2];

            $items = [];
            $q = $db->query($sql . $add_sql);
            while ($a = $db->fetch_assoc($q)) {
                if (isset($a['id'])) {
                    $data[$a['id']] = $a;
                    $ids[$a['id']] = $a['id'];
                } else {
                    $data[] = $a;
                }
            }
        }
        // Save result details to be used by outer code
        $this->_total = $total;
        $this->_pages = $pages;
        $this->_ids = $ids;
        $this->_pager = $pager;

        return [
            'data' => $data,
            'pages' => $pages,
            'total' => $total,
            'ids' => $ids,
        ];
    }
public function _render_table_contents($data, $params = [], $to_hide = [])
    public function _render_table_contents($data, $params = [], $to_hide = [])
    {
        $tbody_attrs = '';
        if (isset($params['tbody'])) {
            $tbody_attrs = is_array($params['tbody']) ? _attrs($params['tbody'], ['class', 'id']) : ' ' . $params['tbody'];
        }
        $body = '<tbody' . $tbody_attrs . '>' . PHP_EOL;
        $collect_buttons = ($params['buttons_group'] ?? null) || is_numeric($params['buttons_dropdown_after'] ?? null);
        foreach ((array) $data as $_id => $row) {
            $tr_attrs = $this->_get_attrs_string_from_params($params['tr'] ?? null, $_id, $row);
            if (isset($params['tr_variant'])) {
                $tr_attrs = $this->_tr_variant_attrs($tr_attrs, $params['tr_variant'], $row, $_id);
            }
            $body .= '<tr' . $tr_attrs . '>' . PHP_EOL;
            foreach ((array) $this->_fields as $info) {
                $name = $info['name'];
                if (isset($to_hide[$name])) {
                    continue;
                }
                $body .= $this->_render_table_td($info, $row, $params, $_id);
            }
            if ($this->_buttons) {
                $body .= '<td nowrap' . $this->_get_attrs_string_from_params($params['td'] ?? null, 'buttons', $row) . '>';
                $buttons = [];
                foreach ((array) $this->_buttons as $info) {
                    $name = $info['name'];
                    $func = &$info['func'];
                    $_extra = &$info['extra'];
                    // Callback to decide if we need to show this field or not
                    if (isset($_extra['display_func']) && is_callable($_extra['display_func'])) {
                        $_display_allowed = $_extra['display_func']($row, $info, $params, $this);
                        if ( ! $_display_allowed) {
                            continue;
                        }
                    }
                    if ($collect_buttons) {
                        $buttons[] = $func($row, $info, $params, $this);
                        continue;
                    }
                    $body .= $func($row, $info, $params, $this) . PHP_EOL;
                }
                if ($collect_buttons) {
                    $body .= $this->_render_buttons_grouped($buttons, $params);
                }
                $body .= '</td>' . PHP_EOL;
            }
            $body .= '</tr>' . PHP_EOL;
        }
        $body .= '</tbody>' . PHP_EOL;
        return $body;
    }
public function _render_table_contents_rotated($data = [], $params = [], $to_hide = [])
    public function _render_table_contents_rotated($data = [], $params = [], $to_hide = [])
    {
        $tbody_attrs = '';
        if (isset($params['tbody'])) {
            $tbody_attrs = is_array($params['tbody']) ? _attrs($params['tbody'], ['class', 'id']) : ' ' . $params['tbody'];
        }
        $body = '<tbody' . $tbody_attrs . '>' . PHP_EOL;
        $row = '';
        foreach ((array) $this->_fields as $info) {
            $name = $info['name'];
            if (isset($to_hide[$name])) {
                continue;
            }
            $body .= '<tr' . $this->_get_attrs_string_from_params($params['tr'] ?? null, $name, $row) . '>' . PHP_EOL;
            foreach ((array) $data as $_id => $row) {
                $body .= $this->_render_table_td($info, $row, $params, $_id);
            }
            $body .= '</tr>' . PHP_EOL;
        }
        if ($this->_buttons) {
            $body .= '<tr>' . PHP_EOL;
            foreach ((array) $data as $_id => $row) {
                $body .= '<td nowrap' . $this->_get_attrs_string_from_params($params['td'], $_id, $row) . '>';
                foreach ((array) $this->_buttons as $info) {
                    $name = $info['name'];
                    $func = &$info['func'];
                    $_extra = &$info['extra'];
                    // Callback to decide if we need to show this field or not
                    if (isset($_extra['display_func']) && is_callable($_extra['display_func'])) {
                        $_display_allowed = $_extra['display_func']($row, $info, $params, $this);
                        if ( ! $_display_allowed) {
                            continue;
                        }
                    }
                    $body .= $func($row, $info, $params, $this) . '<br>' . PHP_EOL;
                }
                $body .= '</td>' . PHP_EOL;
            }
            $body .= '</tr>' . PHP_EOL;
        }
        $body .= '</tbody>' . PHP_EOL;
        return $body;
    }
public function _render_table_td($info, $row, $params, $row_id)
    public function _render_table_td($info, $row, $params, $row_id)
    {
        $name = $info['name'];
        if ( ! array_key_exists($name, $row)) {
            return false;
        }
        $func = &$info['func'];
        $_extra = &$info['extra'];

        $td_width = (($_extra['width'] ?? null) ? ' width="' . preg_replace('~[^[0-9]%]~ims', '', $_extra['width']) . '"' : '');
        $td_nowrap = (($_extra['nowrap'] ?? null) ? ' nowrap="nowrap" ' : '');
        $tip = ($_extra['tip'] ?? null) ? ' ' . $this->_show_tip($_extra['tip'], $info['name'], $row) : '';

        if (($_extra['hl_filter'] ?? null) && isset($this->_filter_data[$name])) {
            $_kw = $this->_filter_data[$name];
            if (is_string($_kw) && strlen($_kw)) {
                $row[$name] = preg_replace('~(' . preg_quote($_kw, '~') . ')~ims', '<b class="' . $this->CLASS_HIGHLIGHT . '">\1</b>', $row[$name]);
            }
        }
        if ($_extra['wordwrap'] ?? null) {
            $row[$name] = wordwrap($row[$name], $_width = $_extra['wordwrap'], $_break = PHP_EOL, $_cut = true);
        }
        if (isset($_extra['transform']) && ! empty($_extra['transform'])) {
            $row[$name] = $this->_apply_transform($row[$name], $_extra['transform']);
        }
        // Callback to decide if we need to show this field or not
        if (isset($_extra['display_func']) && is_callable($_extra['display_func'])) {
            $_display_allowed = $_extra['display_func']($row, $info, $params, $this);
            if ( ! $_display_allowed) {
                return false;
            }
        }
        $td_attrs = '';
        if (isset($params['td']) || isset($_extra['td'])) {
            $td_attrs = $this->_get_attrs_string_from_params(($params['td'] ?? null) ?: $_extra['td'], $name, $row, $row_id);
        }
        if ($row_id !== null && isset($this->_rowspan[$name])) {
            $rowspan = $this->_rowspan[$name][$row_id] ?? null;
            if ($rowspan < 1) {
                return '';
            }
            $td_attrs .= ' rowspan="' . (int) $rowspan . '"';
        }
        return '<td' . $td_width . $td_nowrap . $td_attrs . '>' . $func($row[$name], $info, $row, $params, $this) . $tip . '</td>' . PHP_EOL;
    }

_render_thead

public function _render_thead(&$params, &$a, &$to_hide)
    public function _render_thead(&$params, &$a, &$to_hide)
    {
        $data = &$a['data'];
        $thead_attrs = '';
        if (isset($params['thead'])) {
            $thead_attrs = is_array($params['thead']) ? _attrs($params['thead'], ['class', 'id']) : ' ' . $params['thead'];
        }
        if ($params['sticky_header'] ?? null) {
            if ($this->_is_bs5()) {
                $thead_attrs = $this->_attrs_add($thead_attrs, $this->CLASS_STICKY_HEADER);
            } else {
                $thead_attrs = $this->_attrs_add($thead_attrs, $this->CLASS_STICKY_HEADER_BS3, $this->CLASS_STICKY_HEADER_BS3_STYLE);
                css($this->CLASS_STICKY_HEADER_BS3_CSS, 'inline');
            }
        }
        $body = '<thead' . $thead_attrs . '>' . PHP_EOL;
        $data1row = current($data);
        // Needed to correctly process null values, when some other rows contain real data there
        foreach ((array) $data1row as $k => $v) {
            if (isset($v)) {
                $data1row[$k] = (is_scalar($v) || is_object($v)) ? (string) $v : reset($v);
            }
        }
        $counter2 = 0;
        foreach ((array) $this->_fields as $info) {
            $name = $info['name'];
            if ( ! isset($data1row[$name])) {
                // Fix for anonymous auto-named func data
                if ($info['type'] === 'func' && is_callable($info['func'])) {
                    foreach ((array) $data as $k => $v) {
                        $data[$k][$info['name']] = '';
                    }
                } else {
                    continue;
                }
            }
            if (isset($to_hide[$name])) {
                continue;
            }
            $info['extra'] = (array) $info['extra'];
            if (++$counter2 == 1 && ($this->_params['first_col_width'] ?? null)) {
                $info['extra']['width'] = $this->_params['first_col_width'];
            }
            $th_attrs = '';
            if (isset($info['extra']['th'])) {
                $th_attrs .= is_array($info['extra']['th']) ? ' ' . _attrs($info['extra']['th'], ['class', 'id', 'width']) : ' ' . $info['extra']['th'];
            } elseif (isset($info['extra']['th_id'])) {
                $th_attrs .= $info['extra']['th_id'] ? ' id="' . $info['extra']['th_id'] . '"' : '';
            }
            $th_attrs .= ($info['extra']['width'] ?? null) ? ' width="' . preg_replace('~[^[0-9]%]~ims', '', $info['extra']['width'] ?? null) . '"' : '';
            $th_icon_prepend = (($params['th_icon_prepend'] ?? null) ? '<i class="' . str_replace('%name', $params['th_icon_prepend'], $this->CLASS_TPL_ICON) . '"></i> ' : '');
            $th_icon_append = (($params['th_icon_append'] ?? null) ? ' <i class="' . str_replace('%name', $params['th_icon_append'], $this->CLASS_TPL_ICON) . '"></i>' : '');
            $tip = ($info['extra']['header_tip'] ?? null) ? ' ' . $this->_show_tip($info['extra']['header_tip'] ?? null, $name) : '';
            $title = isset($info['extra']['th_desc']) ? $info['extra']['th_desc'] : $info['desc'];
            $body .= '<th' . $th_attrs . '>' . $th_icon_prepend . t($title) . $th_icon_append . $tip . '</th>' . PHP_EOL;
        }
        if ($this->_buttons) {
            $body .= '<th>' . (isset($params['actions_desc']) ? t($params['actions_desc']) : t('Actions')) . '</th>' . PHP_EOL;
        }
        $body .= '</thead>' . PHP_EOL;
        return $body;
    }
public function _render_totals(&$params, &$a, &$to_hide)
    public function _render_totals(&$params, &$a, &$to_hide)
    {
        $data = &$a['data'];
        $total_fields = $params['totals'];

        $tfoot_attrs = '';
        if (isset($params['tfoot'])) {
            $tfoot_attrs = is_array($params['tfoot']) ? _attrs($params['tfoot'], ['class', 'id']) : ' ' . $params['tfoot'];
        }
        $th_attrs = '';
        $th_icon_prepend = '';
        $tip = '';

        $body = '<tfoot' . $tfoot_attrs . '>' . PHP_EOL;
        foreach ((array) $this->_fields as $info) {
            $name = $info['name'];
            if ( ! isset($total_fields[$name])) {
                $body .= '<th></th>' . PHP_EOL;
                continue;
            }
            $total_value = $total_fields[$name];
            if (is_callable($total_value)) {
                $total_value = $total_value($this, $info, $params, $a);
            }
            $body .= '<th' . $th_attrs . '>' . $th_icon_prepend . $total_value . $th_icon_prepend . $tip . '</th>' . PHP_EOL;
        }
        if ($this->_buttons) {
            $body .= '<th>' . t('TOTALS') . '</th>' . PHP_EOL;
        }
        $body .= '</tfoot>' . PHP_EOL;

        return $body;
    }

_show_tip

public function _show_tip($value = '', $name = '', $row = [])
    public function _show_tip($value = '', $name = '', $row = [])
    {
        $tip = '';
        if (is_string($value)) {
            $tip = $value;
        } elseif (is_array($value)) {
            if ( ! empty($row) && isset($row[$name])) {
                $tip = $value[$row[$name]];
            } elseif (isset($value[$name])) {
                $tip = $value[$name];
            }
        } elseif (is_callable($value)) {
            $tip = $value($name, $row);
        }
        return strlen($tip) ? tip($tip) : '';
    }
public function _table_wrapper($params)

Opening and closing HTML around the table for the `responsive` option (true, or a bs5 breakpoint: sm, md, lg, xl, xxl) and for `sticky_header` given as a height ('400px', '60vh'): a scroll box, so the header sticks inside it. ['', ''] without them.

  • $params array
    public function _table_wrapper($params)
    {
        $class = '';
        $style = '';
        $responsive = $params['responsive'] ?? null;
        if ($responsive) {
            $class = $this->CLASS_RESPONSIVE;
            if ($this->_is_bs5() && in_array($responsive, ['sm', 'md', 'lg', 'xl', 'xxl'], true)) {
                $class .= '-' . $responsive;
            }
        }
        $sticky = $params['sticky_header'] ?? null;
        if (is_string($sticky) && preg_match('~^\d+(\.\d+)?(px|em|rem|vh|%)$~D', $sticky)) {
            $style = 'max-height: ' . $sticky . '; overflow-y: auto;';
        }
        if ( ! strlen($class) && ! strlen($style)) {
            return ['', ''];
        }
        return ['<div' . $this->_attrs_add('', $class, $style) . '>' . PHP_EOL, '</div>' . PHP_EOL];
    }
public function _tr_variant_attrs($attrs, $func, $row, $id)

Row attributes with the class of the `tr_variant` option: its callback gets ($row, $id) and returns a contextual colour (success, danger, warning, info, active; under bs4 / bs5 also primary, secondary, light, dark), giving table-success under bs4 and bs5 and success under Bootstrap 3 and empty. An empty or unknown-format answer leaves the row as it is.

  • $attrs string
  • $func callable
  • $row array
    public function _tr_variant_attrs($attrs, $func, $row, $id)
    {
        // a plain string could name a PHP function ('date'), so only closures and [object, method] arrays count
        $variant = (is_callable($func) && ! is_string($func)) ? $func($row, $id) : null;
        if ( ! is_string($variant) || ! preg_match('~^[a-z][a-z0-9_-]*$~D', $variant)) {
            return $attrs;
        }
        $tpl = conf('css_framework') === 'bs4' ? $this->CLASS_TPL_TR_VARIANT_BS4 : $this->CLASS_TPL_TR_VARIANT;
        return $this->_attrs_add($attrs, str_replace('%name', $variant, $tpl));
    }