yf_html class

HTML high-level controls collection.

Fileplugins/html/classes/yf_html.class.php
Use_class('html')

Public properties

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

PropertyDefaultDescription
$AUTO_ASSIGN_IDS true
$B5_ITEM_OPTIONS
bool
false the B5 item keys (list_group() link / active / disabled / variant, navbar() sub, progress_bar() label / striped / animated, a carousel() slide interval) are read from the items; default false renders items as before, so DB rows with such columns change nothing ($PROJECT_CONF['html']['B5_ITEM_OPTIONS'], 4.0 flips). The widget options in $extra need no flag
$BOXES_USE_STPL false
$BS4_AS_BS3
bool
false css_framework bs4 and empty get the Bootstrap 3 markup of accordion(), breadcrumbs(), thumbnails() and pagination(); default false keeps their Bootstrap 2 markup (4.0 flips)
$BS5_BTN_TOKENS
array
['btn-default' => 'btn-outline-secondary', 'btn-mini' => 'btn-sm', 'btn-xs' => 'btn-sm'] Bootstrap 3 button class => Bootstrap 5 class, for the framework HTML _bs5_btn_html() converts (btn-default becomes the neutral colour only without another one)
$BS5_CLASSES
array
['CLASS_LABEL_RADIO' => ['radio', 'form-check'], 'CLASS_LABEL_RADIO_INLINE' => ['radio-inline', 'form-check-inline'], 'CLASS_LABEL_CHECKBOX' => ['checkbox', 'form-check'], 'CLASS_LABEL_CHECKBOX_INLINE' => ['checkbox-inline', 'form-check-inline'], 'CLASS_SELECT_BOX' => ['form-control', 'form-select'], 'CLASS_LINK_BTN' => ['btn btn-default btn-mini btn-xs', 'btn btn-outline-secondary btn-sm']] property => [bs3 default, Bootstrap 5 value]: _init() switches the ones a site did not change when css_framework is bs5
$CLASS_BS5_BTN_NEUTRAL
string
'btn-outline-secondary' the neutral button colour the bs5 class maps put where Bootstrap 3 had btn-default; _bs5_btn_class() drops it next to another colour
$CLASS_BS5_CHECK_INPUT
string
'form-check-input' class of radio / checkbox inputs under Bootstrap 5
$CLASS_INPUT 'form-control'
$CLASS_LABEL_BTN_CHECKBOX 'btn btn-xs btn-primary'
$CLASS_LABEL_BTN_RADIO 'btn btn-xs btn-primary'
$CLASS_LABEL_CHECKBOX 'checkbox'
$CLASS_LABEL_CHECKBOX_INLINE 'checkbox-inline'
$CLASS_LABEL_CHECKBOX_SELECTED 'active'
$CLASS_LABEL_RADIO 'radio'
$CLASS_LABEL_RADIO_INLINE 'radio-inline'
$CLASS_LABEL_RADIO_SELECTED 'active'
$CLASS_SELECT_BOX 'form-control'
$CLASS_SELECT_OPTION_DEFAULT 'opt-default'
$SELECT_BOX_DEF_OPT_TPL '- %select% %name% -'
$_bs5_switched
array
[] properties _init() switched to their bs5 value (only those go back when css_framework changes)
$_chained_mode null
$_country_names null
$_ids []
$_ip_to_country []
$_is_bs3 null
$_is_bs5 null
$_params []

Public methods 110

a

public function a()

Hyperlink container.

    public function a()
    {
        $args = func_get_args();
        $a = [];
        // numerics params
        if (isset($args[0]) && is_array($args[0])) {
            $a = $args[0];
        } elseif (isset($args[0])) {
            $a['href'] = $args[0];
            $a['title'] = $args[1] ?? null;
            $a['icon'] = $args[2] ?? null;
            $a['text'] = $args[3] ?? null;
            $a['class_add'] = $args[4] ?? null;
            $a['target'] = $args[5] ?? null;
            $a['no_text'] = $args[6] ?? null;
            // named params
        } elseif (isset($args['link'])) {
            $a = $args;
        }
        if (isset($args['extra']) && is_array($args['extra'])) {
            foreach ($args['extra'] as $k => $v) {
                $a[$k] = $v;
            }
        }
        if ( ! isset($a['text'])) {
            $a['text'] = ($a['title'] ?? null) ?: ($a['href'] ?? null);
        }
        if (($a['href'] ?? null) && substr($a['href'], 0, strlen('http')) !== 'http' && substr($a['href'], 0, strlen('//')) !== '//') {
            $a['href'] = url($a['href']);
        }
        // conf can change after _init() (demo and test pages); follow it as form2 / table2 do, so they agree
        (conf('css_framework') === 'bs5') !== (bool) $this->_is_bs5 && $this->_init();
        if ( ! isset($a['class'])) {
            $a['class'] = $this->CLASS_LINK_BTN;
        }
        if ($a['class_add'] ?? null) {
            $a['class'] = $this->_is_bs5 ? $this->_bs5_btn_class($a['class'], $a['class_add']) : $a['class'] . ' ' . $a['class_add'];
        }
        if ( ! isset($a['target'])) {
            $a['target'] = '_blank';
        }
        $icon = '';
        if (isset($a['icon'])) {
            $icon = [];
            if ( ! is_array($a['icon'])) {
                $a['icon'] = [$a['icon']];
            }
            foreach ((array) $a['icon'] as $i) {
                $icon[] = '<i class="' . $i . '"></i>';
            }
            $icon = implode(' ', $icon);
        }
        return '<a' . _attrs($a, ['href', 'title', 'class', 'style', 'id', 'rel', 'target', 'disabled']) . '>' . $icon . (($a['no_text'] ?? null) ? '' : (strlen($a['text']) ? ($icon ? ' ' : '') . _prepare_html($a['text']) : '')) . '</a>';
    }

accordion

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

Accordion; bodies are developer-supplied HTML (as modal() bodies). B5 options: flush (bs5), always_open (no data-parent), theme (bs5).

    public function accordion($data = [], $extra = [])
    {
        if ($this->_is_bs5) {
            return $this->_bs5()->accordion($data, $extra);
        }
        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        $parent_attr = ! empty($extra['always_open']) ? '' : ' data-parent="#' . ($extra['id'] ?? null) . '"';
        $items = [];
        $i = 0;
        foreach ((array) $data as $k => $v) {
            if ( ! is_array($v)) {
                $content = $v;
                $v = [];
            } else {
                $content = ($v['body'] ?? null);
            }
            $name = ($v['name'] ?? null) ?: $k;
            $desc = ($v['desc'] ?? null) ?: ucfirst(str_replace('_', ' ', $name));
            $id = ($v['id'] ?? null) ?: 'accordion_item_' . $k;
            if (isset($extra['selected'])) {
                $is_selected = (($extra['selected'] ?? null) == $k);
            } else {
                $is_selected = (++$i == 1);
            }
            $class_group = ($v['class_group'] ?? null) ?: ($extra['class_group'] ?? null);
            $class_head = ($v['class_head'] ?? null) ?: ($extra['class_head'] ?? null);
            $class_body = ($v['class_body'] ?? null) ?: ($extra['class_body'] ?? null);
            $badge = ($v['badge'] ?? null) ? ' <sup class="badge badge-' . (($v['class_badge'] ?? null) ?: 'info') . '">' . ($v['badge'] ?? null) . '</sup>' : '';

            $items[] =
                '<div class="accordion-group panel panel-default' . ($class_group ? ' ' . $class_group : '') . '">
					<div class="accordion-heading panel-heading' . ($class_head ? ' ' . $class_head : '') . '">
						' . ($this->_is_bs3 ? '<h4 class="panel-title">' : '') . '
						<a class="accordion-toggle" data-toggle="collapse"' . $parent_attr . ' href="#' . $id . '">' . $desc . $badge . '</a>
						' . ($this->_is_bs3 ? '</h4>' : '') . '
					</div>
					<div id="' . $id . '" class="accordion-body panel-collapse collapse' . ($is_selected ? ' in' : '') . ($class_body ? ' ' . $class_body : '') . '">
						<div class="accordion-inner panel-body">' . $content . '</div>
					</div>
				</div>';
        }
        return '<div class="panel-group accordion' . (($extra['class'] ?? null) ? ' ' . ($extra['class'] ?? null) : '') . '" id="' . ($extra['id'] ?? null) . '">' . implode(PHP_EOL, (array) $items) . '</div>';
    }

alert

public function alert($data = [], $extra = [])
    public function alert($data = [], $extra = [])
    {
        if ($this->_is_bs5) {
            return $this->_bs5()->alert($data, $extra);
        }
        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        $close_btn = ( ! ($extra['no_close'] ?? null) && ! ($data['no_close'] ?? null)) ? '<button type="button" class="close" data-dismiss="alert">×</button>' : '';
        $head = is_array($data) ? ($data['head'] ?? null) : '';
        $body = is_array($data) ? ($data['body'] ?? null) : $data;
        $alert_type = ($extra['alert'] ?? null) ?: (is_array($data) ? ($data['alert'] ?? null) : '');
        if ( ! $alert_type) {
            $alert_type = 'error';
        }
        return '
			<div class="alert alert-block alert-' . $alert_type . ' fade in' . (($extra['class'] ?? null) ? ' ' . ($extra['class'] ?? null) : '') . '" id="' . ($extra['id'] ?? null) . '">
				' . $close_btn . '
				' . ($head ? '<h4 class="alert-heading">' . $head . '</h4>' : '') . '
				' . $body . '
			</div>';
    }

badge

public function badge($text = '', $extra = [])

Badge: bs5 `badge text-bg-<type>` (+ `rounded-pill`), bs4 `badge badge-<type>` (+ `badge-pill`), Bootstrap 3 `label label-<type>` (also for a pill: Bootstrap 3 badges have no colours, so it keeps the colour and loses the round corners). Options: type, pill, class_add, id, title, style, no_escape, data-*, attr.

    public function badge($text = '', $extra = [])
    {
        if (is_array($text)) {
            $extra = (array) $extra + $text;
            $text = $extra['text'] ?? '';
        }
        $extra = (array) $extra;
        $this->_sync_fw();
        $type = $this->_color_type($extra['type'] ?? '', 'secondary');
        $pill = ! empty($extra['pill']);
        if ($this->_is_bs5) {
            $class = 'badge text-bg-' . $type . ($pill ? ' rounded-pill' : '');
        } elseif ($this->_is_bs4()) {
            $class = 'badge badge-' . $type . ($pill ? ' badge-pill' : '');
        } else {
            $class = 'label label-' . $type;
        }
        $a = [
            'class' => trim($class . ' ' . ($extra['class_add'] ?? '')),
            'id' => $extra['id'] ?? '',
            'title' => $extra['title'] ?? '',
            'style' => $extra['style'] ?? '',
        ] + $this->_b4_pass($extra);
        return '<span' . $this->_b4_attrs($a) . '>' . $this->_b4_text($text, $extra) . '</span>';
    }

btn_active

public function btn_active($url = '', $active = 0, $extra = [])

Active toggler.

    public function btn_active($url = '', $active = 0, $extra = [])
    {
        ! $url && $url = url('/@object/active/@id');
        $pairs = [
            'class' => ['btn-warning', 'btn-success'],
            'icon' => ['fa fa-ban', 'fa fa-check'],
            'title' => [t('Disabled'), t('Active')],
        ];
        $class = (($extra['class'] ?? null) ?: 'change_active') . (($extra['class_add'] ?? null) ? ' ' . $extra['class_add'] : '');
        return ($extra['disabled'] ?? null) ? $pairs['class'][$active] : $this->a(
            $url,
            $pairs['title'][$active],
            $pairs['icon'][$active],
            '',
            $class . ' active_short ' . $pairs['class'][$active]
        );
    }

button

public function button($text = '', $extra = [])

Button, or a link styled as one with `link` (a trusted URL; javascript: / data: / vbscript: become #). Options: type (primary, success... `link`), outline, size (sm / lg), icon (class of an <i>), link, target, rel, button_type (submit / reset), name, value, active, disabled, class (replaces the btn classes), class_add, id, title, style, no_escape, data-*, attr. Bootstrap 4: the Bootstrap 5 classes; Bootstrap 3: btn-default for outline and the neutral button, btn-xs for sm.

    public function button($text = '', $extra = [])
    {
        if (is_array($text)) {
            $extra = (array) $extra + $text;
            $text = $extra['text'] ?? '';
        }
        $extra = (array) $extra;
        $this->_sync_fw();
        $text = $this->_b4_text($text, $extra);
        $icon = ($extra['icon'] ?? null) ? '<i class="' . _htmlchars(_escape_tpl_braces((string) $extra['icon'])) . '" aria-hidden="true"></i>' : '';
        $is_link = strlen((string) ($extra['link'] ?? '')) > 0;
        $active = ! empty($extra['active']);
        $disabled = ! empty($extra['disabled']);
        $class = ($extra['class'] ?? null) ?: $this->_btn_class($extra);
        $a = [
            'class' => trim($class . ($active ? ' active' : '') . ($disabled && $is_link ? ' disabled' : '') . ' ' . ($extra['class_add'] ?? '')),
            'id' => $extra['id'] ?? '',
            'title' => $extra['title'] ?? '',
            'style' => $extra['style'] ?? '',
            'aria-label' => ! strlen($text) ? ($extra['title'] ?? '') : '',
            'aria-pressed' => $active ? 'true' : '',
        ];
        if ($is_link) {
            $a = ['href' => $this->_safe_href($extra['link'])] + $a + [
                'role' => 'button',
                'target' => $extra['target'] ?? '',
                'rel' => $extra['rel'] ?? '',
                'aria-disabled' => $disabled ? 'true' : '',
                'tabindex' => $disabled ? '-1' : '',
            ];
        } else {
            $a = ['type' => in_array($extra['button_type'] ?? '', ['submit', 'reset'], true) ? $extra['button_type'] : 'button'] + $a + [
                'name' => $extra['name'] ?? '',
                'value' => $extra['value'] ?? '',
                'disabled' => $disabled ? 'disabled' : '',
            ];
        }
        $tag = $is_link ? 'a' : 'button';
        return '<' . $tag . $this->_b4_attrs($a + $this->_b4_pass($extra)) . '>' . $icon . ($icon && strlen($text) ? ' ' : '') . $text . '</' . $tag . '>';
    }
public function button_allow_deny_box($name, $values = [], $selected = null)
    public function button_allow_deny_box($name, $values = [], $selected = null)
    {
        $def_cls = $this->CLASS_LABEL_BTN_RADIO;
        $values = [
            ['class' => $def_cls . ' btn-warning', 'html' => '<i class="fa fa-ban"></i> ' . t('Deny')],
            ['class' => $def_cls . ' btn-success', 'html' => '<i class="fa fa-check"></i> ' . t('Allow')],
        ];
        return $this->button_radio_box($name, $values, $selected);
    }

button_box

public function button_box($name, $values = [], $selected = '', $extra = [])
    public function button_box($name, $values = [], $selected = '', $extra = [])
    {
        if ($this->_is_bs5) {
            return $this->_bs5()->button_box($name, $values, $selected, $extra);
        }
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = ($extra['name'] ?? null);
        }
        $desc = isset($extra['desc']) ? ($extra['desc'] ?? null) : ucfirst(str_replace('_', '', $name));
        $values = isset($extra['values']) ? ($extra['values'] ?? null) : (array) $values; // Required
        $translate = isset($extra['translate']) ? ($extra['translate'] ?? null) : 0;
        if (($extra['no_translate'] ?? null)) {
            $translate = 0;
        }
        $selected = isset($extra['selected']) ? ($extra['selected'] ?? null) : $selected;
        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        if ( ! $values) {
            return false;
        }
        $selected = (string) $selected;

        $items = [];
        $selected_val = '';
        foreach ((array) $values as $key => $cur_value) {
            $_what_compare = $key;
            $is_selected = $_what_compare == $selected;
            $val = $translate ? t($cur_value) : $cur_value;
            $items[] = '<li class="dropdown' . ($is_selected ? ' active' : '') . '"><a data-value="' . $key . '"' . ($is_selected ? ' data-selected="selected"' : '') . '>' . $val . '</a></li>';
            if ($is_selected) {
                $selected_val = $val;
            }
        }
        $class = ($extra['class'] ?? null) ?: 'btn dropdown-toggle';
        ($extra['class_add'] ?? null) && $class .= ' ' . ($extra['class_add'] ?? null);
        $text = $selected_val ?: $desc;

        $body = [];
        $body[] = '<div class="btn-group" id="' . ($extra['id'] ?? null) . '">';
        if (($extra['button_split'] ?? null)) {
            $body[] = '<button class="btn">' . $text . '</button>';
            $body[] = '<button class="' . $class . '" data-toggle="dropdown"><span class="caret"></span></button>';
        } else {
            $body[] = '<button class="' . $class . '" data-toggle="dropdown">' . $text . ' <span class="caret"></span></button>';
        }
        $body[] = '<ul class="dropdown-menu">';
        foreach ($items as $v) {
            $body[] = $v;
        }
        $body[] = '</ul>';
        $body[] = '</div>';
        return implode(PHP_EOL, $body);
    }
