yf_table2 classTable2 high-level generator, mostly using bootstrap html/css framework.
| File | plugins/table2/classes/yf_table2.class.php |
|---|---|
| Use | _class('table2') |
A site sets them in its config with $PROJECT_CONF['table2']['NAME'] = ...;
| Property | Default | Description |
|---|---|---|
$BS5_CLASSESarray |
|
property => [bs3 default, Bootstrap 5 value]: switched as in yf_html when the table renders under css_framework bs5 |
$BS5_COLORSarray |
['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_SWITCHstring |
'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_GROUPstring |
'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_HTMLstring |
'⋯' |
inner HTML of the overflow menu toggle |
$CLASS_CHANGE_ACTIVE |
'change_active' |
|
$CLASS_CONDENSED |
'table-condensed' |
|
$CLASS_DROPDOWN_ITEMstring |
'' |
class of a button moved into the overflow menu (its btn* classes leave it, btn-danger becomes text-danger) |
$CLASS_DROPDOWN_ITEM_BS4string |
'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_FOOTER_LINKS |
'controls' |
|
$CLASS_HEADER_LINKS |
'controls' |
|
$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_RESPONSIVEstring |
'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_HEADERstring |
'sticky-top' |
thead class of `sticky_header` under bs5 |
$CLASS_STICKY_HEADER_BS3string |
'yf-sticky-header' |
thead class and inline style of `sticky_header` under Bootstrap 3, which has no sticky utility |
$CLASS_STICKY_HEADER_BS3_CSSstring |
'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_VARIANTstring |
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_BS4string |
'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_VALUESbool |
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_TEXTbool |
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_switchedarray |
[] |
properties of BS5_CLASSES switched to their bs5 value |
$_buttons |
[] |
|
$_callbacks |
[] |
|
$_chained_mode |
false |
|
$_data_sql_names |
[] |
|
$_extend |
[] |
|
$_fields |
[] |
|
$_filter_data |
[] |
|
$_filter_params |
[] |
|
$_footer_links |
[] |
|
$_form |
null |
|
$_form_params |
[] |
|
$_header_links |
[] |
|
$_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 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);
}
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);
}
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;
}
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;
}
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;
}
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);
}
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);
}
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);
}
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;
}
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;
}
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);
}
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);
}
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;
}
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;
}
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;
}
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);
}
public function header_link($name, $link, $extra = [])
public function header_link($name, $link, $extra = [])
{
$extra['display_in'] = 'header';
return $this->footer_link($name, $link, $extra);
}
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);
}
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);
}
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);
}
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);
}
public function input_padded($name, $extra = [])
public function input_padded($name, $extra = [])
{
$extra['padding'] = true;
return $this->input($name, $extra);
}
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 link($name, $link = '', $data = '', $extra = [])
public function link($name, $link = '', $data = '', $extra = [])
{
if (is_array($link)) {
$extra = (array) $extra + $link;
$link = '';
}
if ( ! is_array($extra)) {
$extra = [];
}
if ($link) {
$extra['link'] = $link;
}
if ($data) {
$extra['data'] = $data;
}
return $this->text($name, '', $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;
}
public function on_post($func)
public function on_post($func)
{
$this->_on[__FUNCTION__][] = $func;
return $this;
}
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);
}
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;
}
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);
}
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);
}
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;
}
public function td($func, $extra = [])
Register callback for TD element.
public function td($func, $extra = [])
{
$this->_callbacks['td'][] = [$func, $extra];
return $this;
}
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;
}
public function text_padded($name, $extra = [])
public function text_padded($name, $extra = [])
{
$extra['padding'] = true;
return $this->text($name, $extra);
}
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);
}
public function tr($func, $extra = [])
Register callback for TR element.
public function tr($func, $extra = [])
{
$this->_callbacks['tr'][] = [$func, $extra];
return $this;
}
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);
}
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);
}
_ hooks and helpers; not routable as page actionspublic function __call($name, $args)
Catch missing method call.
public function __call($name, $args)
{
return main()->extend_call($this, $name, $args, $return_obj = true);
}
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;
}
}
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> ';
}
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;
}
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;
}
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;
}
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;
}
public function _dropdown_item($html)
A rendered row button as a dropdown menu item: the first a / button tag loses its btn* classes (btn-danger becomes text-danger) and gets CLASS_DROPDOWN_ITEM (CLASS_DROPDOWN_ITEM_BS4 under bs4), so its href, title, data-* and the ajax_* / change_active classes the JS handlers need stay.
$html string public function _dropdown_item($html)
{
$html = trim((string) $html);
if ( ! preg_match('~^<(a|button)\b[^>]*>~i', $html, $tag)) {
if ( ! $this->_is_bs5()) {
return $html;
}
// a block element (the active switch's div) may not sit in a span
$wrap = preg_match('~^<(div|p|ul|ol|dl|table|form|fieldset|h[1-6])\b~i', $html) ? 'div' : 'span';
return '<' . $wrap . ' class="dropdown-item-text">' . $html . '</' . $wrap . '>';
}
$tag_name = $tag[1];
$tag = $tag[0];
$classes = [];
if (preg_match('~\sclass=(["\'])(.*?)\1~s', $tag, $m, PREG_OFFSET_CAPTURE)) {
foreach (preg_split('~\s+~', trim($m[2][0]), -1, PREG_SPLIT_NO_EMPTY) as $class) {
if ($class === 'btn-danger') {
$classes[] = 'text-danger';
} elseif ($class !== 'btn' && strpos($class, 'btn-') !== 0) {
$classes[] = $class;
}
}
$new_tag = substr_replace($tag, '', $m[0][1], strlen($m[0][0]));
} else {
$new_tag = $tag;
}
$item_class = conf('css_framework') === 'bs4' ? $this->CLASS_DROPDOWN_ITEM_BS4 : $this->CLASS_DROPDOWN_ITEM;
$class = trim($item_class . ' ' . implode(' ', $classes));
if (strlen($class)) {
$new_tag = '<' . $tag_name . ' class="' . $class . '"' . substr($new_tag, strlen($tag_name) + 1);
}
return $new_tag . substr($html, strlen($tag));
}
public function _extend($name, $func)
public function _extend($name, $func)
{
$this->_extend[$name] = $func;
}
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 _init()
public function _init()
{
html()->_bs5_switch_classes($this, $this->_is_bs5());
}
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;
}
public function _is_bs5()
Whether the table renders Bootstrap 5 (conf('css_framework')).
public function _is_bs5()
{
return conf('css_framework') === 'bs5';
}
public function _is_link_allowed($link = '')
public function _is_link_allowed($link = '')
{
$link = trim($link);
if ( ! strlen($link)) {
return true;
}
$is_link_allowed = true;
if (MAIN_TYPE_ADMIN && main()->ADMIN_GROUP != 1) {
if (in_array($link, ['', '#', 'javascript:void();'])) {
$is_link_allowed = true;
} else {
$is_link_allowed = _class('admin_methods')->_admin_link_is_allowed($link);
}
}
return $is_link_allowed;
}
public function _process_link_params($link, $row = [], $extra = [])
Example: ->btn('custom', './?object=test&uid=%user_id&pid=%product_id', array('link_params' => 'user_id,product_id'));.
public function _process_link_params($link, $row = [], $extra = [])
{
if ( ! strlen($link) || empty($row) || false === strpos($link, '%')) {
return $link;
}
$params = [];
if (isset($extra['link_params'])) {
foreach (explode(',', $extra['link_params']) as $lp) {
$lp = trim($lp);
if (strlen($lp)) {
$params[$lp] = $lp;
}
}
} else {
$params = array_keys($row);
}
foreach ((array) $params as $lp) {
$what = '%' . $lp;
if (false === strpos($link, $what)) {
continue;
}
$link = str_replace($what, urlencode($row[$lp]), $link);
}
if ($row[$extra['id']]) {
$link = str_replace('%d', urlencode($row[$extra['id']]), $link);
}
return $link;
}
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,
]);
}
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 buttonspublic 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 arraypublic 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;
}
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;
}
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));
}