public function button_check_box($name, $values = [], $selected = '')
    public function button_check_box($name, $values = [], $selected = '')
    {
        if ($this->_is_bs5) {
            return $this->_bs5()->button_check_box($name, $values, $selected);
        }
        $extra = [];
        if (is_array($name)) {
            $extra = $name;
            $name = ($extra['name'] ?? null);
        }
        ! is_array($extra) && $extra = [];
        $label_extra = ($extra['label_extra'] ?? null);
        $extra = [
            'name' => $name,
            // the array form passes values / selected inside $name: the [] / '' defaults must not hide them
            'values' => $values ?: ($extra['values'] ?? []),
            'selected' => $selected !== '' ? $selected : ($extra['selected'] ?? ''),
            'use_stpl' => false,
            'label_extra' => [
                'class' => (($label_extra['class'] ?? null) ?: $this->CLASS_LABEL_BTN_CHECKBOX),
            ],
        ] + $extra;
        $label_right = ($extra['label_right'] ?? null) ? '<label class="text"> <small>' . ($extra['desc'] ?? null) . '</small></label>' : '';
        return '<div class="btn-group" data-toggle="buttons">' . $this->check_box($extra) . $label_right . '</div>';
    }
public function button_radio_box($name, $values = [], $selected = null, $extra = [])
    public function button_radio_box($name, $values = [], $selected = null, $extra = [])
    {
        if ($this->_is_bs5) {
            return $this->_bs5()->button_radio_box($name, $values, $selected, $extra);
        }
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = ($extra['name'] ?? null);
        }
        ! is_array($extra) && $extra = [];
        $label_extra = ($extra['label_extra'] ?? null);
        $extra = [
            'name' => $name ?: ($extra['name'] ?? null),
            // the array form passes values / selected inside $name: the [] / null defaults must not hide them
            'values' => $values ?: ($extra['values'] ?? []),
            'selected' => isset($selected) ? $selected : ($extra['selected'] ?? null),
            'use_stpl' => false,
            'label_extra' => [
                'class' => (($label_extra['class'] ?? null) ?: $this->CLASS_LABEL_BTN_RADIO) . (($extra['horizontal'] ?? null) ? ' ' . $this->CLASS_LABEL_RADIO_INLINE : ''),
            ],
        ] + $extra;
        $label_right = ($extra['label_right'] ?? null) ? '<label class="text"> <small>' . ($extra['desc'] ?? null) . '</small></label>' : '';
        return '<div class="btn-group" data-toggle="buttons">' . $this->radio_box($extra) . $label_right . '</div>';
    }
public function button_split_box($name, $values = [], $selected = '', $extra = [])
    public function button_split_box($name, $values = [], $selected = '', $extra = [])
    {
        if ($this->_is_bs5) {
            return $this->_bs5()->button_split_box($name, $values, $selected, $extra);
        }
        $extra['button_split'] = true;
        return $this->button_box($name, $values, $selected, $extra);
    }
public function button_yes_no_box($name, $values = [], $selected = null)
    public function button_yes_no_box($name, $values = [], $selected = null)
    {
        $def_cls = $this->CLASS_LABEL_BTN_RADIO;
        $values = [
            ['class' => $def_cls . ' btn-warning', 'html' => '<i class="fa fa-ban"></i> ' . t('No')],
            ['class' => $def_cls . ' btn-success', 'html' => '<i class="fa fa-check"></i> ' . t('Yes')],
        ];
        return $this->button_radio_box($name, $values, $selected);
    }

card

public function card($extra = [])

panel() under its Bootstrap 5 name: a card under bs5, a panel under Bootstrap 3 / 4.

    public function card($extra = [])
    {
        return $this->panel($extra);
    }
public function chained_wrapper($params = [])

Wrapper for template engine Example: return html()->dd_table(db()->get_2d('SELECT * FROM '.db('countries')));.

    public function chained_wrapper($params = [])
    {
        $this->_chained_mode = true;
        $this->_params = $params;
        return $this;
    }

check_box

public function check_box($name = '', $value = '', $selected = '', $add_str = '', $extra = [])

Simple check box.

    public function check_box($name = '', $value = '', $selected = '', $add_str = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = ($extra['name'] ?? null);
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['name'] = strlen($name ?? '') ? $name : (strlen($extra['name'] ?? '') ? $extra['name'] : 'checkbox');
        $extra['value'] = strlen($value ?? '') ? $value : (strlen($extra['value'] ?? '') ? $extra['value'] : '1');
        $selected = isset($extra['selected']) ? $extra['selected'] : $selected;
        if (isset($extra['checked'])) {
            $selected = $extra['checked'];
        }
        $extra['checked'] = $selected ? 'checked' : null;
        $extra['id'] = (($extra['force_id'] ?? null) ?: ($extra['id'] ?? null)) ?: ($this->AUTO_ASSIGN_IDS ? __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1) : null);
        $extra['desc'] = isset($extra['desc']) ? $extra['desc'] : ucfirst(str_replace('_', '', $extra['name']));
        if ($extra['no_desc'] ?? null) {
            $extra['desc'] = '';
        }
        $extra['class'] = trim(($extra['class'] ?? null) . ' ' . ($extra['class_add'] ?? null));
        if ($this->_is_bs5 && ! in_array($this->CLASS_BS5_CHECK_INPUT, preg_split('~\s+~', $extra['class']), true)) {
            $extra['class'] = trim($extra['class'] . ' ' . $this->CLASS_BS5_CHECK_INPUT);
        }
        $add_str = ($extra['add_str'] ?? null) ? $extra['add_str'] : $add_str;
        $translate = $extra['translate'] ?? false;

        $label_extra = ($extra['label_extra'] ?? null);
        $def_label_class = $this->CLASS_LABEL_CHECKBOX . ' ' . $this->CLASS_LABEL_CHECKBOX_INLINE;
        $label_extra['class'] = isset($label_extra['class']) ? $label_extra['class'] : (isset($extra['class_label_checkbox']) ? $extra['class_label_checkbox'] : $def_label_class);
        if ($extra['class_add_label_checkbox'] ?? null) {
            $label_extra['class'] .= ' ' . $extra['class_add_label_checkbox'];
        }
        if ($selected) {
            $label_extra['class'] .= ' ' . $this->CLASS_LABEL_CHECKBOX_SELECTED;
        }
        $body = [];
        if ($extra['outer_label'] ?? null) {
            $body[] = '<label class="outer-label">' . $extra['outer_label'] . '</label>';
        }
        $extra['type'] = 'checkbox';
        if (($extra['switch'] ?? null) && $this->_is_bs5) {
            $body[] = $this->_bs5()->switch_box($extra, $add_str, $translate);
            return implode(PHP_EOL, $body);
        }
        $body[] = '<label' . _attrs($label_extra, ['id', 'class', 'style']) . '>'
            . '<input' . _attrs($extra, ['type', 'name', 'id', 'value', 'checked', 'class', 'style', 'disabled', 'required']) . ($add_str ? ' ' . $add_str : '')
            . '>  <span>' . ($translate ? t($extra['desc']) : $extra['desc']) . '</span>' // Please do not remove whitespace before   :)
            . '</label>';
        return implode(PHP_EOL, $body);
    }

chosen_box

public function chosen_box($name, $values = [], $selected = '', $extra = [])
    public function chosen_box($name, $values = [], $selected = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
        } else {
            $extra['name'] = $name;
        }
        $extra['force_id'] = $extra['force_id'] ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);

        asset('chosen');

        $js_options = (array) $extra['js_options'] + [
            'disable_search_threshold' => 10,
            'no_results_text' => t('No results match'),
            'placeholder_text_multiple' => t('Select Some Options'),
            'placeholder_text_single' => t('Select an Option'),
            'search_contains' => true,
            // put default js options here
        ];
        jquery('$("#' . addslashes($extra['force_id']) . '").chosen(' . json_encode($js_options, JSON_PRETTY_PRINT) . ');');

        $func = $extra['multiple'] ? 'multi_select' : 'select_box';
        return $this->$func($extra, $values, $selected);
    }

collapse

public function collapse($body = '', $extra = [])

A block shown / hidden by a button (Bootstrap collapse). $body is developer-supplied HTML, not escaped (also run request data in it through _escape_tpl_braces(): the page template parses the output again). Options: button (the toggle text, default "More"; false for no button, e.g. a toggle made with button()), button_extra (button() options), show, id, class_add. Bootstrap 3: the same with data-toggle and `in` (Bootstrap 4: `show`).

    public function collapse($body = '', $extra = [])
    {
        $extra = (array) $extra;
        $this->_sync_fw();
        $id = fix_html_attr_id((string) (($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1)));
        $show = ! empty($extra['show']);
        $button = $extra['button'] ?? t('More');
        $out = '';
        if ($button !== false && strlen((string) $button)) {
            $b = (array) ($extra['button_extra'] ?? []);
            $b += $this->_is_bs5 ? ['data-bs-toggle' => 'collapse', 'data-bs-target' => '#' . $id] : ['data-toggle' => 'collapse', 'data-target' => '#' . $id];
            $b['attr'] = (array) ($b['attr'] ?? []) + ['aria-expanded' => $show ? 'true' : 'false', 'aria-controls' => $id];
            $out = $this->button($button, $b);
        }
        $class = 'collapse' . ($show ? (($this->_is_bs5 || $this->_is_bs4()) ? ' show' : ' in') : '') . ' ' . ($extra['class_add'] ?? '');
        return $out . '<div' . $this->_b4_attrs(['class' => trim($class), 'id' => $id]) . '>' . $body . '</div>';
    }

country

public function country()

Country container.

    public function country()
    {
        asset('bfh-select');

        $args = func_get_args();
        $a = [];
        // numerics params
        if (isset($args[0]) && is_array($args[0])) {
            $a = $args[0];
        } elseif (isset($args[0])) {
            $a['code'] = $args[0];
            $a['link'] = $args[1];
            $a['class'] = $args[2];
        }
        if (isset($args['extra']) && is_array($args['extra'])) {
            foreach ($args['extra'] as $k => $v) {
                $a[$k] = $v;
            }
        }
        $code = strtoupper($a['code'] ?? '');
        if ( ! $code) {
            return false;
        }
        $name = _prepare_html($this->_get_country_name($code));
        if ($a['link']) {
            return a(str_replace('%code', $code, $a['link']), $code . ' | ' . $name, 'bfh-flag-' . $code, '', $a['class']);
        }
        return $this->icon('bfh-flag-' . $code);
    }

date_box

public function date_box($selected = '', $years = '', $name_postfix = '', $add_str = '', $show_what = 'ymd', $show_text = 1, $translate = 1)
    public function date_box($selected = '', $years = '', $name_postfix = '', $add_str = '', $show_what = 'ymd', $show_text = 1, $translate = 1)
    {
        return _class('html_datetime', 'classes/html/')->date_box($selected, $years, $name_postfix, $add_str, $show_what, $show_text, $translate);
    }

date_box2

public function date_box2($name, $selected = '', $years = '', $add_str = '', $show_what = 'ymd', $show_text = 1, $translate = 1)
    public function date_box2($name, $selected = '', $years = '', $add_str = '', $show_what = 'ymd', $show_text = 1, $translate = 1)
    {
        return _class('html_datetime', 'classes/html/')->date_box2($name, $selected, $years, $add_str, $show_what, $show_text, $translate);
    }

date_picker

public function date_picker($name, $cur_date = '', $extra = [])
    public function date_picker($name, $cur_date = '', $extra = [])
    {
        js('jquery-ui');
        css('jquery-ui');

        jquery('$(".datepicker").datepicker({ dateFormat: "yy-mm-dd" });');
        css('//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.4/css/jquery.ui.datepicker.min.css');

        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        // TODO: use input() unified control
        return '<input type="text" name="' . $name . '" class="datepicker" value="' . $cur_date . '" style="width:80px" readonly="true" id="' . $extra['id'] . '" />';
    }

datetime_box2

public function datetime_box2($name, $selected = '', $years = '', $add_str = '', $show_what = 'ymd', $show_text = 1, $translate = 1)
    public function datetime_box2($name, $selected = '', $years = '', $add_str = '', $show_what = 'ymd', $show_text = 1, $translate = 1)
    {
        return _class('html_datetime', 'classes/html/')->datetime_box2($name, $selected, $years, $add_str, $show_what, $show_text, $translate);
    }

dd_table

public function dd_table($replace = [], $field_types = [], $extra = [])
    public function dd_table($replace = [], $field_types = [], $extra = [])
    {
        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        $ts = null;
        if (DEBUG_MODE) {
            $ts = microtime(true);
        }
        $form = form($replace, [
            'legend' => $replace['title'],
            'no_form' => 1,
            'dd_mode' => 1,
            'dd_class' => 'span6 col-md-6',
        ]);
        foreach ((array) $replace as $name => $val) {
            $func = 'container';
            $_extra = [
                'desc' => $name,
                'value' => $val,
            ];
            $ft = $field_types[$name];
            if (isset($ft)) {
                if (is_array($ft)) {
                    if (isset($ft['func'])) {
                        $func = $ft['func'];
                    }
                    $_extra = (array) $ft + $_extra;
                } else {
                    $func = $ft;
                }
            }
            $_extra += (array) $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']($val, $_extra);
                if ( ! $_display_allowed) {
                    continue;
                }
            }
            if ($func) {
                $form->$func($val, $_extra);
            }
        }
        $legend = $extra['legend'] ? '<legend>' . _prepare_html(t($extra['legend'])) . '</legend>' : '';
        $div_class = $extra['div_class'] ? $extra['div_class'] : 'span6 col-md-6';
        if (DEBUG_MODE) {
            debug('dd_table[]', [
                'fields' => $replace,
                'field_types' => $field_types,
                'extra' => $extra,
                'time' => round(microtime(true) - $ts, 5),
                'trace' => main()->trace_string(),
            ]);
        }
        return '<div class="row-fluid" id="' . $extra['id'] . '">' . $legend . '<div class="' . $div_class . '">' . $form . '</div></div>';
    }

div_box

public function div_box($name, $values = [], $selected = '', $extra = [])
    public function div_box($name, $values = [], $selected = '', $extra = [])
    {
        if ($this->_is_bs5) {
            return $this->_bs5()->div_box($name, $values, $selected, $extra);
        }
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = ($extra['name'] ?? null);
        }
        $desc = ($extra['desc'] ?? null) ? ($extra['desc'] ?? null) : ucfirst(str_replace('_', '', $name));
        $values = isset($extra['values']) ? ($extra['values'] ?? null) : (array) $values; // Required
        $translate = isset($extra['translate']) ? ($extra['translate'] ?? null) : 0;
        if ($extra['no_translate'] ?? null) {
            $translate = 0;
        }
        $selected = ($extra['selected'] ?? null) ?: $selected;
        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        if ( ! $values) {
            return false;
        }
        $selected = (string) $selected;

        $items = [];
        $selected_val = '';
        foreach ((array) $values as $key => $cur_value) {
            $_what_compare = $key;
            $is_selected = $_what_compare == $selected;
            $val = ($translate ? t($cur_value) : $cur_value);
            $items[] = '<li class="dropdown' . ($is_selected ? ' active' : '') . '"><a data-value="' . $key . '"' . ($is_selected ? ' data-selected="selected"' : '') . '>' . $val . '</a></li>';
            if ($is_selected) {
                $selected_val = $val;
            }
        }
        $body = [];
        $body[] = '<li class="dropdown" style="list-style-type:none;" id="' . ($extra['id'] ?? null) . '">';
        $body[] = '<a class="dropdown-toggle" data-toggle="dropdown">' . ($selected_val ?: $desc) . ' <span class="caret"></span></a>';
        $body[] = '<ul class="dropdown-menu">';
        foreach ($items as $v) {
            $body[] = $v;
        }
        $body[] = '</ul>';
        $body[] = '</li>';
        return implode(PHP_EOL, $body);
    }

grid

public function grid($data = [], $extra = [])
    public function grid($data = [], $extra = [])
    {
        if ($this->_is_bs5) {
            return $this->_bs5()->grid($data, $extra);
        }
        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        $rows = [];
        $ul_opened = false;
        foreach ((array) $data as $id => $row) {
            $items = [];
            $row_col = count((array) $row) ? floor(12 / count((array) $row)) : 1;
            if ($row_col < 1) {
                $row_col = 1;
            }
            foreach ((array) $row as $rid => $item) {
                // a plain string cell is the body, not an array of characters
                $item = is_array($item) ? $item : [$item];
                $body = ($item[0] ?? null) ?: ($item['body'] ?? null);
                $col = ($item[1] ?? null) ?: ($item['col'] ?? null);
                $class = ($item['class'] ?? null);
                if ( ! $col) {
                    $col = $row_col;
                }
                $items[] = '<div class="span' . $col . ' col-md-' . $col . ($class ? ' ' . $class : '') . '">' . $body . '</div>';
            }
            $rows[] = '<div class="row-fluid show-grid">' . implode(PHP_EOL, $items) . '</div>';
        }
        return '<div class="grid' . (($extra['class'] ?? null) ? ' ' . ($extra['class'] ?? null) : '') . '" id="' . ($extra['id'] ?? null) . '">' . implode(PHP_EOL, (array) $rows) . '</div>';
    }

icon

public function icon()

Icon container.

    public function icon()
    {
        $args = func_get_args();
        $a = [];
        // numerics params
        if (isset($args[0]) && is_array($args[0])) {
            $a = $a[0];
        } elseif (isset($args[0])) {
            $a['icon'] = $args[0];
            $a['title'] = $args[1];
            $a['text'] = $args[2];
            $a['class'] = $args[3];
            // named params
        } elseif (isset($args['icon'])) {
            $a = $args;
        }
        if (isset($args['extra']) && is_array($args['extra'])) {
            foreach ($args['extra'] as $k => $v) {
                $a[$k] = $v;
            }
        }
        if ( ! isset($a['text'])) {
            $a['text'] = $a['title'];
        }
        if ( ! isset($a['style'])) {
            $a['style'] = 'padding-right:5px';
        }
        return '<span style="' . $a['style'] . '" title="' . _prepare_html($a['title']) . '"><i class="' . $a['icon'] . '"></i>' . (strlen($a['text'] ?? '') ? ' ' . _prepare_html($a['text']) : '') . '</span>';
    }
public function image_select_box($name, $values = [], $selected = '', $extra = [])
    public function image_select_box($name, $values = [], $selected = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
        } else {
            $extra['name'] = $name;
        }
        $extra['force_id'] = $extra['force_id'] ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);

        asset('jquery-image-picker');
        asset('jquery-imagesloaded');
        asset('masonry');
        $extra['class_add'] = trim($extra['class_add'] . ' image-picker masonry');

        $js_options = (array) $extra['js_options'] + [
            // put default js options here
        ];
        jquery('
			var select_box = $("#' . addslashes($extra['force_id']) . '")
			select_box.imagepicker(' . json_encode($js_options, JSON_PRETTY_PRINT) . ');

			var container = select_box.next("ul.thumbnails");
			container.imagesLoaded(function(){
				container.masonry({
					itemSelector: "li",
				});
			});
		');

        $extra['option_callback'] = function ($key, $cur_value, $is_selected, $text, $extra) {
            return '<option value="' . $key . '"' . ($is_selected ? ' selected="selected"' : '') . ' data-img-src="' . $cur_value . '">' . $text . '</option>';
        };
        $func = $extra['multiple'] ? 'multi_select' : 'select_box';
        return $this->$func($extra, $values, $selected);
    }

input

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

Simple input form control.

    public function input($name = '', $value = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = $extra['name'];
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['name'] = ($extra['name'] ?? null) ?: ($name ?: 'text');
        $extra['value'] = _escape_tpl_braces($extra['value'] ?: $value);
        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        $extra['desc'] = ($extra['desc'] ?? null) ?: ucfirst(str_replace('_', '', $extra['name']));
        $extra['type'] = ($extra['type'] ?? null) ?: 'text';
        $extra['placeholder'] = _escape_tpl_braces($extra['placeholder'] ? t($extra['placeholder']) : $extra['desc']);
        $extra['title'] = _escape_tpl_braces($extra['title'] ?? null);
        $extra['class'] = isset($extra['class']) ? $extra['class'] : $this->CLASS_INPUT;
        if ($extra['class_add']) {
            $extra['class'] = trim($extra['class'] . ' ' . $extra['class_add']);
        }
        $attrs_names = ['name', 'type', 'id', 'class', 'style', 'placeholder', 'value', 'data', 'size', 'maxlength', 'pattern', 'disabled', 'required', 'autocomplete', 'accept', 'target', 'autofocus', 'title', 'min', 'max', 'step', 'readonly'];
        return '<input' . _attrs($extra, $attrs_names) . '>';
    }

ip

public function ip()

IP container.

    public function ip()
    {
        asset('bfh-select');

        $args = func_get_args();
        $a = [];
        // numerics params
        if (isset($args[0]) && is_array($args[0])) {
            $a = $args[0];
        } elseif (isset($args[0])) {
            $a['ip'] = $args[0];
        }
        if (isset($args['extra']) && is_array($args['extra'])) {
            foreach ($args['extra'] as $k => $v) {
                $a[$k] = $v;
            }
        }
        $ip = $a['ip'];
        $code = strtoupper($this->_get_ip_country($ip));
        $name = _prepare_html($this->_get_country_name($code));
        return a('http://www.infobyip.com/ip-' . urlencode($ip) . '.html', $ip . ' | ' . $code . ' | ' . $name, ($code ? 'bfh-flag-' . $code : ''), $ip, $a['class']);
    }

jumbotron

public function jumbotron($data = [], $extra = [])
    public function jumbotron($data = [], $extra = [])
    {
        if ($this->_is_bs5) {
            return $this->_bs5()->jumbotron($data, $extra);
        }
        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        return '<div class="jumbotron' . (($extra['class'] ?? null) ? ' ' . ($extra['class'] ?? null) : '') . '" id="' . ($extra['id'] ?? null) . '"><h1>' . ($data['head'] ?? null) . '</h1>' . ($data['body'] ?? null) . '</div>';
    }

li

public function li($data = [], $extra = [])
    public function li($data = [], $extra = [])
    {
        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        $items = [];
        foreach ((array) $data as $v) {
            if ( ! is_array($v)) {
                $body = $v;
                $v = [];
            } else {
                $body = ($v['body'] ?? null) ?: ($v['name'] ?? null);
            }
            if ( ! strlen((string) $body)) {
                continue;
            }
            $class_item = ($v['class'] ?? null) ?: ($extra['class_item'] ?? null);
            $badge = ($v['badge'] ?? null) ? ' <sup class="badge badge-' . (($v['class_badge'] ?? null) ?: 'info') . '">' . ($v['badge'] ?? null) . '</sup>' : '';
            $items[] = '<li class="' . ($class_item ? ' ' . $class_item : '') . '"' . (($v['id'] ?? null) ? ' id="' . ($v['id'] ?? null) . '"' : '') . '>' . $badge . (($v['link'] ?? null) ? '<a href="' . ($v['link'] ?? null) . '">' . $body . '</a>' : $body) . '</li>';
            if (is_array(($v['sub'] ?? null))) {
                $items[] = $this->li(($v['sub'] ?? null), $extra);
                continue;
            }
        }
        return '<ul' . _attrs($extra, ['id', 'class', 'style']) . '>' . implode(PHP_EOL, (array) $items) . '</ul>';
    }

li_tree

public function li_tree($data = [], $extra = [])
    public function li_tree($data = [], $extra = [])
    {
        return _class('html_tree', 'classes/html/')->li_tree($data, $extra);
    }

list_box

public function list_box($name, $values = [], $selected = '', $extra = [])
    public function list_box($name, $values = [], $selected = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = $extra['name'];
        }
        $desc = isset($extra['desc']) ? $extra['desc'] : ucfirst(str_replace('_', '', $name));
        $values = isset($extra['values']) ? $extra['values'] : (array) $values; // Required
        $translate = isset($extra['translate']) ? $extra['translate'] : 0;
        if ($extra['no_translate'] ?? null) {
            $translate = 0;
        }
        $selected = isset($extra['selected']) ? $extra['selected'] : $selected;
        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        if ( ! $values) {
            return false;
        }
        asset('bfh-select');

        $extra['class'] = 'bfh-selectbox ' . ($extra['class_add'] ?? null);
        $extra['data-name'] = $name;
        $extra['data-value'] = _escape_tpl_braces((string) $selected);
        $extra['data-filter'] = isset($extra['data-filter']) ? $extra['data-filter'] : (count((array) $values) > 10 ? 'true' : '');

        $body = '<div' . _attrs($extra, ['id', 'class', 'style']) . '>';
        foreach ((array) $values as $key => $cur_value) {
            $body .= '<div data-value="' . $key . '">' . ($translate ? t($cur_value) : $cur_value) . '</div>' . PHP_EOL;
        }
        $body .= '</div>';

        return $body;
    }

list_group

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

List group; item bodies are developer-supplied HTML (as modal() bodies). B5 options: numbered, flush (bs4 / bs5), theme (bs5), variant; item link / active / disabled / variant with B5_ITEM_OPTIONS (_list_group_item_state()).

    public function list_group($data = [], $extra = [])
    {
        if ($this->_is_bs5) {
            return $this->_bs5()->list_group($data, $extra);
        }
        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        $has_links = $this->_list_group_has_links($data);
        $bs4 = $this->_is_bs4();
        $items = [];
        foreach ((array) $data as $v) {
            if ( ! is_array($v)) {
                $body = $v;
                $v = [];
            } else {
                $body = ($v['body'] ?? null);
            }
            $type = ($v['type'] ?? null) ?: ($extra['type'] ?? null);
            $class_item = ($v['class_item'] ?? null) ?: ($extra['class_item'] ?? null);
            $state = $this->_list_group_item_state($v, $extra, $bs4 ? 'bs4' : 'bs3');
            if ( ! $has_links && $state === null) {
                $items[] = '<li class="list-group-item' . ($class_item ? ' ' . $class_item : '') . '"><span class="badge">' . ($v['badge'] ?? null) . '</span> ' . $body . '</li>';
                continue;
            }
            $state ??= ['link' => null, 'class' => '', 'attrs' => []];
            $tag = $has_links ? ($state['link'] !== null ? 'a' : 'div') : 'li';
            $a = ['href' => $state['link'] ?? '', 'class' => trim('list-group-item' . $state['class'] . ' ' . $class_item)] + $state['attrs'];
            $items[] = '<' . $tag . $this->_b4_attrs($a) . '><span class="badge">' . ($v['badge'] ?? null) . '</span> ' . $body . '</' . $tag . '>';
        }
        $tag = $has_links ? 'div' : ( ! empty($extra['numbered']) ? 'ol' : 'ul');
        $class = ($bs4 && ! empty($extra['flush']) ? ' list-group-flush' : '') . (($extra['class'] ?? null) ? ' ' . ($extra['class'] ?? null) : '');
        return '<' . $tag . ' class="list-group' . $class . '" id="' . ($extra['id'] ?? null) . '">' . implode(PHP_EOL, (array) $items) . '</' . $tag . '>';
    }

media_objects

public function media_objects($data = [], $extra = [])
    public function media_objects($data = [], $extra = [])
    {
        if ($this->_is_bs5) {
            return $this->_bs5()->media_objects($data, $extra);
        }
        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        if ($data) {
            $data = $this->_recursive_sort_items($data);
        }
        $img_class = (($extra['img_class'] ?? null) ?: 'media-object') . (($extra['img_class_add'] ?? null) ? ' ' . ($extra['img_class_add'] ?? null) : '');
        $keys = array_keys($data);
        $keys_counter = array_flip($keys);
        $items = [];
        foreach ((array) $data as $id => $item) {
            $next_id = ($keys[$keys_counter[$id] + 1] ?? null);
            $next_item = $next_id ? ($data[$next_id] ?? null) : [];
            $close_num_levels = 1;
            if ($next_item) {
                $close_num_levels = ($item['level'] ?? null) - ($next_item['level'] ?? null) + 1;
                if ($close_num_levels < 0) {
                    $close_num_levels = 0;
                }
            } elseif (($item['level'] ?? null) > 0) {
                $close_num_levels = ($item['level'] ?? null) + 1;
            }
            $items[] = '
				<div class="media">
					<a class="pull-left"' . (($item['link'] ?? null) ? ' href="' . ($item['link'] ?? null) . '"' : '') . '>'
                . '<img class="' . $img_class . '" alt="' . ($item['alt'] ?? null) . '" src="' . ($item['img'] ?? null) . '"' . (($item['img_width'] ?? null) ? ' width="' . ($item['img_width'] ?? null) . '"' : '') . (($item['img_height'] ?? null) ? ' height="' . ($item['img_height'] ?? null) . '"' : '') . '></a>
					<div class="media-body">
						<h4 class="media-heading">'
                . (($item['link'] ?? null) ? '<a href="' . ($item['link'] ?? null) . '">' : '') . ($item['head'] ?? null) . (($item['link'] ?? null) ? '</a>' : '')
                . (($item['date'] ?? null) ? ' <small class="pull-right">' . _format_date(($item['date'] ?? null), ($extra['date_format'] ?? null) ?: 'full') . '</small>' : '')
                . '</h4>'
                . ($item['body'] ?? null) . '
			';
            if ($close_num_levels) {
                $items[] = str_repeat(PHP_EOL . '</div></div>' . PHP_EOL, $close_num_levels);
            }
        }
        return '<div class="media-objects' . (($extra['class'] ?? null) ? ' ' . ($extra['class'] ?? null) : '') . '" id="' . ($extra['id'] ?? null) . '">' . implode(PHP_EOL, (array) $items) . '</div>';
    }

modal

public function modal($extra = [])

Modal dialog: id, header, body, footer (developer-supplied HTML, not escaped: escape request data in them and run it through _escape_tpl_braces(), the page template parses the output again), show_close, inline, style. B5 options: size (sm, lg, xl), fullscreen (true or a breakpoint), centered, scrollable, static, fade, theme (bs5); open it with modal_button().

    public function modal($extra = [])
    {
        if ($this->_is_bs5) {
            return $this->_bs5()->modal($extra);
        }
        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        $def_style = ($extra['inline'] ?? null) ? 'position: relative; top: auto; left: auto; right: auto; bottom: auto; margin: 0 auto 20px; z-index: 1; max-width: 100%; display: block; overflow-y: auto;' : '';
        $extra['style'] = ($extra['style'] ?? null) ?: $def_style;
        [$modal_add, $dialog_add, $modal_attrs] = $this->_modal_options_bs3($extra);
        return '
			<div class="modal' . $modal_add . '" style="' . ($extra['style'] ?? null) . '" id="' . ($extra['id'] ?? null) . '"' . $modal_attrs . '>
				<div class="modal-dialog' . $dialog_add . '">
					<div class="modal-content">
						<div class="modal-header">'
            . (($extra['show_close'] ?? null) ? '<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>' : '')
            . (($extra['header'] ?? null) ? '<h3>' . ($extra['header'] ?? null) . '</h3>' : '')
            . '</div>
						<div class="modal-body">' . ($extra['body'] ?? null) . '</div>'
            . (($extra['footer'] ?? null) ? '<div class="modal-footer">' . ($extra['footer'] ?? null) . '</div>' : '') . '
					</div>
				</div>
			</div>';
    }

modal_button

public function modal_button($text = '', $id = '', $extra = [])

Button that opens the modal() $id; $extra are button() options.

    public function modal_button($text = '', $id = '', $extra = [])
    {
        $extra = (array) $extra;
        $this->_sync_fw();
        $id = fix_html_attr_id((string) $id);
        $extra += $this->_is_bs5 ? ['data-bs-toggle' => 'modal', 'data-bs-target' => '#' . $id] : ['data-toggle' => 'modal', 'data-target' => '#' . $id];
        return $this->button($text, $extra);
    }

module_menu

public function module_menu($obj, $items = [], $extra = [])
    public function module_menu($obj, $items = [], $extra = [])
    {
        // TODO: check $obj for methods existance and hide not existing
        // TODO: build auto-menu if items empty
        $out = [];
        $selected = '/' . ($_GET['object'] ?? null) . '/' . ($_GET['action'] ?? null);
        $selected2 = '/@object/' . ($_GET['action'] ?? null);
        foreach ((array) $items as $i) {
            $is_selected = (strpos($i[0], $selected) === 0) || (strpos($i[0], $selected2) === 0);
            $out[] = a($i[0], $i[1], $i[2], $i[3], $is_selected ? 'disabled btn-primary' : '', ($i[5] ?? null) ?: '', $i[6]);
        }
        return implode($out) . PHP_EOL;
    }
public function multi_check_box($name, $values = [], $selected = [], $horizontal = true, $type = 2, $add_str = '', $translate = 0, $name_as_array = false, $extra = [])

Processing many checkboxes at one time.

    public function multi_check_box($name, $values = [], $selected = [], $horizontal = true, $type = 2, $add_str = '', $translate = 0, $name_as_array = false, $extra = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = $extra['name'];
        }
        $values = isset($extra['values']) ? $extra['values'] : (array) $values; // Required
        $translate = isset($extra['translate']) ? $extra['translate'] : 0;
        if ($extra['no_translate'] ?? null) {
            $translate = 0;
        }
        $selected = ($extra['selected'] ?? null);
        $type = isset($extra['type']) ? $extra['type'] : ($type !== null ? $type : 2);
        $horizontal = isset($extra['horizontal']) ? $extra['horizontal'] : $horizontal;
        $name_as_array = isset($extra['name_as_array']) ? $extra['name_as_array'] : false;
        $add_str = isset($extra['add_str']) ? $extra['add_str'] : '';
        if ($extra['class'] ?? null) {
            $add_str .= ' class="' . trim($extra['class'] . ' ' . $extra['class_add']) . '" ';
        }
        if ($extra['style'] ?? null) {
            $add_str .= ' style="' . $extra['style'] . '" ';
        }
        if ( ! $values) {
            return false;
        }
        if ( ! is_array($selected)) {
            $selected = (string) $selected;
        }
        $body = [];
        if ($extra['outer_label'] ?? null) {
            $body[] = '<label class="outer-label">' . $extra['outer_label'] . '</label>';
        }
        foreach ((array) $values as $key => $value) {
            $sel_text = '';
            // Selected value could be an array
            if (is_array($selected)) {
                if ($type == 1) {
                    $sel_text = in_array($value, $selected) ? 'checked="checked"' : '';
                } else {
                    $sel_text = isset($selected[$key]) ? 'checked="checked"' : '';
                }
            } elseif (strlen($selected)) {
                $_what_compare = (string) ($type == 1 ? $value : $key);
                $sel_text = $_what_compare == $selected ? 'checked="checked"' : '';
            } else {
                $sel_text = '';
            }
            $is_selected = strlen($sel_text) ? 1 : 0;
            if ($name_as_array) {
                $val_name = $name . '[' . $key . ']';
            } else {
                $val_name = $name . '_' . $key;
            }
            $label_extra = ($extra['label_extra'] ?? null);
            $label_extra['class'] = isset($label_extra['class']) ? $label_extra['class'] : (isset($extra['class_label_checkbox']) ? $extra['class_label_checkbox'] : $this->CLASS_LABEL_CHECKBOX);
            if ($horizontal) {
                $label_extra['class'] .= ' ' . $this->CLASS_LABEL_CHECKBOX_INLINE;
            }
            if ($extra['class_add_label_checkbox'] ?? null) {
                $label_extra['class'] .= ' ' . $extra['class_add_label_checkbox'];
            }
            if ($is_selected) {
                $label_extra['class'] .= ' ' . $this->CLASS_LABEL_CHECKBOX_SELECTED;
            }
            $id = __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
            if ($extra['no_desc'] ?? null) {
                $desc = '';
            } else {
                $desc = ($translate ? t($value) : $value);
            }
            if ($this->BOXES_USE_STPL) {
                $body[] = tpl()->parse('system/common/check_box_item', [
                    'name' => $val_name,
                    'value' => $key,
                    'desc' => $desc,
                    'selected' => $sel_text,
                    'add_str' => $add_str,
                    'label' => $translate ? t($value) : $value,
                    'id' => $id,
                    'extra' => $extra,
                ]);
            } else {
                $body[] = '<label' . _attrs($label_extra, ['id', 'class', 'style']) . '>'
                    . '<input type="checkbox"' . $this->_bs5_check_input_attr($add_str) . ' name="' . $val_name . '" id="' . $id . '" value="' . $key . '"'
                    . ($is_selected ? ' ' . $sel_text : '') . ($add_str ? ' ' . trim($add_str) : '')
                    . '>  ' . '<span>' . $desc . '</span>'  // Please do not remove whitespace :)
                    . '</label>';
            }
        }
        return implode(PHP_EOL, $body);
    }

multi_select

public function multi_select($name, $values = [], $selected = '', $show_text = false, $type = 2, $add_str = '', $translate = 0, $level = 0, $disabled = false)
    public function multi_select($name, $values = [], $selected = '', $show_text = false, $type = 2, $add_str = '', $translate = 0, $level = 0, $disabled = false)
    {
        $extra = [];
        // Passing params as array
        if (is_array($name)) {
            $extra = $name;
            $name = $extra['name'];
        }
        $values = isset($extra['values']) ? $extra['values'] : (array) $values; // Required
        $translate = isset($extra['translate']) ? $extra['translate'] : $translate;
        if ($extra['no_translate'] ?? null) {
            $translate = 0;
        }
        $selected = isset($extra['selected']) ? $extra['selected'] : $selected;
        $show_text = isset($extra['show_text']) ? $extra['show_text'] : ($show_text !== null ? $show_text : false);
        $type = isset($extra['type']) ? $extra['type'] : ($type !== null ? $type : 2);
        $level = isset($extra['level']) ? $extra['level'] : $level;
        // (example: $add_str = 'size=6') disabled
        $add_str = isset($extra['add_str']) ? $extra['add_str'] : $add_str;
        $extra['class'] = isset($extra['class']) ? $extra['class'] : $this->CLASS_SELECT_BOX;
        ($extra['class_add'] ?? null) && $extra['class'] = trim(($extra['class'] ?? null) . ' ' . $extra['class_add']);
        if ( ! $values) {
            return false;
        }
        if (($extra['disabled'] ?? null) || $disabled) {
            $extra['disabled'] = 'disabled';
        }
        if ( ! is_array($selected)) {
            $selected = (string) $selected;
        }
        $body = [];
        $id = '';
        if ($level == 0) {
            ($extra['force_id'] ?? null) && $id = $extra['force_id'];
            $id = $id ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
            if ($this->AUTO_ASSIGN_IDS) {
                $extra['id'] = $id;
            }
            if ($extra['outer_label'] ?? null) {
                $body[] = '<label class="outer-label">' . $extra['outer_label'] . '</label>';
            }
            $extra['multiple'] = 'multiple';
            $extra['name'] = $name ? $name . '[]' : '';
            $body[] = '<select' . _attrs($extra, ['name', 'id', 'class', 'style', 'multiple', 'disabled', 'required']) . ($add_str ? ' ' . trim($add_str) : '') . '>';
        }
        if ($show_text && $level == 0) {
            $def_opt_text = $show_text;
            if ($show_text == 1) {
                $def_opt_text = str_replace(['%name%', '%select%'], [t($name), t('Select')], $this->SELECT_BOX_DEF_OPT_TPL);
            }
            $body[] = '<option value="" class="' . $this->CLASS_SELECT_OPTION_DEFAULT . '">' . $def_opt_text . '</option>';
        }
        $self_func = __FUNCTION__;
        foreach ((array) $values as $key => $value) {
            if (is_array($value)) {
                $body[] = '<optgroup label="' . $key . '" title="' . ($translate ? t($key) : $key) . '">';
                $body[] = $this->$self_func($name, $value, $selected, $show_text, $type, $add_str, $translate, $level + 1);
                $body[] = '</optgroup>';
            } else {
                // Selected value could be an array
                if (is_array($selected)) {
                    if ($type == 1) {
                        $sel_text = in_array($key, $selected) ? 'selected="selected"' : '';
                    } else {
                        $sel_text = isset($selected[$key]) ? 'selected="selected"' : '';
                    }
                } elseif (strlen($selected)) {
                    $_what_compare = (string) ($type == 1 ? $value : $key);
                    $sel_text = $_what_compare == $selected ? 'selected="selected"' : '';
                } else {
                    $sel_text = '';
                }
                $body[] = '<option value="' . $key . '"' . ($sel_text ? ' ' . $sel_text : '') . '>' . ($translate ? t($value) : $value) . '</option>';
            }
        }
        $body[] = $level == 0 ? '</select>' : '';
        return implode(PHP_EOL, $body);
    }
public function multi_select_box($name, $values = [], $selected = '', $show_text = false, $type = 2, $add_str = '', $translate = 0, $level = 0, $disabled = false)

Alias.

    public function multi_select_box($name, $values = [], $selected = '', $show_text = false, $type = 2, $add_str = '', $translate = 0, $level = 0, $disabled = false)
    {
        return $this->multi_select($name, $values, $selected, $show_text, $type, $add_str, $translate, $level, $disabled);
    }

offcanvas

public function offcanvas($extra = [])

Side panel that slides in (bs5 offcanvas); Bootstrap 3 / 4 render a modal() with the same id. $extra['body'] is developer-supplied HTML, not escaped (also run request data in it through _escape_tpl_braces(): the page template parses the output again); the title is escaped. Options: id, title, body, placement (start, end, top, bottom), backdrop (false, `static`), scroll, show, no_close, class_add, no_escape (title), data-*, attr. Open it with offcanvas_button().

    public function offcanvas($extra = [])
    {
        $extra = (array) $extra;
        $this->_sync_fw();
        $extra['id'] = fix_html_attr_id((string) (($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1)));
        if ($this->_is_bs5) {
            return $this->_bs5()->offcanvas($extra);
        }
        return $this->modal([
            'id' => $extra['id'],
            'header' => $this->_b4_text($extra['title'] ?? '', $extra),
            'body' => $extra['body'] ?? '',
            'show_close' => empty($extra['no_close']),
        ]);
    }
public function offcanvas_button($text = '', $id = '', $extra = [])

Button that opens the offcanvas() $id (Bootstrap 3: the modal); $extra are button() options.

    public function offcanvas_button($text = '', $id = '', $extra = [])
    {
        $extra = (array) $extra;
        $this->_sync_fw();
        $id = fix_html_attr_id((string) $id);
        $extra += $this->_is_bs5 ? ['data-bs-toggle' => 'offcanvas', 'data-bs-target' => '#' . $id] : ['data-toggle' => 'modal', 'data-target' => '#' . $id];
        $extra['attr'] = (array) ($extra['attr'] ?? []) + ['aria-controls' => $id];
        return $this->button($text, $extra);
    }

pagination

public function pagination($data = [], $extra = [])
    public function pagination($data = [], $extra = [])
    {
        if ($this->_is_bs5) {
            return $this->_bs5()->pagination($data, $extra);
        }
        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        $prev = $next = null;
        if (isset($data['prev'])) {
            $prev = ($data['prev'] ?? null);
            unset($data['prev']);
        }
        if (isset($data['next'])) {
            $next = ($data['next'] ?? null);
            unset($data['next']);
        }
        $items = [];
        // TODO: auto-detect current page and need of first. last
        if ($prev || $this->_pagination_state('prev', $extra) === 'disabled') {
            $items[] = $this->_pagination_item('prev', $prev, t('Prev'), $extra);
        }
        foreach ((array) $data as $page => $link) {
            $items[] = $this->_pagination_item($page, $link, $page, $extra);
        }
        if ($next || $this->_pagination_state('next', $extra) === 'disabled') {
            $items[] = $this->_pagination_item('next', $next, t('Next'), $extra);
        }
        [$size_class, $wrap_class] = $this->_pagination_classes($extra['size'] ?? '', $extra['align'] ?? '', $this->_is_bs3 ? 'bs3' : 'bs2');
        $class = strlen($size_class) ? trim($size_class . ' ' . ($extra['class'] ?? '')) : ($extra['class'] ?? null);
        if ($this->_is_bs3) {
            return '<div' . (strlen($wrap_class) ? ' class="' . $wrap_class . '"' : '') . '><ul class="pagination' . ($class ? ' ' . $class : '') . '" id="' . ($extra['id'] ?? null) . '">' . implode(PHP_EOL, $items) . '</ul></div>';
        }
        return '<div class="pagination' . ($class ? ' ' . $class : '') . '" id="' . ($extra['id'] ?? null) . '"><ul>' . implode(PHP_EOL, $items) . '</ul></div>';
    }

panel

public function panel($extra = [])

Panel (bs5: card): title, body (developer-supplied HTML, as modal() bodies), class, class_add. B5 options: subtitle (escaped), footer (HTML), img_top / img_alt, border (colour), theme (bs5). Alias card().

    public function panel($extra = [])
    {
        if ($this->_is_bs5) {
            return $this->_bs5()->panel($extra);
        }
        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        $border = $this->_color_type($extra['border'] ?? '', '');
        $extra['class'] = ($extra['class'] ?? null) ?: 'panel panel-' . (in_array($border, ['primary', 'success', 'info', 'warning', 'danger'], true) ? $border : 'default');
        $subtitle = strlen((string) ($extra['subtitle'] ?? '')) ? ' <small>' . $this->_b4_text($extra['subtitle'], $extra) . '</small>' : '';
        $img = strlen((string) ($extra['img_top'] ?? '')) ? '<img' . $this->_b4_attrs(['src' => $this->_safe_href($extra['img_top']), 'class' => 'img-responsive']) . $this->_img_alt($extra) . '>' : '';
        $footer = strlen((string) ($extra['footer'] ?? '')) ? '<div class="panel-footer">' . $extra['footer'] . '</div>' : '';
        return
            '<div class="' . implode(' ', array_filter([($extra['class'] ?? null), ($extra['class_add'] ?? null)])) . '" id="' . ($extra['id'] ?? null) . '">
				<div class="panel-heading">
					<h3 class="panel-title">' . ($extra['title'] ?? null) . $subtitle . '</h3>
				</div>' . $img . '
				<div class="panel-body">' . ($extra['body'] ?? null) . '</div>' . $footer . '
			</div>';
    }

phone_box

public function phone_box($name = 'phone', $values = [], $selected = '', $extra = [])
    public function phone_box($name = 'phone', $values = [], $selected = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
        } else {
            $extra['name'] = $name;
        }
        if ( ! $extra['name']) {
            $name = $extra['name'] = 'phone';
        }
        $extra['force_id'] = $extra['force_id'] ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        if ( ! $extra['id']) {
            $extra['id'] = $extra['force_id'] . '_input';
        }

        asset('jquery-intl-tel-input');

        $countries = [];
        foreach ((array) main()->get_data('geo_countries') as $k => $data) {
            $countries[] = strtolower($data['code']);
        }
        $preferred_countries = [
            'ua',
            'by',
        ];
        $js_options = (array) $extra['js_options'] + [
            'autoPlaceholder' => true,
            'onlyCountries' => $countries,
            'preferredCountries' => $preferred_countries,
            // 'excludeCountries' => ["us"],
            // 'separateDialCode' => true,
            // 'allowDropdown' => false,
            // 'autoHideDialCode' => false,
            // 'dropdownContainer' => "body",
            // 'initialCountry' => "auto",
            // 'nationalMode' => false,
            // 'numberType' => "MOBILE",
            // 'utilsScript' => '//cdnjs.cloudflare.com/ajax/libs/intl-tel-input/8.5.2/js/utils.js',
        ];
        jquery('
			var input_selector = "#' . addslashes($extra['id']) . '";
			var phone = $(input_selector);
			var reset = function() {
				phone.removeClass("phone-error");
				phone.removeClass("phone-success");
			};

			phone.intlTelInput($.merge(
				' . json_encode($js_options, JSON_PRETTY_PRINT) . '
				, {
					geoIpLookup : function(callback) {
						$.get("//ipinfo.io", function() {}, "jsonp").always(function(resp) {
							var countryCode = (resp && resp.country) ? resp.country : "";
							callback(countryCode);
						});
					}
				}
			));

			phone.blur(function() {
				reset();
				if ($.trim(phone.val())) {
					if (phone.intlTelInput("isValidNumber")) {
						phone.addClass("phone-success");
					} else {
						phone.addClass("phone-error");
					}
				}
			});

			// on keyup / change flag: reset
			phone.on("keyup change", reset);

			$(phone).closest("form").submit(function() {
				phone.val(phone.intlTelInput("getNumber"));
			});
		');
        return $this->input($extra + [
            'maxlength' => 20,
            'type' => 'tel',
            //			'pattern' => '^[0-9\s\(\)-]{7,20}$',
        ]);
    }

placeholder

public function placeholder($extra = [])

Skeleton lines while content loads (bs5 placeholders). Options: lines (3), widths (columns of 12 per line, repeated; default [12, 8, 6]), glow (true, `wave`, false), size (xs / sm / lg), color, class_add, id. Bootstrap 3 / 4: an empty div.

    public function placeholder($extra = [])
    {
        $extra = (array) $extra;
        $this->_sync_fw();
        if ($this->_is_bs5) {
            return $this->_bs5()->placeholder($extra);
        }
        return '<div' . $this->_b4_attrs(['class' => trim($extra['class_add'] ?? ''), 'id' => $extra['id'] ?? '', 'aria-hidden' => 'true']) . '></div>';
    }

progress_bar

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

Progress bar (several values: stacked). B5 options: striped, animated, height, theme (bs5); with B5_ITEM_OPTIONS per bar label (escaped unless no_escape), striped, animated.

    public function progress_bar($data = [], $extra = [])
    {
        if ($this->_is_bs5) {
            return $this->_bs5()->progress_bar($data, $extra);
        }
        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        $items = [];
        foreach ((array) $data as $v) {
            if ( ! is_array($v)) {
                $val = $v;
                $v = [];
            } else {
                $val = ($v['val'] ?? null);
            }
            $type = ($v['type'] ?? null) ?: ($extra['type'] ?? null);
            $class_item = ($v['class_item'] ?? null) ?: ($extra['class_item'] ?? null);
            [$striped, $animated, $label] = $this->_progress_bar_item($v, $extra);
            $bar_add = ($striped || $animated) ? ' progress-bar-striped' : '';
            $bar_add .= $animated ? ($this->_is_bs4() ? ' progress-bar-animated' : ' active') : '';
            $items[] = '<div class="progress-bar bar bar-' . $type . ' progress-bar-' . $type . $bar_add . ($class_item ? ' ' . $class_item : '')
                . '" style="width: ' . $val . '%;' . (($v['style'] ?? null) ? ' ' . ($v['style'] ?? null) : '') . '" role="progressbar">' . $label . '</div>';
        }
        $height = $this->_css_length($extra['height'] ?? '');
        return '<div class="progress' . (($extra['class'] ?? null) ? ' ' . ($extra['class'] ?? null) : '') . '" id="' . ($extra['id'] ?? null) . '"'
            . ($height !== '' ? ' style="height: ' . $height . '"' : '') . '>' . implode(PHP_EOL, (array) $items) . '</div>';
    }

radio_box

public function radio_box($name, $values = [], $selected = '', $horizontal = true, $type = 2, $add_str = '', $translate = 0)
    public function radio_box($name, $values = [], $selected = '', $horizontal = true, $type = 2, $add_str = '', $translate = 0)
    {
        $extra = [];
        if (is_array($name)) {
            $extra = $name;
            $name = ($extra['name'] ?? null);
        }
        $values = isset($extra['values']) ? ($extra['values'] ?? null) : (array) $values; // Required
        $translate = isset($extra['translate']) ? ($extra['translate'] ?? null) : $translate;
        if ($extra['no_translate'] ?? null) {
            $translate = 0;
        }
        $selected = isset($extra['selected']) ? ($extra['selected'] ?? null) : $selected;
        $type = isset($extra['type']) ? ($extra['type'] ?? null) : ($type !== null ? $type : 2);
        $horizontal = isset($extra['horizontal']) ? ($extra['horizontal'] ?? null) : $horizontal;
        $add_str = isset($extra['add_str']) ? ($extra['add_str'] ?? null) : $add_str;
        if ($extra['class_add'] ?? null) {
            $add_str .= ' class="' . trim(($extra['class'] ?? null) . ' ' . ($extra['class_add'] ?? null)) . '" ';
        }
        if ($extra['style'] ?? null) {
            $add_str .= ' style="' . ($extra['style'] ?? null) . '" ';
        }
        if ( ! $values) {
            return false;
        }
        $selected = (string) $selected;
        $this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1;
        $id_prefix = __FUNCTION__ . '_' . $this->_ids[__FUNCTION__];
        ($extra['force_id'] ?? null) && $id_prefix = ($extra['force_id'] ?? null);
        $counter = 0;
        $body = [];
        if ($extra['outer_label'] ?? null) {
            $body[] = '<label class="outer-label">' . ($extra['outer_label'] ?? null) . '</label>';
        }
        $orig_extra = $extra;
        $use_stpl = isset($extra['use_stpl']) ? ($extra['use_stpl'] ?? null) : $this->BOXES_USE_STPL;
        foreach ((array) $values as $value => $val_name) {
            $label_extra_class = '';
            if (is_array($val_name)) {
                $extra = (array) $orig_extra + (array) ($val_name['extra'] ?? null);
                $label_extra_class = ($val_name['class'] ?? null);
                $val_name = ($val_name['html'] ?? null);
            }
            $is_selected = ((string) ($type == 1 ? $val_name : $value) == $selected);
            $id = $id_prefix . '_' . ++$counter;
            if ($use_stpl) {
                $body[] = tpl()->parse('system/common/radio_box_item', [
                    'name' => $name,
                    'value' => $value,
                    'selected' => $is_selected ? 'checked="checked"' : '',
                    'add_str' => $add_str,
                    'label' => $translate ? t($val_name) : $val_name,
                    'horizontal' => (int) ((bool) $horizontal),
                    'id' => $id,
                    'extra' => $extra,
                ]);
            } else {
                $label_extra = $extra['label_extra'] ?? null;
                if (isset($label_extra['class'])) {
                    $label_extra['class'] = ($label_extra_class ?: ($label_extra['class'] ?? null) ?: $this->CLASS_LABEL_RADIO) . ($horizontal ? ' ' . $this->CLASS_LABEL_RADIO_INLINE : '');
                }
                if ($extra['class_add_label_radio'] ?? null) {
                    $label_extra['class'] = ($label_extra['class'] ?? '') . ' ' . ($extra['class_add_label_radio'] ?? null);
                }
                if ($is_selected) {
                    $label_extra['class'] = ($label_extra['class'] ?? '') . ' ' . $this->CLASS_LABEL_RADIO_SELECTED;
                }
                $body[] =
                    '<label' . _attrs($label_extra, ['id', 'class', 'style']) . '>'
                    . '<input type="radio"' . $this->_bs5_check_input_attr($add_str) . ' name="' . $name . '" id="' . $id . '" value="' . $value . '"' . ($add_str ? ' ' . trim($add_str) : '') . ($is_selected ? ' checked="checked"' : '') . '>'
                    . '<span>' . t($val_name) . '</span>'
                    . '</label>' . PHP_EOL;
            }
        }
        return implode(PHP_EOL, $body);
    }

ratio

public function ratio($html = '', $extra = [])

Responsive embed box: bs5 `ratio ratio-16x9`, Bootstrap 3 `embed-responsive embed-responsive-16by9`. `ratio` is "W x H" (16x9, 4x3, 1x1, 21x9 or any other, e.g. 3:2, which gets an inline style). $html is developer-supplied HTML (an iframe, a video), not escaped (also run request data in it through _escape_tpl_braces(): the page template parses the output again); Bootstrap 3 / 4 fill the box only with an iframe, embed, object, video or `.embed-responsive-item` child. Options: ratio, class_add, id, style, data-*, attr.

    public function ratio($html = '', $extra = [])
    {
        $extra = (array) $extra;
        $this->_sync_fw();
        [$w, $h] = preg_match('~^([1-9]\d{0,3})\s*[x:/]\s*([1-9]\d{0,3})$~D', trim((string) ($extra['ratio'] ?? '')), $m) ? [(int) $m[1], (int) $m[2]] : [16, 9];
        $ratio = $w . 'x' . $h;
        $percent = rtrim(rtrim(sprintf('%.4F', $h / $w * 100), '0'), '.') . '%';
        if ($this->_is_bs5) {
            $known = in_array($ratio, ['1x1', '4x3', '16x9', '21x9'], true);
            $class = $known ? 'ratio ratio-' . $ratio : 'ratio';
            $style = $known ? '' : '--bs-aspect-ratio: ' . $percent . ';';
        } else {
            $known = ['16x9' => '16by9', '4x3' => '4by3'] + ($this->_is_bs4() ? ['21x9' => '21by9', '1x1' => '1by1'] : []);
            $known = $known[$ratio] ?? '';
            $class = $known ? 'embed-responsive embed-responsive-' . $known : 'embed-responsive';
            $style = $known ? '' : 'padding-bottom: ' . $percent . ';';
        }
        $a = [
            'class' => trim($class . ' ' . ($extra['class_add'] ?? '')),
            'id' => $extra['id'] ?? '',
            'style' => trim($style . ' ' . ($extra['style'] ?? '')),
        ] + $this->_b4_pass($extra);
        return '<div' . $this->_b4_attrs($a) . '>' . $html . '</div>';
    }

select2_box

public function select2_box($name, $values = [], $selected = '', $extra = [])
    public function select2_box($name, $values = [], $selected = '', $extra = [])
    {
        $css = [];
        $style = [];
        if (is_array($name)) {
            $extra = (array) $extra + $name;
        } else {
            $extra['name'] = $name;
        }
        $extra['force_id'] = $extra['force_id'] ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        // put default js options here
        $js_options = (array) $extra['js_options'] + [
            'width' => 'element',
            'placeholder' => ($extra['placeholder'] ?? null) ?: ($extra['desc'] ?? null),
        ];
        // ajax
        $js_functions = [];
        if ($extra['ajax_func'] ?? null) {
            foreach ((array) $extra['ajax_func'] as $key => $value) {
                $js_options['ajax'][$key] = '%__' . $key . '__%';
                $js_functions['"%__' . $key . '__%"'] = $value;
            }
        }
        if ($extra['ajax'] ?? null) {
            $js_options += [
                'maximumSelectionSize' => 10,
                'minimumInputLength' => 1,
                'initSelection' => '%__initSelection__%',
            ];
            $js_options['ajax'] = $extra['ajax'];
            $js_options['ajax'] += [
                // 'dataType'           => 'json',
                // 'quietMillis'        => 500,
                // 'cache'              => true,
                'data' => '%__data__%',
                'results' => '%__results__%',
            ];
            $js_functions += [
                '"%__data__%"' => 'function( term, page, context ) { return { q: term, page: page }; }',
                '"%__results__%"' => 'function( data, page, query ) { return { results: data.items, more: data.more || false  }; }',
                '"%__initSelection__%"' => 'function( element, callback ) {
					var $this = $(element);
					var id = $this.val();
					if( id !== "" ) {
						$.ajax("' . $js_options['ajax']['url'] . '" + "&q=" + id, {
							dataType: "json"
						}).done(function(data) { callback(data.items[0] || null); });
					}
				}',
            ];
        }
        asset('jq-select2');
        // prepare js options
        $_js_options = json_encode($js_options, JSON_PRETTY_PRINT);
        $js_functions && $_js_options = str_replace(array_keys($js_functions), array_values($js_functions), $_js_options);
        jquery('$("#' . addslashes($extra['force_id']) . '").select2(' . $_js_options . ');');
        $func = $extra['multiple'] ? 'multi_select' : 'select_box';
        if ($extra['ajax']) {
            $func = 'input';
            $extra['id'] = $extra['force_id'];
            $extra['value'] = ($extra['selected'] ?? null) ?: ($extra['value'] ?? null) ?: '';
            $css[] = $this->CLASS_INPUT;
        }
        $css[] = 'no-chosen';
        $extra['class'] && $css[] = $extra['class'];
        $extra['style'] && $style[] = $extra['style'];
        $extra['class'] = implode(' ', $css);
        $extra['style'] = implode(';', $style);
        return $this->$func($extra, $values, $selected);
    }

select_box

public function select_box($name, $values = [], $selected = '', $show_text = false, $type = 2, $add_str = '', $translate = 0, $level = 0, $extra = [])
    public function select_box($name, $values = [], $selected = '', $show_text = false, $type = 2, $add_str = '', $translate = 0, $level = 0, $extra = [])
    {
        // Passing params as array
        if (is_array($name)) {
            $extra = $name;
            $name = $extra['name'];
        }
        ! is_array($extra) && $extra = [];
        $values = isset($extra['values']) ? $extra['values'] : (array) $values; // Required
        $translate = isset($extra['translate']) ? $extra['translate'] : $translate;
        if ($extra['no_translate'] ?? null) {
            $translate = 0;
        }
        $selected = isset($extra['selected']) ? $extra['selected'] : $selected;
        $show_text = isset($extra['show_text']) ? $extra['show_text'] : ($show_text !== null ? $show_text : false);
        $type = isset($extra['type']) ? $extra['type'] : ($type !== null ? $type : 2);
        $level = isset($extra['level']) ? $extra['level'] : $level;
        // (example: $add_str = 'size=6')
        $add_str = isset($extra['add_str']) ? $extra['add_str'] : $add_str;
        $extra['class'] = isset($extra['class']) ? $extra['class'] : $this->CLASS_SELECT_BOX;
        ($extra['class_add'] ?? null) && $extra['class'] = trim(($extra['class'] ?? null) . ' ' . $extra['class_add']);
        if ( ! $values && @ ! $extra['ajax']) {
            return false;
        }
        if ($extra['disabled'] ?? null) {
            $extra['disabled'] = 'disabled';
        }
        $id = null;
        $body = [];
        if ($level == 0) {
            ($extra['force_id'] ?? null) && $id = $extra['force_id'];
            $id = $id ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
            if ($this->AUTO_ASSIGN_IDS) {
                $extra['id'] = $id;
            }
            if ($extra['outer_label'] ?? null) {
                $body[] = '<label class="outer-label">' . $extra['outer_label'] . '</label>';
            }
            $extra['name'] = $name;
            $body[] = '<select' . _attrs($extra, ['name', 'id', 'class', 'style', 'disabled', 'required']) . ($add_str ? ' ' . $add_str : '') . '>';
        }
        $selected = (string) $selected;
        if ($show_text && $level == 0) {
            $def_opt_text = $show_text;
            if ($show_text == 1) {
                $def_opt_text = str_replace(['%name%', '%select%'], [t($name), t('Select')], $this->SELECT_BOX_DEF_OPT_TPL);
            }
            $body[] = '<option value="" class="' . $this->CLASS_SELECT_OPTION_DEFAULT . '">' . $def_opt_text . '</option>';
        }
        $self_func = __FUNCTION__;
        $option_callback = ($extra['option_callback'] ?? null);
        $use_option_callback = is_callable($option_callback);
        foreach ((array) $values as $key => $cur_value) {
            if (is_array($cur_value)) {
                $body[] = '<optgroup label="' . $key . '" title="' . ($translate ? t($key) : $key) . '">';
                $body[] = $this->$self_func($name, $cur_value, $selected, $show_text, $type, $add_str, $translate, $level + 1);
                $body[] = '</optgroup>';
            } else {
                $_what_compare = (string) ($type == 1 ? $cur_value : $key);
                $is_selected = ($_what_compare == $selected);
                $text = $translate ? t($cur_value) : $cur_value;
                if ($use_option_callback) {
                    $body[] = $option_callback($key, $cur_value, $is_selected, $text, $extra);
                } else {
                    $body[] = '<option value="' . $key . '"' . ($is_selected ? ' selected="selected"' : '') . '>' . $text . '</option>';
                }
            }
        }
        $body[] = $level == 0 ? '</select>' : '';
        return implode(PHP_EOL, $body);
    }

simple_table

public function simple_table($replace = [], $extra = [])
    public function simple_table($replace = [], $extra = [])
    {
        if ( ! $replace) {
            return false;
        }
        $key_name = isset($extra['key']['name']) ? $extra['key']['name'] : 'key';
        $val_name = isset($extra['val']['name']) ? $extra['val']['name'] : 'val';

        $key_extra = isset($extra['key']['extra']) ? $extra['key']['extra'] : [];
        $val_extra = isset($extra['val']['extra']) ? $extra['val']['extra'] : [];

        $key_callable = null;
        $val_callable = null;
        $key_func = isset($extra['key']['func']) ? $extra['key']['func'] : 'text';
        if ( ! is_string($key_func) && is_callable($key_func)) {
            $key_callable = $key_func;
            $key_func = 'func';
        }
        $val_func = isset($extra['val']['func']) ? $extra['val']['func'] : 'text';
        if ( ! is_string($val_func) && is_callable($val_func)) {
            $val_callable = $val_func;
            $val_func = 'func';
        }

        $data = [];
        foreach ((array) $replace as $k => $v) {
            $data[] = [
                $key_name => $k,
                $val_name => $v,
            ];
        }

        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        $extra['no_header'] = isset($extra['no_header']) ? $extra['no_header'] : 1;
        $extra['pager_records_on_page'] = isset($extra['pager_records_on_page']) ? $extra['pager_records_on_page'] : 10000;
        $extra['no_total'] = isset($extra['no_total']) ? $extra['no_total'] : true;
        $extra['no_pages'] = isset($extra['no_pages']) ? $extra['no_pages'] : true;
        $extra['hide_empty'] = isset($extra['hide_empty']) ? $extra['hide_empty'] : true;

        return table($data, $extra)
            ->$key_func($key_name, $key_callable, $key_extra)
            ->$val_func($val_name, $val_callable, $val_extra);
    }

spinner

public function spinner($extra = [])

Loading indicator: bs5 `spinner-border` / `spinner-grow`, Bootstrap 3 a spinning Font Awesome icon. Options: type (border / grow), size (sm), color, text (for screen readers, default "Loading..."), class_add (`htmx-indicator` makes it an hx-indicator), id, style, no_escape, data-*, attr. Bootstrap 4 gets the Bootstrap 3 markup.

    public function spinner($extra = [])
    {
        $extra = (array) $extra;
        $this->_sync_fw();
        $text = $this->_b4_text($extra['text'] ?? t('Loading...'), $extra);
        $color = $this->_color_type($extra['color'] ?? '', '');
        $color = ($color === '' || $color === 'default') ? '' : 'text-' . $color;
        $sm = ($extra['size'] ?? '') === 'sm';
        $a = ['id' => $extra['id'] ?? '', 'style' => $extra['style'] ?? '', 'role' => 'status'] + $this->_b4_pass($extra);
        if ($this->_is_bs5) {
            $type = ($extra['type'] ?? '') === 'grow' ? 'spinner-grow' : 'spinner-border';
            $a = ['class' => trim($type . ($sm ? ' ' . $type . '-sm' : '') . ($color ? ' ' . $color : '') . ' ' . ($extra['class_add'] ?? ''))] + $a;
            return '<span' . $this->_b4_attrs($a) . '><span class="visually-hidden">' . $text . '</span></span>';
        }
        $a = ['class' => trim($color . ' ' . ($extra['class_add'] ?? ''))] + $a;
        return '<span' . $this->_b4_attrs($a) . '><i class="fa fa-spinner fa-spin' . ($sm ? '' : ' fa-2x') . '" aria-hidden="true"></i><span class="sr-only">' . $text . '</span></span>';
    }

tabs

public function tabs($tabs = [], $extra = [])

Tabs; the tab contents are developer-supplied HTML (as modal() bodies). B5 options of the nav: type (tabs, pills, underline), fill, justified, vertical (see _tabs_nav_options()); form2 passes its `tabs` param here.

    public function tabs($tabs = [], $extra = [])
    {
        if ($this->_is_bs5) {
            return $this->_bs5()->tabs($tabs, $extra);
        }
        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);

        $extra_by_id = [];
        if (isset($extra['by_id'])) {
            $extra_by_id = (array) ($extra['by_id'] ?? null);
            unset($extra['by_id']);
        }
        $links_prefix = ($extra['links_prefix'] ?? null) ?: 'tab_';
        [$nav_class, $nav_toggle, $nav_vertical] = $this->_tabs_nav_options($extra, 'bs3');

        $headers = [];
        $items = [];
        $i = 0;
        foreach ((array) $tabs as $k => $v) {
            $desc_raw = null;
            $disabled = null;
            if ( ! is_array($v)) {
                $content = $v;
                $v = [];
            } else {
                $content = ($v['content'] ?? null);
                $desc_raw = ($v['desc_raw'] ?? null);
                $disabled = ($v['disabled'] ?? null);
            }
            $content = trim($content ?? '');
            $_extra = (array) ($extra_by_id[$k] ?? null) + (array) $extra;
            if (($_extra['hide_empty'] ?? null) && ! strlen($content)) {
                continue;
            }
            $name = ($v['name'] ?? null) ?: $k;
            $desc = ($v['desc'] ?? null) ?: ( ! ($_extra['no_auto_desc'] ?? null) ? ucfirst(str_replace('_', ' ', $name)) : $name);
            $id = preg_replace('~[^a-z0-9_-]+~i', '', ($v['id'] ?? null) ?: $links_prefix . $k);
            if (isset($_extra['selected'])) {
                $is_active = (($_extra['selected'] ?? null) == $k);
            } else {
                $is_active = (++$i == 1);
            }
            if (isset($_extra['totals'][$name])) {
                $v['badge'] = (int) (isset($_extra['totals'][$name]['total']) ? ($_extra['totals'][$name]['total'] ?? null) : ($_extra['totals'][$name] ?? null));
            }
            $badge = isset($v['badge']) ? ' <sup class="badge badge-' . (($v['class_badge'] ?? null) ?: 'info') . '">' . ($v['badge'] ?? null) . '</sup>' : '';
            if ( ! ($_extra['no_headers'] ?? null)) {
                $class_head = ($v['class_head'] ?? null) ?: ($_extra['class_head'] ?? null);
                $class_head .= ($_extra['class_add_head'] ?? null) ? ' ' . ($_extra['class_add_head'] ?? null) : '';
                if ($is_active) {
                    $class_head = trim('active ' . $class_head);
                }
                $_extra_head = (array) ($_extra['tab_head'] ?? null);
                $_extra_head['class'] = ($_extra_head['class'] ?? null) ?: $class_head;
                $headers[] =
                    '<li' . _attrs($_extra_head, ['id', 'class', 'style']) . '>
						<a ' . ( ! $disabled ? 'href="#' . fix_html_attr_id($id) . '" ' : '') . 'data-toggle="' . $nav_toggle . '">' . ($desc_raw ?: t($desc)) . $badge . '</a>
					</li>';
            }
            $class_body = (($_extra['class'] ?? null) ?: ($v['class_body'] ?? null)) ?: ($_extra['class_body'] ?? null);
            $class_body = $class_body ?: 'tab-pane';
            $class_body .= ($_extra['class_add_body'] ?? null) ? ' ' . ($_extra['class_add_body'] ?? null) : '';
            if ($is_active || ($_extra['show_all'] ?? null)) {
                $class_body = trim('active ' . $class_body);
            } else {
                $class_body = trim('fade ' . $class_body);
            }
            $_extra_body = (array) ($_extra['tab_body'] ?? null);
            $_extra_body['id'] = ($_extra_body['id'] ?? null) ?: $id;
            $_extra_body['class'] = ($_extra_body['class'] ?? null) ?: $class_body;
            $items[] = '<div' . _attrs($_extra_body, ['id', 'class', 'style']) . '>' . $content . '</div>';
        }
        $nav = $headers ? '<ul id="' . ($extra['id'] ?? null) . '" class="' . $nav_class . '">' . implode(PHP_EOL, (array) $headers) . '</ul>' : '';
        $content = '<div id="' . ($extra['id'] ?? null) . '_content" class="tab-content">' . implode(PHP_EOL, (array) $items) . '</div>';
        if ($nav_vertical && $headers) {
            return '<div class="row"><div class="col-sm-3">' . $nav . '</div>' . PHP_EOL . '<div class="col-sm-9">' . $content . '</div></div>';
        }
        return ($headers ? $nav . PHP_EOL : '') . $content;
    }

textarea

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

Simple textarea form control.

    public function textarea($name = '', $value = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = $extra['name'];
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['name'] = ($extra['name'] ?? null) ?: ($name ?: 'text');
        $extra['value'] = ($extra['value'] ?? null) ?: $value;
        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        $extra['desc'] = ($extra['desc'] ?? null) ?: ucfirst(str_replace('_', '', $extra['name']));
        $extra['type'] = ($extra['type'] ?? null) ?: 'text';
        $extra['placeholder'] = _escape_tpl_braces($extra['placeholder'] ? t($extra['placeholder']) : $extra['desc']);
        $extra['title'] = _escape_tpl_braces($extra['title'] ?? null);
        $extra['contenteditable'] = ( ! isset($extra['contenteditable']) || $extra['contenteditable']) ? 'true' : false;

        $attrs_names = ['id', 'name', 'placeholder', 'contenteditable', 'class', 'style', 'cols', 'rows', 'title', 'required', 'size', 'disabled', 'readonly', 'autocomplete', 'autofocus'];
        return '<textarea' . _attrs($extra, $attrs_names) . '>' . _escape_tpl_braces( ! isset($extra['no_escape']) ? _htmlchars($extra['value']) : $extra['value']) . '</textarea>';
    }

thumbnails

public function thumbnails($data = [], $extra = [])
    public function thumbnails($data = [], $extra = [])
    {
        if ($this->_is_bs5) {
            return $this->_bs5()->thumbnails($data, $extra);
        }
        $items = [];
        $columns = (int) ($extra['columns'] ?? null) ?: 3;
        $row_class = 'span' . round(12 / $columns) . ' col-md-' . round(12 / $columns);
        foreach ((array) $data as $k => $v) {
            if ( ! is_array($v)) {
                $img_src = $v;
                $v = [];
            } else {
                $img_src = ($v['img'] ?? null);
            }
            $class_item = ($v['class_item'] ?? null) ?: ($extra['class_item'] ?? null);
            $tag = $this->_is_bs3 ? 'div' : 'li';
            $items[] =
                '<' . $tag . ' class="' . $row_class . ($class_item ? ' ' . $class_item : '') . '"' . (($v['style'] ?? null) ? ' style="' . ($v['style'] ?? null) . '"' : '') . '>
					<div class="thumbnail">
						<img alt="' . _prepare_html(($v['alt'] ?? null) ?: ($v['head'] ?? null)) . '" src="' . _prepare_html($img_src) . '" />
						' . ((($v['head'] ?? null) || ($v['body'] ?? null)) ? '<div class="caption">' . (($v['head'] ?? null) ? '<h3>' . _prepare_html(($v['head'] ?? null)) . '</h3>' : '') . ' ' . ($v['body'] ?? null) . '</div>' : '') . '
					</div>
				</' . $tag . '>';
        }
        $body = [];
        $tag = $this->_is_bs3 ? 'div' : 'ul';
        foreach (array_chunk($items, $columns) as $_items) {
            $id = __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
            $body[] = '<' . $tag . ' class="thumbnails' . (($extra['class'] ?? null) ? ' ' . ($extra['class'] ?? null) : '') . '" id="' . $id . '">' . implode(PHP_EOL, (array) $_items) . '</' . $tag . '>';
        }
        return implode(PHP_EOL, $body);
    }

time_box

public function time_box($selected = '', $name_postfix = '', $add_str = '', $show_text = 1, $translate = 1)
    public function time_box($selected = '', $name_postfix = '', $add_str = '', $show_text = 1, $translate = 1)
    {
        return _class('html_datetime', 'classes/html/')->time_box($selected, $name_postfix, $add_str, $show_text, $translate);
    }

time_box2

public function time_box2($name, $selected = '', $add_str = '', $show_text = 1, $translate = 1)
    public function time_box2($name, $selected = '', $add_str = '', $show_text = 1, $translate = 1)
    {
        return _class('html_datetime', 'classes/html/')->time_box2($name, $selected, $add_str, $show_text, $translate);
    }

toast

public function toast($body = '', $extra = [])

Notice that shows and hides itself: a bs5 toast (asset yf_bs5_toasts shows it on load and after htmx swaps), a Bootstrap 3 / 4 alert(). $body is developer-supplied HTML, not escaped (escape request data in it and run it through _escape_tpl_braces(): the page template parses the output again); the title is escaped. Options: title, type (success, info, warning, danger / error...), autohide (default true), delay (ms, default 5000), show (default true; false renders it hidden for site JS), no_close, id, class_add, no_escape (title), data-*, attr. Put toasts in toast_container(). The alert fallback keeps title, type, no_close, id and class_add; it ignores autohide, delay, show, data-* and attr.

    public function toast($body = '', $extra = [])
    {
        $extra = (array) $extra;
        $this->_sync_fw();
        if ($this->_is_bs5) {
            return $this->_bs5()->toast($body, $extra);
        }
        $type = $this->_color_type($extra['type'] ?? '', 'info');
        $alert = ['alert' => in_array($type, ['success', 'info', 'warning', 'danger'], true) ? $type : 'info', 'no_close' => $extra['no_close'] ?? null];
        ($extra['id'] ?? null) && $alert['id'] = $extra['id'];
        ($extra['class_add'] ?? null) && $alert['class'] = $extra['class_add'];
        return $this->alert(['head' => $this->_b4_text($extra['title'] ?? '', $extra), 'body' => (string) $body], $alert);
    }
public function toast_container($extra = [])

Fixed corner box that stacks toasts. Options: toasts (rendered toast() HTML, a string or an array), position (top-end default, top-start, top-center, middle-*, bottom-*), id, class_add, data-*, attr. An empty one also loads the yf_bs5_toasts asset, which shows the toasts of `_class('htmx')->_toast()` in the first container of the page. One container per corner: two overlap (MESSAGES_AS_TOASTS uses top-end). Bootstrap 3: a plain div around the alerts.

    public function toast_container($extra = [])
    {
        $extra = (array) $extra;
        $this->_sync_fw();
        if ($this->_is_bs5) {
            return $this->_bs5()->toast_container($extra);
        }
        $toasts = $extra['toasts'] ?? '';
        $a = ['class' => trim('yf-toast-container ' . ($extra['class_add'] ?? '')), 'id' => $extra['id'] ?? ''] + $this->_b4_pass($extra);
        return '<div' . $this->_b4_attrs($a) . '>' . (is_array($toasts) ? implode(PHP_EOL, $toasts) : (string) $toasts) . '</div>';
    }

tooltip

public function tooltip($text = '', $extra = [])
    public function tooltip($text = '', $extra = [])
    {
        if ($this->_is_bs5) {
            return $this->_bs5()->tooltip($text, $extra);
        }
        if (is_array($text)) {
            $extra = (array) $extra + $text;
            $text = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['text'] = ($extra['text'] ?? null) ?: $text;
        $extra['icon'] = ($extra['icon'] ?? null) ?: 'icon icon-info-sign fa fa-question-circle';
        $extra['href'] = ($extra['href'] ?? null) ?: '#';
        $extra['class'] = (($extra['class'] ?? null) ?: 'yf_tip') . (($extra['class_add'] ?? null) ? ' ' . ($extra['class_add'] ?? null) : '');
        $extra['data-content'] = ($extra['data-content'] ?? null) ?: _prepare_html(($extra['text'] ?? null));
        $extra['data-toggle'] = ($extra['data-toggle'] ?? null) ?: 'popover';
        $extra['data-container'] = ($extra['data-container'] ?? null) ?: 'body';
        $extra['data-html'] = ($extra['data-html'] ?? null) ?: 'true';
        return ( ! ($extra['no_nbsp'] ?? null) ? ' ' : '') . '<span' . _attrs($extra, ['id', 'class', 'style']) . '><i class="' . ($extra['icon'] ?? null) . '"></i></span>';
    }

tree

public function tree($data = [], $extra = [])
    public function tree($data = [], $extra = [])
    {
        return _class('html_tree', 'classes/html/')->tree($data, $extra);
    }

well

public function well($body = '', $extra = [])
    public function well($body = '', $extra = [])
    {
        if ($this->_is_bs5) {
            return $this->_bs5()->well($body, $extra);
        }
        $extra['id'] = ($extra['id'] ?? null) ?: __FUNCTION__ . '_' . ($this->_ids[__FUNCTION__] = ($this->_ids[__FUNCTION__] ?? 0) + 1);
        if ( ! ($extra['class'] ?? null)) {
            $extra['class'] = 'well-lg';
        }
        return '<div class="well well-lg' . (($extra['class'] ?? null) ? ' ' . ($extra['class'] ?? null) : '') . '" id="' . ($extra['id'] ?? null) . '">' . $body . '</div>';
    }

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);
    }

__clone

public function __clone()

We cleanup object properties when cloning.

    public function __clone()
    {
        $keep_prefix = 'CLASS_';
        $keep_len = strlen($keep_prefix);
        $keep_prefix2 = 'CONF_';
        $keep_len2 = strlen($keep_prefix2);
        foreach ((array) get_object_vars($this) as $k => $v) {
            if (substr($k, 0, $keep_len) === $keep_prefix) {
                continue;
            }
            if (substr($k, 0, $keep_len2) === $keep_prefix2) {
                continue;
            }
            if ($k[0] == '_') {
                $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 '';
        }
    }

_b4_attrs

public function _b4_attrs($a)

Attributes string of $a (name => value, plus `attr`), template braces escaped; empty values are left out.

  • $a array
    public function _b4_attrs($a)
    {
        $a = array_filter((array) $a, fn ($v) => $v !== null && $v !== '' && $v !== false);
        return _attrs(_escape_tpl_braces($a), array_diff(array_keys($a), ['attr']));
    }

_b4_pass

public function _b4_pass($extra)

The data-* and `attr` (extra attributes) options of $extra, to add to a component's own attributes.

    public function _b4_pass($extra)
    {
        $a = [];
        foreach ((array) $extra as $k => $v) {
            is_string($k) && str_starts_with($k, 'data-') && $a[$k] = $v;
        }
        is_array($extra['attr'] ?? null) && $a['attr'] = $extra['attr'];
        return $a;
    }

_b4_text

public function _b4_text($text, $extra = [])

Text of a component, escaped; with `no_escape` as it is (developer-supplied HTML, never request data).

    public function _b4_text($text, $extra = [])
    {
        $text = (string) $text;
        return ($extra['no_escape'] ?? null) ? $text : _prepare_html($text, 0);
    }

_bs5

public function _bs5()

Bootstrap 5 markup of the widgets, bound to this instance (id counters, class properties).

    public function _bs5()
    {
        $bs5 = _class('html_bs5', 'classes/html/');
        $bs5->_parent = $this;
        return $bs5;
    }
public function _bs5_btn_class($class, $add = '')

Bootstrap 5 button class list $class . ' ' . $add: Bootstrap 3 put a colour over btn-default (btn-default btn-success), Bootstrap 5 has no such base colour, so the neutral colour (CLASS_BS5_BTN_NEUTRAL) leaves $class when $add brings another button colour. Only $class, the base list, loses it: a neutral class in $add stays. The base cannot tell the framework default (CLASS_BTN_MINI) from a site class that happens to be btn-outline-secondary, so that one goes next to a colour from $add too.

  • $class string
  • $add string
    public function _bs5_btn_class($class, $add = '')
    {
        $class = (string) $class;
        $add = (string) $add;
        $neutral = $this->CLASS_BS5_BTN_NEUTRAL;
        $tokens = preg_split('~\s+~', trim($class), -1, PREG_SPLIT_NO_EMPTY);
        if (in_array($neutral, $tokens, true) && $this->_bs5_btn_colors($add)) {
            $class = implode(' ', array_diff($tokens, [$neutral]));
        }
        return $add === '' ? $class : $class . ' ' . $add;
    }
public function _bs5_btn_colors($class)

Button colour classes (btn-primary, btn-outline-danger, ...) of a class list.

  • $class string
    public function _bs5_btn_colors($class)
    {
        $tokens = preg_split('~\s+~', trim((string) $class), -1, PREG_SPLIT_NO_EMPTY);
        return array_values(preg_grep('~^btn-(outline-)?(primary|secondary|success|danger|warning|info|light|dark|link)$~', $tokens));
    }

_bs5_btn_html

public function _bs5_btn_html($data)

The class attributes of framework HTML (a string or the strings of an array, such as the pair_* data handlers) with Bootstrap 5 button classes: each whole token of BS5_BTN_TOKENS is replaced in place (btn-default only when no other colour is there, a repeated result such as btn-sm once); lists without such a token stay as they are.

    public function _bs5_btn_html($data)
    {
        if (is_array($data)) {
            foreach ($data as $k => $v) {
                is_string($v) && $data[$k] = $this->_bs5_btn_html($v);
            }
            return $data;
        }
        return preg_replace_callback('~(\sclass=)(["\'])(.*?)\2~', function ($m) {
            $tokens = preg_split('~\s+~', trim($m[3]), -1, PREG_SPLIT_NO_EMPTY);
            if ( ! array_intersect($tokens, array_keys($this->BS5_BTN_TOKENS))) {
                return $m[0];
            }
            $has_color = (bool) $this->_bs5_btn_colors($m[3]);
            $out = [];
            foreach ($tokens as $t) {
                if (isset($this->BS5_BTN_TOKENS[$t])) {
                    $t = $this->BS5_BTN_TOKENS[$t];
                    if ($t === '' || ($t === $this->CLASS_BS5_BTN_NEUTRAL && $has_color) || in_array($t, $out, true)) {
                        continue;
                    }
                }
                $out[] = $t;
            }
            return $m[1] . $m[2] . implode(' ', $out) . $m[2];
        }, (string) $data);
    }
public function _bs5_check_input_attr(&$add_str = '')

` class="form-check-input"` for a radio / checkbox input under Bootstrap 5, '' otherwise; when $add_str already has a class attribute, '' and the class is merged into $add_str instead (two class attributes: the browser keeps only the first).

  • $add_str string
    public function _bs5_check_input_attr(&$add_str = '')
    {
        if ( ! $this->_is_bs5) {
            return '';
        }
        $add_str = (string) $add_str;
        if (preg_match('~(?:^|\s)class=(["\'])(.*?)\1~i', $add_str, $m)) {
            if ( ! in_array($this->CLASS_BS5_CHECK_INPUT, preg_split('~\s+~', $m[2]), true)) {
                $add_str = str_replace($m[0], substr($m[0], 0, -strlen($m[2]) - 1) . trim($m[2] . ' ' . $this->CLASS_BS5_CHECK_INPUT) . $m[1], $add_str);
            }
            return '';
        }
        return ' class="' . $this->CLASS_BS5_CHECK_INPUT . '"';
    }
public function _bs5_switch_classes($obj, $is_bs5)

Switches the properties of $obj->BS5_CLASSES (property => [bs3 default, Bootstrap 5 value]) to their Bootstrap 5 value when $is_bs5 and back when not. Used by yf_html, yf_form2 and yf_table2; $obj->_bs5_switched remembers what was switched.

  • $obj object
  • $is_bs5 bool
    public function _bs5_switch_classes($obj, $is_bs5)
    {
        foreach ((array) $obj->BS5_CLASSES as $prop => [$bs3, $bs5]) {
            if ($is_bs5 && $obj->$prop === $bs3) {
                $obj->$prop = $bs5;
                $obj->_bs5_switched[$prop] = true;
            } elseif ( ! $is_bs5 && isset($obj->_bs5_switched[$prop])) {
                // only a value set here goes back: a site that chose `form-select` under bs3 keeps it
                $obj->$prop === $bs5 && $obj->$prop = $bs3;
                unset($obj->_bs5_switched[$prop]);
            }
        }
    }

_btn_class

public function _btn_class($extra = [])

Button classes of button() and the components built on it: `type` (colour, `link`), `outline`, `size` (sm / xs / lg). Bootstrap 4 has the Bootstrap 5 ones; Bootstrap 3: an outline button is btn-default, sm is btn-xs (as the framework's mini buttons).

    public function _btn_class($extra = [])
    {
        $extra = (array) $extra;
        $type = $this->_color_type($extra['type'] ?? '', '');
        $outline = ! empty($extra['outline']);
        $bs45 = $this->_is_bs5 || $this->_is_bs4();
        if (strtolower((string) ($extra['type'] ?? '')) === 'link') {
            $color = 'btn-link';
        } elseif ($bs45) {
            $color = $type === '' ? $this->CLASS_BS5_BTN_NEUTRAL : 'btn-' . ($outline ? 'outline-' : '') . $type;
        } else {
            $color = ($type === '' || $outline) ? 'btn-default' : 'btn-' . $type;
        }
        $size = ['sm' => ['btn-xs', 'btn-sm'], 'xs' => ['btn-xs', 'btn-sm'], 'lg' => ['btn-lg', 'btn-lg']][$extra['size'] ?? ''] ?? null;
        return 'btn ' . $color . ($size ? ' ' . $size[(int) $bs45] : '');
    }

_color_type

public function _color_type($type, $default = '')

Colour name of the components (badge, button, toast, spinner...): primary, secondary, success, danger, warning, info, light, dark (error / important = danger, default = secondary), else $default; Bootstrap 3 has no secondary / light / dark (`default`).

  • $default string
    public function _color_type($type, $default = '')
    {
        $type = strtolower(trim((string) $type));
        $type = ['error' => 'danger', 'important' => 'danger', 'default' => 'secondary', 'inverse' => 'dark'][$type] ?? $type;
        if ( ! in_array($type, ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark'], true)) {
            $type = $default;
        }
        return ($this->_is_bs5 || $this->_is_bs4() || ! in_array($type, ['secondary', 'light', 'dark'], true)) ? $type : 'default';
    }

_count_levels

public function _count_levels(&$children, $start_id = 0, $level = 0)
    public function _count_levels(&$children, $start_id = 0, $level = 0)
    {
        $ids = [];
        foreach ((array) $children[$start_id] as $id => $_tmp) {
            $ids[$id] = $level;
            if (isset($children[$id])) {
                foreach ((array) $this->_count_levels($children, $id, $level + 1) as $_id => $_level) {
                    $ids[$_id] = $_level;
                }
            }
        }
        return $ids;
    }

_css_length

public function _css_length($v)

CSS length of a `height` option: a positive int (or digits) is px, a number with px / rem / em / % stays; anything else is ''.

    public function _css_length($v)
    {
        if (is_int($v) || (is_string($v) && ctype_digit($v))) {
            return (int) $v > 0 && (int) $v < 10000 ? (int) $v . 'px' : '';
        }
        $v = trim((string) $v);
        return preg_match('~^\d{1,4}(?:\.\d{1,3})?(?:px|rem|em|%)$~D', $v) ? $v : '';
    }
public function _get_country_name($code)
    public function _get_country_name($code)
    {
        if ( ! isset($this->_country_names)) {
            $this->_country_names = db()->select('code', 'name')->from('geo_countries')->get_2d();
        }
        return $this->_country_names[$code];
    }
public function _get_ip_country($ip)
    public function _get_ip_country($ip)
    {
        if ( ! isset($this->_ip_to_country[$ip])) {
            $code = _class('api')->_geoip_country_code_by_name($ip);
            $this->_ip_to_country[$ip] = $code;
        }
        return $this->_ip_to_country[$ip];
    }

_img_alt

public function _img_alt($extra)

` alt="..."` of the card image (`img_alt`, escaped; empty for a decorative image).

  • $extra array
    public function _img_alt($extra)
    {
        return ' alt="' . _htmlchars(_escape_tpl_braces((string) ($extra['img_alt'] ?? ''))) . '"';
    }

_init

public function _init()
    public function _init()
    {
        $css_framework = (string) conf('css_framework');
        $this->_is_bs3 = ($css_framework == 'bs3') || ($this->BS4_AS_BS3 && in_array($css_framework, ['bs4', 'empty', ''], true));
        $this->_is_bs5 = ($css_framework == 'bs5');
        $this->_bs5_switch_classes($this, $this->_is_bs5);
    }

_is_bs4

public function _is_bs4()

css_framework bs4: the components use Bootstrap 4 classes where they differ from Bootstrap 3 (badge, button, collapse, dropdown, ratio), as table2 does for its row variants; the others get the Bootstrap 3 markup.

    public function _is_bs4()
    {
        return ! $this->_is_bs5 && conf('css_framework') === 'bs4';
    }
public function _list_group_item_state($v, $extra, $fw)

B5 options of one list_group() item: null without them (the old markup), else ['link' => safe URL or null, 'class' => classes to add, 'attrs' => aria attributes]. `link` (trusted URL; javascript: / data: / vbscript: become #), `active`, `disabled` (a disabled link has no href), `variant` (item colour: list-group-item-<colour>; Bootstrap 3 has success, info, warning, danger). `type` stays ignored, as it always was. The item keys count only with B5_ITEM_OPTIONS; a `variant` of $extra always. $fw: bs5, bs4 (list-group-item-action on links) or bs3.

  • $v array
  • $extra array
  • $fw string
    public function _list_group_item_state($v, $extra, $fw)
    {
        $v = $this->B5_ITEM_OPTIONS ? (array) $v : [];
        $link = (string) ($v['link'] ?? '');
        $active = ! empty($v['active']);
        $disabled = ! empty($v['disabled']);
        $variant = $this->_color_type(($v['variant'] ?? null) ?: ($extra['variant'] ?? ''), '');
        if ($fw === 'bs3' && ! in_array($variant, ['success', 'info', 'warning', 'danger'], true)) {
            $variant = '';
        }
        if ( ! strlen($link) && ! $active && ! $disabled && $variant === '') {
            return null;
        }
        $is_link = strlen($link) > 0;
        $class = ($is_link && $fw !== 'bs3' ? ' list-group-item-action' : '') . ($variant !== '' ? ' list-group-item-' . $variant : '') . ($active ? ' active' : '') . ($disabled ? ' disabled' : '');
        return [
            'link' => $is_link ? ($disabled ? '' : $this->_safe_href($link)) : null,
            'class' => $class,
            'attrs' => ['aria-current' => $active ? 'true' : '', 'aria-disabled' => $disabled ? 'true' : ''],
        ];
    }
public function _modal_options_bs3($extra)

B5 modal() options under Bootstrap 3 / 4: [classes added to .modal, classes added to .modal-dialog, attributes]. size sm / lg (xl is lg under Bootstrap 3), fade, static (backdrop and Escape do not close it); Bootstrap 4 also xl, centered, scrollable. fullscreen and theme are Bootstrap 5 only.

  • $extra array
    public function _modal_options_bs3($extra)
    {
        $bs4 = $this->_is_bs4();
        $sizes = ['sm' => 'modal-sm', 'lg' => 'modal-lg', 'xl' => $bs4 ? 'modal-xl' : 'modal-lg'];
        $dialog = [$sizes[$extra['size'] ?? ''] ?? ''];
        if ($bs4) {
            ! empty($extra['centered']) && $dialog[] = 'modal-dialog-centered';
            ! empty($extra['scrollable']) && $dialog[] = 'modal-dialog-scrollable';
        }
        $dialog = trim(implode(' ', $dialog));
        return [
            // an inline modal is shown without the JS that would add `in`
            ! empty($extra['fade']) && empty($extra['inline']) ? ' fade' : '',
            strlen($dialog) ? ' ' . $dialog : '',
            ! empty($extra['static']) ? ' data-backdrop="static" data-keyboard="false"' : '',
        ];
    }
public function _pagination_classes($size, $align, $fw)

Classes of the pagination `size` (sm, lg) and `align` (center, end) options: [classes of the pagination list, classes of its wrapper]. $fw: bs5 (justify-content-* on the list), bs3 (text-center / text-right on the wrapper, the list is inline-block) or bs2 (pagination-small / -large / -centered / -right on div.pagination). Other values give no class.

  • $size string
  • $align string
  • $fw string
    public function _pagination_classes($size, $align, $fw)
    {
        $size = in_array($size, ['sm', 'lg'], true) ? $size : '';
        $align = in_array($align, ['center', 'end'], true) ? $align : '';
        $list = [];
        $wrap = '';
        if ($fw === 'bs2') {
            $size && $list[] = $size === 'sm' ? 'pagination-small' : 'pagination-large';
            $align && $list[] = $align === 'center' ? 'pagination-centered' : 'pagination-right';
        } else {
            $size && $list[] = 'pagination-' . $size;
            if ($fw === 'bs5') {
                $align && $list[] = 'justify-content-' . $align;
            } elseif ($align) {
                $wrap = $align === 'center' ? 'text-center' : 'text-right';
            }
        }
        return [implode(' ', $list), $wrap];
    }
public function _pagination_item($key, $link, $text, $extra)

One pagination() item (Bootstrap 2 / 3 markup; yf_html_bs5 has its own): a link, the current page (active) or a disabled entry.

  • $key int|string
  • $link string
  • $text string
  • $extra array
    public function _pagination_item($key, $link, $text, $extra)
    {
        $state = $this->_pagination_state($key, $extra);
        if ($state === 'disabled') {
            return '<li class="disabled"><span>' . $text . '</span></li>';
        }
        if ($state === 'active') {
            return '<li class="active"><a href="' . $link . '" aria-current="page">' . $text . '</a></li>';
        }
        return '<li><a href="' . $link . '">' . $text . '</a></li>';
    }
public function _pagination_state($key, $extra)

State of a pagination() item: 'disabled' for a key listed in $extra['disabled'] (page keys, 'prev', 'next'), 'active' for the key in $extra['current'], '' otherwise.

  • $key int|string
  • $extra array
    public function _pagination_state($key, $extra)
    {
        if (in_array((string) $key, array_map('strval', (array) ($extra['disabled'] ?? [])), true)) {
            return 'disabled';
        }
        return (isset($extra['current']) && (string) $key === (string) $extra['current']) ? 'active' : '';
    }
public function _progress_bar_item($v, $extra)

[striped, animated, label (escaped unless no_escape)] of one progress_bar() bar: the bar keys only with B5_ITEM_OPTIONS, the striped / animated options of $extra always.

  • $v array
  • $extra array
    public function _progress_bar_item($v, $extra)
    {
        $v = $this->B5_ITEM_OPTIONS ? (array) $v : [];
        return [$v['striped'] ?? ($extra['striped'] ?? null), $v['animated'] ?? ($extra['animated'] ?? null), isset($v['label']) ? $this->_b4_text($v['label'], $v) : ''];
    }
public function _recursive_sort_items($items = [], $skip_item_id = 0, $parent_id = 0)

Get and sort items ordered array (recursively).

    public function _recursive_sort_items($items = [], $skip_item_id = 0, $parent_id = 0)
    {
        $children = [];
        foreach ((array) $items as $id => $info) {
            $parent_id = (int) $info['parent_id'];
            if ($skip_item_id == $id) {
                continue;
            }
            $children[$parent_id][$id] = $id;
        }
        $ids = $this->_count_levels($children, 0);
        $new_items = [];
        foreach ((array) $ids as $id => $level) {
            $new_items[$id] = $items[$id] + ['level' => $level];
        }
        return $new_items;
    }

_safe_href

public function _safe_href($url)

$url, or '#' when it is a javascript:, data: or vbscript: URL (entities decoded, whitespace and control characters dropped, as browsers read the scheme). The link options take trusted URLs; this only keeps a script URL out of them.

    public function _safe_href($url)
    {
        $url = (string) $url;
        $scheme = preg_replace('~[\x00-\x20\x7f]+~', '', html_entity_decode($url, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
        return preg_match('~^(?:javascript|data|vbscript):~i', $scheme) ? '#' : $url;
    }

_sync_fw

public function _sync_fw()

conf can change after _init() (demo and test pages); the components follow it as a() does.

    public function _sync_fw()
    {
        (conf('css_framework') === 'bs5') !== (bool) $this->_is_bs5 && $this->_init();
    }
public function _tabs_nav_options($extra, $fw)

B5 tabs() options of the whole nav: type (tabs, pills, underline), fill, justified, vertical. Returns [nav classes, data-toggle value, vertical]. $fw bs5: nav-pills / nav-underline / nav-fill / nav-justified / flex-column; Bootstrap 3 / 4: underline is tabs, fill is nav-justified (not on a vertical nav), vertical is nav-stacked in a col-sm-3 next to the panes.

  • $extra array
  • $fw string
    public function _tabs_nav_options($extra, $fw)
    {
        $type = in_array($extra['type'] ?? '', ['pills', 'underline'], true) ? $extra['type'] : 'tabs';
        $vertical = ! empty($extra['vertical']);
        if ($fw === 'bs5') {
            $class = ['nav', 'nav-' . $type, ! empty($extra['fill']) ? 'nav-fill' : '', ! empty($extra['justified']) ? 'nav-justified' : '', $vertical ? 'flex-column' : ''];
        } else {
            // Bootstrap 3 nav-justified lays the items out in one row, which a stacked nav cannot have
            $justified = ! $vertical && ( ! empty($extra['fill']) || ! empty($extra['justified']));
            $class = ['nav', $type === 'pills' ? 'nav-pills' : 'nav-tabs', $justified ? 'nav-justified' : '', $vertical ? 'nav-stacked' : ''];
        }
        return [implode(' ', array_filter($class)), $type === 'pills' ? 'pill' : 'tab', $vertical];
    }

_theme_attr

public function _theme_attr($theme)

` data-bs-theme="light|dark"` of the B5 `theme` option (colour mode of one widget) under bs5; '' otherwise and for other values.

    public function _theme_attr($theme)
    {
        return ($this->_is_bs5 && in_array($theme, ['light', 'dark'], true)) ? ' data-bs-theme="' . $theme . '"' : '';
    }