yf_form2 class

Form2 high-level generator and handler, mostly using bootstrap html/css framework.

Fileplugins/form2/classes/yf_form2.class.php
Use_class('form2')

Public properties

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

PropertyDefaultDescription
$BS5_CLASSES
array
['CLASS_TPL_BADGE' => ['badge badge-%name', 'badge text-bg-%name'], 'CLASS_TPL_LABEL' => ['label label-%name', 'badge text-bg-%name'], 'CLASS_BTN_MINI' => ['btn btn-default btn-mini btn-xs', 'btn btn-outline-secondary btn-sm'], 'CLASS_BTN_DEFAULT' => ['btn btn-default', 'btn btn-outline-secondary'], 'CLASS_BTN_SUBMIT' => ['btn btn-default btn-primary', 'btn btn-primary'], 'CLASS_LABEL_INFO' => ['label label-info', 'badge text-bg-info'], ... (11 items)]
array (
  'CLASS_TPL_BADGE' => 
  array (
    0 => 'badge badge-%name',
    1 => 'badge text-bg-%name',
  ),
  'CLASS_TPL_LABEL' => 
  array (
    0 => 'label label-%name',
    1 => 'badge text-bg-%name',
  ),
  'CLASS_BTN_MINI' => 
  array (
    0 => 'btn btn-default btn-mini btn-xs',
    1 => 'btn btn-outline-secondary btn-sm',
  ),
  'CLASS_BTN_DEFAULT' => 
  array (
    0 => 'btn btn-default',
    1 => 'btn btn-outline-secondary',
  ),
  'CLASS_BTN_SUBMIT' => 
  array (
    0 => 'btn btn-default btn-primary',
    1 => 'btn btn-primary',
  ),
  'CLASS_LABEL_INFO' => 
  array (
    0 => 'label label-info',
    1 => 'badge text-bg-info',
  ),
  'CLASS_ERROR' => 
  array (
    0 => 'alert alert-error alert-danger',
    1 => 'alert alert-danger alert-dismissible',
  ),
  'CLASS_TPL_INPUT_SIZE' => 
  array (
    0 => 'input-%size',
    1 => 'form-control-%size',
  ),
  'CLASS_TPL_SELECT_SIZE' => 
  array (
    0 => 'input-%size',
    1 => 'form-select-%size',
  ),
  'CLASS_RANGE_VALUE_WRAP' => 
  array (
    0 => 'input-group',
    1 => 'd-flex align-items-center gap-2',
  ),
  'CLASS_RANGE_VALUE' => 
  array (
    0 => 'input-group-addon',
    1 => 'badge text-bg-secondary',
  ),
)
property => [bs3 default, Bootstrap 5 value]: switched as in yf_html when the form renders under css_framework bs5
$BS5_COLORS
array
['default' => 'secondary', 'important' => 'danger', 'error' => 'danger', 'inverse' => 'dark'] Bootstrap 2 / 3 badge and label colour names => Bootstrap 5 colour, for the `badge` / `label` options under bs5
$CLASS_BS5_BTN_CHECK
string
'btn btn-outline-secondary btn-sm' label of btn_group radio / check boxes under bs5 (input.btn-check + label.btn)
$CLASS_BS5_COLOR 'form-control form-control-color'
$CLASS_BS5_RANGE
string
'form-range' range() / color() inputs under bs5 (Bootstrap 3 keeps CLASS_FORM_CONTROL)
$CLASS_BTN_DEFAULT 'btn btn-default'
$CLASS_BTN_MINI 'btn btn-default btn-mini btn-xs'
$CLASS_BTN_SUBMIT 'btn btn-default btn-primary'
$CLASS_CKEDITOR 'ckeditor'
$CLASS_ERROR 'alert alert-error alert-danger'
$CLASS_FORM_CONTROL 'form-control'
$CLASS_FORM_MAIN 'form-horizontal'
$CLASS_ICON_CALENDAR 'icon icon-calendar fa fa-calendar fa-fw'
$CLASS_ICON_CURRENCY 'icon-dollar fa fa-dollar fa-fw'
$CLASS_ICON_EMAIL 'icon-email fa fa-at fa-fw'
$CLASS_ICON_LOGIN 'icon-user fa fa-user fa-fw'
$CLASS_ICON_PREVIEW 'icon-eye fa fa-eye'
$CLASS_ICON_PSWD 'icon-key fa fa-key fa-fw'
$CLASS_ICON_SAVE 'icon-save fa fa-save'
$CLASS_LABEL_INFO 'label label-info'
$CLASS_RANGE_VALUE 'input-group-addon'
$CLASS_RANGE_VALUE_WRAP
string
'input-group' wrapper of a range() with `show_value` and the class of its output element
$CLASS_REQUIRED 'control-group-required form-group-required'
$CLASS_STACKED_ROW 'stacked-row'
$CLASS_TPL_BADGE 'badge badge-%name'
$CLASS_TPL_INPUT_SIZE
string
'input-%size' size class of input(); %size is the `sizing` option (sm, lg)
$CLASS_TPL_LABEL 'label label-%name'
$CLASS_TPL_SELECT_SIZE
string
'input-%size' size class of select_box() / multi_select(); %size is the `sizing` option (sm, lg)
$CONF_BOXES_USE_BTN_GROUP false
$CONF_CSRF_NAME '_token'
$CONF_CSRF_PROTECTION true
$CONF_FLOATING_SKIP_TYPES
array
['range', 'color', 'file', 'image', 'checkbox', 'radio', ... (10 items)]
array (
  0 => 'range',
  1 => 'color',
  2 => 'file',
  3 => 'image',
  4 => 'checkbox',
  5 => 'radio',
  6 => 'button',
  7 => 'submit',
  8 => 'reset',
  9 => 'hidden',
)
input() types the form-level `floating` option leaves with a normal label (Bootstrap 5 floats only text-like controls)
$CONF_FORM_AUTOID_PREFIX 'form_autoid_'
$CONF_FORM_ID_FIELD '__form_id__'
$CONF_NO_DB_WRITES
bool
false db_insert_if_ok() / db_update_if_ok() validate and run their callbacks and events but write nothing (demo sites); default false writes as before ($PROJECT_CONF['form2']['CONF_NO_DB_WRITES'])
$CONF_SIZING_ON_SELECTS_ALL_FW
bool
false an explicit `sizing` on select_box() / multi_select() / textarea() sizes them under bs3 / bs4 / empty too (it is ignored there by default; bs5 always applies it)
$SKIP_ON_POST_ON_CSRF_FAIL
bool
false SECURITY: on_post is not called when this form's CSRF token check failed (the error display stays the same); default false calls it before validation as before (4.0 flips)
$_body []
$_bs5_switched
array
[] properties of BS5_CLASSES switched to their bs5 value
$_chained_mode null
$_db_change_if_ok null
$_extend []
$_extra null
$_fieldset_mode_on false
$_form_id null
$_isset_hidden_form_id null
$_isset_hidden_token null
$_on []
$_pair_allow_deny null
$_pair_yes_no null
$_params []
$_rendered null
$_replace []
$_sql null
$_stacked_mode_on null
$_tabbed_mode_on null
$_tabs_counter 0
$_tabs_extra null
$_tabs_name null
$_validate null
$_validate_rules []
$_validate_rules_names []

Public methods 174

active_box

public function active_box($name = '', $desc = '', $extra = [], $replace = [])
    public function active_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        if (is_array($name)) {
            $extra += $name;
            $desc = '';
        }
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['name'] = ($extra['name'] ?? null) ?: ($name ?: 'active');
        $extra['desc'] = $this->_prepare_desc($extra, $desc);
        $func = function ($extra, $r, $form) {
            $form->_prepare_inline_error($extra);
            $as_btn_group = isset($extra['btn_group']) ? $extra['btn_group'] : $form->CONF_BOXES_USE_BTN_GROUP;
            // Bootstrap 5 has no data-toggle="buttons": the group is input.btn-check + label.btn (yf_html_bs5::button_radio_box())
            $bs5_btn_group = $as_btn_group && $form->_is_bs5();
            if ($bs5_btn_group) {
                $extra['label_extra']['class'] = $extra['label_extra']['class'] ?? $form->CLASS_BS5_BTN_CHECK;
            } elseif ($as_btn_group) {
                $extra['class_add_controls'] = 'btn-group';
                $extra['controls']['data-toggle'] = 'buttons';
            }
            if ( ! ($extra['items'] ?? null)) {
                $data_handler = $as_btn_group ? 'pair_active_btn_group' : 'pair_active';
                $extra['items'] = $form->_bs5_pair(main()->get_data($data_handler));
            }
            if ($bs5_btn_group) {
                $extra['items'] = $form->_bs5_btn_check_items($extra['items']);
            }
            $extra['values'] = $extra['items'];
            $extra['desc'] = ! ($form->_params['no_label'] ?? null) ? $extra['desc'] : '';
            $extra['id'] = $form->_prepare_id($extra);
            if ( ! isset($extra['horizontal'])) {
                $extra['horizontal'] = true;
            }
            $extra['selected'] = isset($extra['selected']) ? $extra['selected'] : $r[$extra['name']];
            if (isset($form->_params['selected'])) {
                $extra['selected'] = $form->_params['selected'][$extra['name']];
            }
            $extra = $form->_input_assign_params_from_validate($extra);
            if (($this->_params['filter'] ?? null) && ! ($extra['renderer'] ?? null)) {
                $extra['no_label'] = 1;
                $extra['label_right'] = 1;
                $extra['renderer'] = 'button_yes_no_box';
            }
            $renderer = ($extra['renderer'] ?? null) ?: ($bs5_btn_group ? 'button_radio_box' : 'radio_box');
            return $form->_row_html(html()->$renderer($extra), $extra, $r);
        };
        if ($this->_chained_mode) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }

admin_info

public function admin_info($name = '', $desc = '', $extra = [], $replace = [])
    public function admin_info($name = '', $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_info', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }
public function admin_location_box($name = '', $desc = '', $extra = [], $replace = [])
    public function admin_location_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['for_type'] = 'admin';
        return $this->location_select_box($name, $desc, $extra, $replace);
    }
public function admin_method_box($name = '', $desc = '', $extra = [], $replace = [])
    public function admin_method_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['for_type'] = 'admin';
        return $this->method_select_box($name, $desc, $extra, $replace);
    }
public function admin_template_box($name = '', $desc = '', $extra = [], $replace = [])
    public function admin_template_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['for_type'] = 'admin';
        return $this->template_select_box($name, $desc, $extra, $replace);
    }
public function allow_deny_box($name = '', $desc = '', $extra = [], $replace = [])
    public function allow_deny_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if ( ! isset($this->_pair_allow_deny)) {
            $this->_pair_allow_deny = $this->_bs5_pair(main()->get_data('pair_allow_deny'));
        }
        $extra['items'] = $this->_pair_allow_deny;
        if (($this->_params['filter'] ?? null) || ($extra['v2'] ?? null)) {
            $extra['no_label'] = 1;
            $extra['label_right'] = 1;
            $extra['renderer'] = 'button_allow_deny_box';
        }
        $func = 'active_box';
        return $this->$func($name, $desc, $extra, $replace);
    }

array_to_form

public function array_to_form($a = [], $params = [], $replace = [])
    public function array_to_form($a = [], $params = [], $replace = [])
    {
        $this->_params = $params + (array) $this->_params;
        $this->_replace = $replace + (array) $this->_replace;
        // Example of row: ['text', 'login', ['class' => 'input-medium']]
        foreach ((array) $a as $v) {
            $func = '';
            if (is_string($v)) {
                $func = $v;
                $v = [];
            } elseif (is_array($v)) {
                $func = $v[0];
            }
            if ( ! $func || ! method_exists($this, $func)) {
                continue;
            }
            $this->$func(($v[1] ?? null), ($v[2] ?? null), ($v[3] ?? null), ($v[4] ?? null), ($v[5] ?? null));
        }
        return $this;
    }

auto

public function auto($table = '', $id = '', $params = [])

Enable automatic fields parsing mode.

    public function auto($table = '', $id = '', $params = [])
    {
        return _class('form2_auto', 'classes/form2/')->auto($table, $id, $params, $this);
    }

birth_box

public function birth_box($name = '', $values = [], $extra = [], $replace = [])
    public function birth_box($name = '', $values = [], $extra = [], $replace = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if ( ! $name) {
            $name = 'birth';
        }
        return $this->date_box($name, $values, $extra, $replace);
    }

box

public function box($name, $desc = '', $extra = [], $replace = [])
    public function box($name, $desc = '', $extra = [], $replace = [])
    {
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['name'] = ($extra['name'] ?? null) ?: $name;
        $extra['desc'] = $this->_prepare_desc($extra, $desc);
        $func = function ($extra, $r, $form) {
            $form->_prepare_inline_error($extra);
            $extra['edit_link'] = $extra['edit_link'] ? (isset($r[$extra['edit_link']]) ? $r[$extra['edit_link']] : $extra['edit_link']) : '';
            $extra['values'] = isset($extra['values']) ? $extra['values'] : []; // Required
            $extra['selected'] = $form->_prepare_selected($extra['name'], $extra, $r);
            $extra['id'] = $form->_prepare_id($extra);
            $extra = $form->_input_assign_params_from_validate($extra);

            return $form->_row_html($r[$extra['name']], $extra, $r);
        };
        if ($this->_chained_mode) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }

button

public function button($name, $desc = '', $extra = [], $replace = [])
    public function button($name, $desc = '', $extra = [], $replace = [])
    {
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if ( ! $desc) {
            $desc = ucfirst(str_replace('_', ' ', (string) $name));
        }
        $extra['type'] = 'button';
        if ( ! isset($extra['value'])) {
            $extra['value'] = $desc;
        }
        $extra['value'] = t($extra['value']);
        if ( ! ($extra['class'] ?? null)) {
            $extra['class'] = $this->CLASS_BTN_DEFAULT;
        }
        return $this->input($name, $desc, $extra, $replace);
    }

button_box

public function button_box($name, $values, $extra = [], $replace = [])
    public function button_box($name, $values, $extra = [], $replace = [])
    {
        return $this->_html_control($name, $values, $extra, $replace, 'button_box');
    }
public function button_check_box($name, $values, $extra = [], $replace = [])
    public function button_check_box($name, $values, $extra = [], $replace = [])
    {
        return $this->_html_control($name, $values, $extra, $replace, 'button_check_box');
    }
public function button_radio_box($name, $values, $extra = [], $replace = [])
    public function button_radio_box($name, $values, $extra = [], $replace = [])
    {
        return $this->_html_control($name, $values, $extra, $replace, 'button_radio_box');
    }
public function button_split_box($name, $values, $extra = [], $replace = [])
    public function button_split_box($name, $values, $extra = [], $replace = [])
    {
        return $this->_html_control($name, $values, $extra, $replace, 'button_split_box');
    }
public function button_yes_no_box($name, $values, $extra = [], $replace = [])
    public function button_yes_no_box($name, $values, $extra = [], $replace = [])
    {
        return $this->_html_control($name, $values, $extra, $replace, 'button_yes_no_box');
    }

captcha

public function captcha($name = '', $desc = '', $extra = [], $replace = [])
    public function captcha($name = '', $desc = '', $extra = [], $replace = [])
    {
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['name'] = ($extra['name'] ?? null) ?: ($name ?: 'captcha');
        $extra['desc'] = $this->_prepare_desc($extra, $desc);
        $func = function ($extra, $r, $form) {
            $form->_prepare_inline_error($extra);
            $extra['id'] = $form->_prepare_id($extra);
            $extra['required'] = true;
            $extra['value'] = _escape_tpl_braces($r['captcha']);
            $extra['input_attrs'] = _attrs($extra, ['class', 'style', 'placeholder', 'pattern', 'disabled', 'required', 'autocomplete', 'accept', 'value', 'size']);
            $extra = $form->_input_assign_params_from_validate($extra);
            return $form->_row_html(_class('captcha')->show_block(url('/dynamic/captcha_image'), $extra), $extra, $r);
        };
        if ($this->_chained_mode) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }
public function chained_wrapper($replace = [], $params = [])

Wrapper for template engine Example: return form($replace) ->text('login','Login') ->text('password','Password') ->text('first_name','First Name') ->text('last_name','Last Name') ->text('go_after_login','Url after login') ->box_with_link('group_box','Group','groups_link') ->active('active','Active') ->info('add_date','Added');.

    public function chained_wrapper($replace = [], $params = [])
    {
        if ($replace && is_string($replace)) {
            $sql = $replace;
            $this->_sql = $sql;
            $db = is_object($params['db']) ? $params['db'] : db();
            $replace = $db->get_2d($sql);
        }
        if ( ! is_array($replace)) {
            $replace = [];
        }
        if (isset($params['filter']) && ! is_array($params['filter']) && is_numeric($params['filter']) || is_bool($params['filter'] ?? null) && ! empty($params['filter'])) {
            $filter_name = ($params['filter_name'] ?? null) ?: $_GET['object'] . '__' . $_GET['action'];
            $params['selected'] = $_SESSION[$filter_name];
            $replace['form_action'] = ($replace['form_action'] ?? null) ?: url('/@object/filter_save/' . $filter_name);
            $replace['clear_url'] = ($replace['clear_url'] ?? null) ?: url('/@object/filter_save/' . $filter_name . '/clear');
            if (MAIN_TYPE_ADMIN && ! isset($params['class'])) {
                $params['class'] = 'form-vertical';
            }
        }
        if ( ! ($params['no_chained_mode'] ?? null)) {
            $this->_chained_mode = true;
        }
        $this->_replace = $replace;
        $this->_params = $params;
        html()->_bs5_switch_classes($this, $this->_is_bs5());
        return $this;
    }

change_if_ok

public function change_if_ok($table, $fields, $type, $extra = [])

Alias.

    public function change_if_ok($table, $fields, $type, $extra = [])
    {
        return $this->_db_change_if_ok($table, $fields, $type, $extra);
    }

check_box

public function check_box($name, $value = '', $extra = [], $replace = [])
    public function check_box($name, $value = '', $extra = [], $replace = [])
    {
        if (is_array($value)) {
            $extra = (array) $extra + $value;
            $value = '';
        }
        $as_btn_group = isset($extra['btn_group']) ? $extra['btn_group'] : $this->CONF_BOXES_USE_BTN_GROUP;
        // `switch`: a Bootstrap 5 form-switch (yf_html::check_box()), a plain checkbox under Bootstrap 3, never a button
        if ($extra['switch'] ?? null) {
            $as_btn_group = false;
        }
        if ($as_btn_group && $this->_is_bs5()) {
            $extra['label_extra']['class'] = $extra['label_extra']['class'] ?? $this->CLASS_BS5_BTN_CHECK;
            return $this->_html_control($name, $value, $extra, $replace, 'button_check_box');
        }
        if ($as_btn_group) {
            $extra['class_add_controls'] = 'btn-group';
            $extra['controls']['data-toggle'] = 'buttons';
            $extra['class_add_label_checkbox'] = 'btn btn-xs btn-default';
            $extra['desc'] = '<span><i class="icon icon-check fa fa-check"></i></span> ' . $extra['desc'];
        }
        return $this->_html_control($name, $value, $extra, $replace, 'check_box');
    }

chosen_box

public function chosen_box($name, $values, $extra = [], $replace = [])
    public function chosen_box($name, $values, $extra = [], $replace = [])
    {
        return $this->_html_control($name, $values, $extra, $replace, 'chosen_box');
    }

city_box

public function city_box($name = '', $desc = '', $extra = [], $replace = [])
    public function city_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_boxes', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }

color

public function color($name = '', $desc = '', $extra = [], $replace = [])

HTML5.

    public function color($name = '', $desc = '', $extra = [], $replace = [])
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['type'] = 'color';
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if ( ! $name) {
            $name = 'color';
        }
        return $this->input($name, $desc, $extra, $replace);
    }

container

public function container($text, $desc = '', $extra = [], $replace = [])

Bootstrap-compatible html wrapper for any custom content inside. Can be used for inline rich editor editing with ckeditor, enable with: $extra = ['ckeditor' => true].

    public function container($text, $desc = '', $extra = [], $replace = [])
    {
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $text = (string) $text;
        $extra['text'] = $text;
        $extra['desc'] = $this->_prepare_desc($extra, $desc);

        $func = function ($extra, $r, $form) {
            $extra['edit_link'] = ($extra['edit_link'] ?? null) ? (isset($r[$extra['edit_link']]) ? $r[$extra['edit_link']] : ($extra['edit_link'] ?? null)) : '';
            $extra['contenteditable'] = isset($extra['ckeditor']) ? 'true' : 'false';
            $extra['id'] = $form->_prepare_id($extra, 'content_editable');
            $extra['desc'] = ! ($form->_params['no_label'] ?? null) ? ($extra['desc'] ?? null) : '';

            $attrs_names = ['id', 'contenteditable', 'style', 'class', 'title'];
            if ($extra['ckeditor'] ?? null) {
                $extra['ckeditor_inline'] = true;
            }
            return $form->_row_html(isset($extra['ckeditor']) ? '<div' . _attrs($extra, $attrs_names) . '>' . $extra['text'] . '</div>' : $extra['text'], $extra, $r);
        };
        if ($this->_chained_mode) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }

country_box

public function country_box($name = '', $desc = '', $extra = [], $replace = [])
    public function country_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_boxes', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }

currency_box

public function currency_box($name = '', $desc = '', $extra = [], $replace = [])
    public function currency_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_boxes', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }

custom_fields

public function custom_fields($name, $custom_fields, $extra = [], $replace = [])
    public function custom_fields($name, $custom_fields, $extra = [], $replace = [])
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['name'] = ($extra['name'] ?? null) ?: $name;
        $extra['custom_fields'] = $custom_fields;
        $func = function ($extra, $r, $form) {
            $custom_fields = explode(',', $extra['custom_fields']);
            $sub_array_name = ($extra['sub_array'] ?? null) ?: 'custom';
            $custom_info = _attrs_string2array($r[$extra['name']]);

            $body = [];
            $form->_chained_mode = false;
            foreach ((array) $custom_fields as $field_name) {
                if (empty($field_name)) {
                    continue;
                }
                $str = _class('html')->input([
                    'id' => 'custom_' . $field_name . '_' . $r['id'],
                    'name' => $sub_array_name . '[' . $field_name . ']', // Example: custom[color]
                    'desc' => $field_name,
                    'value' => $custom_info[$field_name],
                ]);
                $desc = ucfirst(str_replace('_', ' ', $field_name)) . ' [Custom]';
                $body[] = $form->container($str, $desc);
            }
            $form->_chained_mode = true;
            return implode(PHP_EOL, $body);
        };
        if ($this->_chained_mode) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }

date

public function date($name = '', $desc = '', $extra = [], $replace = [])

HTML5.

    public function date($name = '', $desc = '', $extra = [], $replace = [])
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['type'] = 'date';
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if ( ! $name) {
            $name = 'date';
        }
        return $this->input($name, $desc, $extra, $replace);
    }

date_box

public function date_box($name = '', $values = [], $extra = [], $replace = [])
    public function date_box($name = '', $values = [], $extra = [], $replace = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if ( ! $name) {
            $name = 'date';
        }
        return $this->_html_control($name, $values, $extra, $replace, 'date_box2');
    }

daterange

public function daterange($name = '', $desc = '', $extra = [], $replace = [])

Daterange picker (Alias).

    public function daterange($name = '', $desc = '', $extra = [], $replace = [])
    {
        return $this->daterange_select($name, $desc, $extra, $replace);
    }
public function daterange_select($name = '', $desc = '', $extra = [], $replace = [])

Daterange picker.

    public function daterange_select($name = '', $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_daterange', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }

datetime

public function datetime($name = '', $desc = '', $extra = [], $replace = [])

HTML5.

    public function datetime($name = '', $desc = '', $extra = [], $replace = [])
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['type'] = 'datetime';
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if ( ! $name) {
            $name = 'datetime';
        }
        return $this->input($name, $desc, $extra, $replace);
    }

datetime_box

public function datetime_box($name = '', $values = [], $extra = [], $replace = [])
    public function datetime_box($name = '', $values = [], $extra = [], $replace = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if ( ! $name) {
            $name = 'datetime';
        }
        if ( ! isset($extra['show_what'])) {
            $extra['show_what'] = 'ymdhis';
        }
        return $this->date_box($name, $values, $extra, $replace);
    }
public function datetime_local($name = '', $desc = '', $extra = [], $replace = [])

HTML5.

    public function datetime_local($name = '', $desc = '', $extra = [], $replace = [])
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['type'] = 'datetime-local';
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if ( ! $name) {
            $name = 'datetime_local';
        }
        return $this->input($name, $desc, $extra, $replace);
    }
public function datetime_select($name = '', $desc = '', $extra = [], $replace = [])

Datetimepicker, src: http://tarruda.github.io/bootstrap-datetimepicker/ params : no_date // no date picker no_time // no time picker.

    public function datetime_select($name = '', $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_datetime', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }
public function db_insert_if_ok($table, $fields, $add_fields = [], $extra = [])
    public function db_insert_if_ok($table, $fields, $add_fields = [], $extra = [])
    {
        $extra['add_fields'] = $add_fields;
        return $this->_db_change_if_ok($table, $fields, 'insert', $extra);
    }
public function db_update_if_ok($table, $fields, $where_id = null, $extra = [])
    public function db_update_if_ok($table, $fields, $where_id = null, $extra = [])
    {
        $extra['where_id'] = $where_id;
        return $this->_db_change_if_ok($table, $fields, 'update', $extra);
    }

decimal

public function decimal($name, $desc = '', $extra = [], $replace = [])
    public function decimal($name, $desc = '', $extra = [], $replace = [])
    {
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['step'] = ($extra['step'] ?? null) ?: '0.01';
        return $this->number($name, $desc, $extra, $replace);
    }

div_box

public function div_box($name, $values, $extra = [], $replace = [])
    public function div_box($name, $values, $extra = [], $replace = [])
    {
        return $this->_html_control($name, $values, $extra, $replace, 'div_box');
    }

email

public function email($name = '', $desc = '', $extra = [], $replace = [])

HTML5.

    public function email($name = '', $desc = '', $extra = [], $replace = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['type'] = 'email';
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if ( ! $name) {
            $name = 'email';
        }
        $extra['prepend'] = isset($extra['prepend']) ? $extra['prepend'] : '<i class="' . $this->CLASS_ICON_EMAIL . '"></i>';
        return $this->input($name, $desc, $extra, $replace);
    }

fieldset_end

public function fieldset_end($extra = [])

Paired with fieldset_start.

    public function fieldset_end($extra = [])
    {
        $func = function ($extra, $r, $form) {
            if ($form->_fieldset_mode_on) {
                $form->_fieldset_mode_on = false;
                return '</fieldset>' . PHP_EOL;
            }
        };
        $replace = [];
        if ($this->_chained_mode || $extra['chained_mode']) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }
public function fieldset_start($name = '', $extra = [])

Shortcut for adding fieldset.

    public function fieldset_start($name = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        $extra['name'] = ($extra['name'] ?? null) ?: $name;
        $func = function ($extra, $r, $form) {
            $body = '';
            if ($form->_fieldset_mode_on ?? false) {
                $body = '</fieldset>' . PHP_EOL;
            } else {
                $form->_fieldset_mode_on = true;
            }
            $body .= '<fieldset' . _attrs($extra, ['class', 'style', 'id', 'name']) . '>';
            if ($extra['legend'] ?? null) {
                $body .= PHP_EOL . '<legend>' . _htmlchars(t($extra['legend'])) . '</legend>' . PHP_EOL;
            }
            return $body;
        };
        $replace = [];
        if ($this->_chained_mode || ($extra['chained_mode'] ?? null)) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }

file

public function file($name, $desc = '', $extra = [], $replace = [])
    public function file($name, $desc = '', $extra = [], $replace = [])
    {
        $extra['type'] = 'file';
        $this->_params['for_upload'] = true;
        return $this->input($name, $desc, $extra, $replace);
    }

float

public function float($name, $desc = '', $extra = [], $replace = [])
    public function float($name, $desc = '', $extra = [], $replace = [])
    {
        return $this->decimal($name, $desc, $extra, $replace);
    }

form_begin

public function form_begin($name = '', $method = '', $extra = [], $replace = [])
    public function form_begin($name = '', $method = '', $extra = [], $replace = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        // Merge params passed to table2() and params passed here, with params here have more priority:
        $tmp = $this->_params;
        foreach ((array) $extra as $k => $v) {
            $tmp[$k] = $v;
        }
        $extra = $tmp;
        unset($tmp);

        $extra['name'] = ($extra['name'] ?? null) ?: ($name ?: 'form_action');
        $extra['method'] = ($extra['method'] ?? null) ?: ($method ?: 'post');

        $func = function ($extra, $r, $form) {
            $enctype = '';
            if ($extra['enctype'] ?? null) {
                $enctype = $extra['enctype'];
            } elseif ($extra['for_upload'] ?? null) {
                $enctype = 'multipart/form-data';
            }
            $extra['enctype'] = $enctype;
            if ( ! isset($extra['action'])) {
                $get_id = isset($_GET['id']) && strlen($_GET['id']) ? urlencode($_GET['id']) : '';
                $get_page = isset($_GET['page']) && strlen($_GET['page']) ? urlencode($_GET['page']) : '';
                $extra['action'] = isset($r[$extra['name'] ?? '']) ? $r[$extra['name'] ?? ''] : url('/@object/@action/' . $get_id . '/' . $get_page) . ($form->_params['links_add'] ?? null);
            }
            if (MAIN_TYPE_USER) {
                if (strpos($extra['action'], 'http://') === false && strpos($extra['action'], 'https://') !== 0) {
                    $extra['action'] = process_url($extra['action'], true);
                }
            }
            $extra['class'] = ($extra['class'] ?? null) ?: $form->CLASS_FORM_MAIN;// col-md-6';
            if ($extra['class_add'] ?? null) {
                $extra['class'] .= ' ' . $extra['class_add'];
            }
            $extra['autocomplete'] = isset($extra['autocomplete']) ? $extra['autocomplete'] : true;

            $advanced_js_validation = conf('form_advanced_js_validation');
            if ($advanced_js_validation) {
                $extra['data-fv-framework'] = 'bootstrap';
            }

            $body = '<form' . _attrs($extra, ['method', 'action', 'class', 'style', 'id', 'name', 'autocomplete', 'enctype', 'novalidate', 'target']) . '>' . PHP_EOL;
            $form->_fieldset_mode_on = true;
            $body .= '<fieldset' . _attrs($extra['fieldset'] ?? null, ['class', 'style', 'id', 'name']) . '>';
            if ($extra['legend'] ?? null) {
                $body .= PHP_EOL . '<legend>' . _htmlchars(t($extra['legend'])) . '</legend>' . PHP_EOL;
            }
            return $body;
        };
        if ($this->_chained_mode) {
            $this->_body[__FUNCTION__] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }

form_end

public function form_end($extra = [], $replace = [])
    public function form_end($extra = [], $replace = [])
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $func = function ($extra, $r, $form) {
            $form->_fieldset_mode_on = false;
            $body = '</fieldset>' . PHP_EOL;
            $body .= '</form>' . PHP_EOL;
            return $body;
        };
        if ($this->_chained_mode) {
            $this->_body[__FUNCTION__] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }

func

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

Custom function, useful to insert custom html and not breaking form chain.

    public function func($name, $func, $extra = [], $replace = [])
    {
        if (is_array($func)) {
            $extra = (array) $extra + $func;
            $func = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if ( ! $func) {
            if (isset($extra['callback'])) {
                $func = $extra['callback'];
            } elseif (isset($extra['function'])) {
                $func = $extra['function'];
            } else {
                $func = $extra['func'];
            }
        }
        $extra['name'] = ($extra['name'] ?? null) ?: $name;
        $extra['desc'] = $this->_prepare_desc($extra);
        if ($this->_chained_mode) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }

google_maps

public function google_maps($name = '', $desc = '', $extra = [], $replace = [])
    public function google_maps($name = '', $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_google_maps', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }

hidden

public function hidden($name, $extra = [], $replace = [])

Just hidden input.

    public function hidden($name, $extra = [], $replace = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['name'] = ($extra['name'] ?? null) ?: $name;
        $func = function ($extra, $r, $form) {
            $extra['id'] = $form->_prepare_id($extra);
            $extra['value'] = _escape_tpl_braces($form->_prepare_value($extra, $r, $form->_params));
            $extra['type'] = 'hidden';

            $attrs_names = ['type', 'id', 'name', 'value', 'data'];
            return '<input' . _attrs($extra, $attrs_names) . '>';
        };
        if ($this->_chained_mode) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }
public function icon_select_box($name = '', $desc = '', $extra = [], $replace = [])
    public function icon_select_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_boxes', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }

image

public function image($name = '', $desc = '', $extra = [], $replace = [])

Image upload.

    public function image($name = '', $desc = '', $extra = [], $replace = [])
    {
        if (is_array($desc)) {
            $extra += $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $this->_params['for_upload'] = true;
        $extra['name'] = ($extra['name'] ?? null) ?: ($name ?: 'image');
        $extra['desc'] = $this->_prepare_desc($extra, $desc);
        $extra['type'] = 'file';
        return $this->input($extra['name'], $extra['desc'], $extra, $replace);
    }
public function image_select_box($name, $values, $extra = [], $replace = [])
    public function image_select_box($name, $values, $extra = [], $replace = [])
    {
        return $this->_html_control($name, $values, $extra, $replace, 'image_select_box');
    }

info

public function info($name, $desc = '', $extra = [], $replace = [])
    public function info($name, $desc = '', $extra = [], $replace = [])
    {
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['name'] = ($extra['name'] ?? null) ?: $name;
        $extra['desc'] = $this->_prepare_desc($extra, $desc);
        $func = function ($extra, $r, $form) {
            $form->_prepare_inline_error($extra);
            $extra['desc'] = ! ($extra['no_label'] ?? null) && ! ($form->_params['no_label'] ?? null) ? ($extra['desc'] ?? null) : '';

            $value = ($r[$extra['name'] ?? ''] ?? null) ?: ($extra['value'] ?? null);
            if (is_array($extra['data'] ?? null)) {
                if (isset($extra['data'][$value])) {
                    $value = $extra['data'][$value];
                } elseif (isset($extra['data'][$extra['name']])) {
                    $value = $extra['data'][$extra['name']];
                }
            }
            $value = _escape_tpl_braces( ! isset($extra['no_escape']) ? _htmlchars($value) : $value);
            if ( ! ($extra['no_translate'] ?? null)) {
                $extra['desc'] = t($extra['desc']);
                $value = t($value);
            }
            if ($extra['no_text'] ?? null) {
                $value = '';
            }
            if ($extra['link'] ?? null) {
                if (MAIN_TYPE_ADMIN && main()->ADMIN_GROUP != 1 && ! _class('admin_methods')->_admin_link_is_allowed($extra['link'])) {
                    $extra['link'] = '';
                }
            }
            $icon = ($extra['icon'] ?? null) ? '<i class="' . $extra['icon'] . '"></i> ' : '';
            $content = '';
            if ($extra['link'] ?? null) {
                if ($extra['rewrite'] ?? null) {
                    $extra['link'] = url($extra['link']);
                }
                $extra['class'] = ($extra['class'] ?? null) ?: $form->CLASS_BTN_MINI;
                $extra['class'] = $form->_prepare_css_class($extra['class'], $r[$extra['name']], $extra);
                // info_link() / link() take the link from $replace
                $extra['href'] = _escape_tpl_braces($extra['link']);
                $extra['title'] = _escape_tpl_braces(($extra['title'] ?? null) ?: ($extra['desc'] ?? null) ?: ($extra['name'] ?? null));
                $attrs_names = ['href', 'name', 'class', 'style', 'disabled', 'target', 'alt', 'title'];
                $content = '<a' . _attrs($extra, $attrs_names) . '>' . $icon . $value . '</a>';
            } else {
                $extra['class'] = ($extra['class'] ?? null) ?: $form->CLASS_LABEL_INFO;
                $content = '<span class="' . $form->_prepare_css_class($extra['class'] ?? null, ($r[$extra['name'] ?? ''] ?? null), $extra) . '">' . $icon . $value . '</span>';
            }
            return $form->_row_html($content, $extra, $r);
        };
        if ($this->_chained_mode) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }

info_date

public function info_date($name = '', $format = '', $extra = [], $replace = [])
    public function info_date($name = '', $format = '', $extra = [], $replace = [])
    {
        $r = (array) $this->_replace + (array) $replace;
        if (is_array($format)) {
            $extra = (array) $extra + $format;
            $format = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['format'] = ($extra['format'] ?? null) ?: $format;
        $replace[$name] = _format_date($r[$name] ?? null, $extra['format'] ?? null);
        $this->_replace[$name] = $replace[$name];
        return $this->info($name, $format, $extra, $replace);
    }

info_lang

public function info_lang($name = '', $extra = [], $replace = [])
    public function info_lang($name = '', $extra = [], $replace = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['name'] = ($extra['name'] ?? null) ?: $name;
        $func = function ($extra, $r, $form) {
            $extra['desc'] = ! $extra['no_label'] && ! $form->_params['no_label'] ? $extra['desc'] : '';
            $value = ($r[$extra['name']] ?? null) ?: $extra['value'];
            $lang = $value;
            asset('bfh-select');
            if ( ! isset($form->lang_def_country)) {
                $form->lang_def_country = main()->get_data('lang_def_country');
            }
            $content = html()->icon('bfh-flag-' . $form->lang_def_country[$lang], strtoupper($lang));
            return $form->_row_html($content, $extra, $r);
        };
        if ($this->_chained_mode) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }

input

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

General input.

    public function input($name, $desc = '', $extra = [], $replace = [])
    {
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['name'] = ($extra['name'] ?? null) ?: $name;
        $extra['desc'] = $this->_prepare_desc($extra, $desc);
        $func = function ($extra, $r, $form) {
            $form->_prepare_inline_error($extra);
            $extra['id'] = $form->_prepare_id($extra);
            $extra['placeholder'] = t(isset($extra['placeholder']) ? $extra['placeholder'] : $extra['desc']);
            $extra['value'] = _escape_tpl_braces($form->_prepare_value($extra, $r, $form->_params));
            $extra['type'] = ($extra['type'] ?? null) ?: 'text';
            $extra['edit_link'] = ($extra['edit_link'] ?? null) ? (isset($r[$extra['edit_link']]) ? $r[$extra['edit_link']] : ($extra['edit_link'] ?? null)) : '';
            $bs5_class = ['range' => $form->CLASS_BS5_RANGE, 'color' => $form->CLASS_BS5_COLOR][$extra['type']] ?? null;
            $bs5_class = $bs5_class !== null && $form->_is_bs5() ? $bs5_class : null;
            $extra['class'] = ($bs5_class ?? $form->CLASS_FORM_CONTROL) . $form->_prepare_css_class('', ($r[$extra['name'] ?? ''] ?? null), $extra);
            if (($this->_params['filter'] ?? null) && ! isset($extra['sizing'])) {
                $extra['sizing'] = 'sm';
            }
            // Supported: sm, lg; Bootstrap 5 has no size for form-range
            if (($extra['sizing'] ?? null) && ! ($bs5_class && $extra['type'] === 'range')) {
                $extra['class'] .= ' ' . str_replace('%size', $extra['sizing'], $form->CLASS_TPL_INPUT_SIZE);
            }
            if ( ! isset($extra['floating']) && ($form->_params['floating'] ?? null) && ! in_array($extra['type'], (array) $form->CONF_FLOATING_SKIP_TYPES, true)) {
                $extra['floating'] = 1;
            }
            if ($form->_params['no_label'] ?? null) {
                $extra['desc'] = '';
            }
            $extra = $form->_input_assign_params_from_validate($extra);
            $attrs_names = ['name', 'type', 'id', 'class', 'style', 'placeholder', 'value', 'data', 'size', 'maxlength', 'pattern', 'disabled', 'readonly', 'required', 'autocomplete', 'accept', 'target', 'autofocus', 'title', 'min', 'max', 'step'];
            $html = '<input' . _attrs($extra, $attrs_names) . '>';
            if (($extra['show_value'] ?? null) && $extra['type'] === 'range') {
                $html = $form->_range_value_html($html, $extra);
            }
            return $form->_row_html($html, $extra, $r);
        };
        if ($this->_chained_mode) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }

insert_if_ok

public function insert_if_ok($table, $fields, $add_fields = [], $extra = [])

Alias.

    public function insert_if_ok($table, $fields, $add_fields = [], $extra = [])
    {
        return $this->db_insert_if_ok($table, $fields, $add_fields, $extra);
    }

integer

public function integer($name, $desc = '', $extra = [], $replace = [])
    public function integer($name, $desc = '', $extra = [], $replace = [])
    {
        return $this->number($name, $desc, $extra, $replace);
    }

language_box

public function language_box($name = '', $desc = '', $extra = [], $replace = [])
    public function language_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_boxes', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }

list_box

public function list_box($name, $values, $extra = [], $replace = [])
    public function list_box($name, $values, $extra = [], $replace = [])
    {
        return $this->_html_control($name, $values, $extra, $replace, 'list_box');
    }

locale_box

public function locale_box($name = '', $desc = '', $extra = [], $replace = [])
    public function locale_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_boxes', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }
public function location_select_box($name = '', $desc = '', $extra = [], $replace = [])
    public function location_select_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_boxes', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }

login

public function login($name = '', $desc = '', $extra = [], $replace = [])

Custom.

    public function login($name = '', $desc = '', $extra = [], $replace = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['type'] = ($extra['type'] ?? null) ?: 'text';
        $extra['prepend'] = isset($extra['prepend']) ? $extra['prepend'] : '<i class="' . $this->CLASS_ICON_LOGIN . '"></i>';
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if ( ! $name) {
            $name = 'login';
        }
        return $this->input($name, $desc, $extra, $replace);
    }
public function method_select_box($name = '', $desc = '', $extra = [], $replace = [])
    public function method_select_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_boxes', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }

money

public function money($name, $desc = '', $extra = [], $replace = [])
    public function money($name, $desc = '', $extra = [], $replace = [])
    {
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['prepend'] = isset($extra['prepend']) ? $extra['prepend'] : (($this->_params['currency'] ?? null) ?: '<i class="' . $this->CLASS_ICON_CURRENCY . '"></i>');
        $extra['append'] = isset($extra['append']) ? $extra['append'] : ''; // '.00';
        $extra['maxlength'] = isset($extra['maxlength']) ? $extra['maxlength'] : '8';
        return $this->decimal($name, $desc, $extra, $replace);
    }

month

public function month($name = '', $desc = '', $extra = [], $replace = [])

HTML5.

    public function month($name = '', $desc = '', $extra = [], $replace = [])
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['type'] = 'month';
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if ( ! $name) {
            $name = 'month';
        }
        return $this->input($name, $desc, $extra, $replace);
    }
public function multi_check_box($name, $values, $extra = [], $replace = [])
    public function multi_check_box($name, $values, $extra = [], $replace = [])
    {
        return $this->_html_control($name, $values, $extra, $replace, 'multi_check_box');
    }

multi_select

public function multi_select($name, $values, $extra = [], $replace = [])
    public function multi_select($name, $values, $extra = [], $replace = [])
    {
        return $this->_html_control($name, $values, $extra, $replace, 'multi_select_box');
    }
public function multi_select_box($name, $values, $extra = [], $replace = [])
    public function multi_select_box($name, $values, $extra = [], $replace = [])
    {
        return $this->_html_control($name, $values, $extra, $replace, 'multi_select_box');
    }

number

public function number($name, $desc = '', $extra = [], $replace = [])

HTML5.

    public function number($name, $desc = '', $extra = [], $replace = [])
    {
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['type'] = 'number';
        $extra['maxlength'] = isset($extra['maxlength']) ? $extra['maxlength'] : '10';
        return $this->input($name, $desc, $extra, $replace);
    }
public function on_after_render($func)
    public function on_after_render($func)
    {
        $this->_on[__FUNCTION__] = $func;
        return $this;
    }
public function on_after_update($func)
    public function on_after_update($func)
    {
        $this->_on[__FUNCTION__] = $func;
        return $this;
    }
public function on_after_validate($func)
    public function on_after_validate($func)
    {
        $this->_on[__FUNCTION__] = $func;
        return $this;
    }
public function on_before_render($func)
    public function on_before_render($func)
    {
        $this->_on[__FUNCTION__] = $func;
        return $this;
    }
public function on_before_update($func)
    public function on_before_update($func)
    {
        $this->_on[__FUNCTION__] = $func;
        return $this;
    }
public function on_before_validate($func)
    public function on_before_validate($func)
    {
        $this->_on[__FUNCTION__] = $func;
        return $this;
    }

on_post

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

order_box

public function order_box($name = '', $data = [], $extra = [], $replace = [])
    public function order_box($name = '', $data = [], $extra = [], $replace = [])
    {
        $data = $data ?: [
            'asc' => 'Ascending',
            'desc' => 'Descending',
        ];
        $extra['horizontal'] = isset($extra['horizontal']) ? $extra['horizontal'] : 1;
        return $this->radio_box($name ?: 'order_direction', t($data), $extra, $replace);
    }

password

public function password($name = '', $desc = '', $extra = [], $replace = [])
    public function password($name = '', $desc = '', $extra = [], $replace = [])
    {
        $extra['type'] = 'password';
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if ( ! $name) {
            $name = 'password';
        }
        $extra['prepend'] = isset($extra['prepend']) ? $extra['prepend'] : '<i class="' . $this->CLASS_ICON_PSWD . '"></i>';
        return $this->input($name, $desc, $extra, $replace);
    }

phone

public function phone($name = '', $desc = '', $extra = [], $replace = [])

Alias.

    public function phone($name = '', $desc = '', $extra = [], $replace = [])
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['type'] = 'tel';
        if (is_array($name)) {
            $extra += $name;
            $name = '';
        }
        if ( ! $name) {
            $name = 'phone';
        }
        return $this->input($name, $desc, $extra, $replace);
    }

phone_box

public function phone_box($name = '', $extra = [], $replace = [])
    public function phone_box($name = '', $extra = [], $replace = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
        } else {
            $extra['name'] = $name;
        }
        if ( ! $extra['name']) {
            $name = $extra['name'] = 'phone';
        }
        $func = function ($extra, $r, $form) {
            asset('jquery-formvalidation');
            jquery('
				var yf_phone_callback = function(value, validator, $field) {
					var isValid = value === "" || $field.intlTelInput("isValidNumber"),
						err	 = $field.intlTelInput("getValidationError"),
						message = null;
					switch (err) {
						case intlTelInputUtils.validationError.INVALID_COUNTRY_CODE:
							message = "' . t('The country code is not valid') . '";
							break;
						case intlTelInputUtils.validationError.TOO_SHORT:
							message = "' . t('The phone number is too short') . '";
							break;
						case intlTelInputUtils.validationError.TOO_LONG:
							message = "' . t('The phone number is too long') . '";
							break;
						case intlTelInputUtils.validationError.NOT_A_NUMBER:
							message = "' . t('The value is not a number') . '";
							break;
						default:
							message = "' . t('The phone number is not valid') . '";
							break;
					}
					return {
						valid: isValid,
						message: message
					};
				}
				var form = $("#' . addslashes($extra['name']) . '").closest("form")
				form.formValidation({
					framework: "bootstrap",
					fields: {
						"' . addslashes($extra['name']) . '": {
							validators: {
								callback: {
									callback: yf_phone_callback,
								}
							}
						}
					}
				})
				// Revalidate the number when changing the country
				.on("click", ".country-list", function() {
					form.formValidation("revalidateField", "' . addslashes($extra['name']) . '");
				});
			');
            $form->_prepare_inline_error($extra);
            $extra['edit_link'] = $extra['edit_link'] ? (isset($r[$extra['edit_link']]) ? $r[$extra['edit_link']] : $extra['edit_link']) : '';
            $extra['selected'] = $form->_prepare_selected($extra['name'], $extra, $r);
            $extra['value'] = &$extra['selected'];
            $extra['id'] = ($extra['id'] ?? null) ?: $extra['name'];

            $extra = $form->_input_assign_params_from_validate($extra);
            $content = _class('html')->phone_box($extra);
            if ($extra['no_label'] || $form->_params['no_label']) {
                $extra['desc'] = '';
            }
            if ($extra['hide_empty'] && ! strlen($content)) {
                return '';
            }
            return $form->_row_html($content, $extra, $r);
        };
        if ($this->_chained_mode) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }

preview

public function preview($name = '', $value = '', $extra = [], $replace = [])
    public function preview($name = '', $value = '', $extra = [], $replace = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if (is_array($value)) {
            $extra = (array) $extra + $value;
            $value = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $name = ($extra['name'] ?? null) ?: $name;
        $desc = '';
        $extra['desc'] = ($extra['desc'] ?? null) ?: 'Preview';
        $extra['class_add'] = ($extra['class_add'] ?? null) ?: 'preview';
        if ( ! $name) {
            $name = 'preview';
        }
        if ( ! isset($extra['icon'])) {
            $extra['icon'] = $this->CLASS_ICON_PREVIEW;
        }
        return $this->submit($name, $desc, $extra, $replace);
    }

price

public function price($name, $desc = '', $extra = [], $replace = [])
    public function price($name, $desc = '', $extra = [], $replace = [])
    {
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['min'] = ($extra['min'] ?? null) ?: '0';
        return $this->money($name, $desc, $extra, $replace);
    }

radio_box

public function radio_box($name, $values, $extra = [], $replace = [])
    public function radio_box($name, $values, $extra = [], $replace = [])
    {
        $as_btn_group = isset($extra['btn_group']) ? $extra['btn_group'] : $this->CONF_BOXES_USE_BTN_GROUP;
        if ($as_btn_group && $this->_is_bs5()) {
            $extra['label_extra']['class'] = $extra['label_extra']['class'] ?? $this->CLASS_BS5_BTN_CHECK;
            return $this->_html_control($name, $values, $extra, $replace, 'button_radio_box');
        }
        if ($as_btn_group) {
            $extra['class_add_controls'] = 'btn-group';
            $extra['controls']['data-toggle'] = 'buttons';
            $extra['class_add_label_radio'] = 'btn btn-xs btn-default';
        }
        return $this->_html_control($name, $values, $extra, $replace, 'radio_box');
    }

range

public function range($name = '', $desc = '', $extra = [], $replace = [])

HTML5.

    public function range($name = '', $desc = '', $extra = [], $replace = [])
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['type'] = 'range';
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if ( ! $name) {
            $name = 'range';
        }
        return $this->input($name, $desc, $extra, $replace);
    }

region_box

public function region_box($name = '', $desc = '', $extra = [], $replace = [])
    public function region_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_boxes', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }

render

public function render($extra = [], $replace = [])

Render result form html, gathered by row functions Params here not required, but if provided - will be passed to form_begin().

    public function render($extra = [], $replace = [])
    {
        if (isset($this->_rendered)) {
            return $this->_rendered;
        }
        $ts = null;
        if (DEBUG_MODE) {
            $ts = microtime(true);
        }
        _class('core_events')->fire('form.before_render', [$extra, $replace, $this]);
        $this->_extra = $extra;
        $on_before_render = $extra['on_before_render'] ?? $this->_on['on_before_render'] ?? null;
        if (is_callable($on_before_render)) {
            $on_before_render($extra, $replace, $this);
        }
        if ( ! is_array($this->_body)) {
            $this->_body = [];
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra_override = [];
        $form_id = $this->_get_form_id($extra, $replace);
        if ($form_id) {
            $extra_override = $this->_get_extra_override($form_id);
        }
        $headless_form = (($extra['no_form'] ?? null) || ($this->_params['no_form'] ?? null));

        $csrf_protect = isset($extra['csrf']) ? (bool) $extra['csrf'] : (isset($this->_params['csrf']) ? $this->_params['csrf'] : $this->CONF_CSRF_PROTECTION);
        if (isset($extra['method']) && strtolower($extra['method']) != 'post') {
            $csrf_protect = false;
        }
        if ($headless_form) {
            $csrf_protect = false;
        }
        if ($csrf_protect && is_callable($csrf_protect)) {
            $csrf_protect = $csrf_protect($this, $extra);
        }
        if ($csrf_protect) {
            $csrf_guard = _class('csrf_guard')->configure([
                'form_id' => $form_id,
                'token_name' => $this->CONF_CSRF_NAME,
            ]);
        }
        if (is_post()) {
            $is_current_form = isset($_POST[$this->CONF_FORM_ID_FIELD]) && ($_POST[$this->CONF_FORM_ID_FIELD] == $form_id);
            $csrf_failed = false;
            if ($csrf_protect && $is_current_form && ! $csrf_guard->validate($_POST[$this->CONF_CSRF_NAME])) {
                $csrf_failed = true;
                // We need this as validation now is skipping empty values
                if ( ! isset($_POST[$this->CONF_CSRF_NAME]) || (trim(($_POST[$this->CONF_CSRF_NAME] ?? '')) == '')) {
                    $_POST[$this->CONF_CSRF_NAME] = '__wrong_token_' . md5(microtime()) . '__';
                }
                $this->_params['show_alerts'] = true;
                $this->_validate_rules[$this->CONF_CSRF_NAME] = function ($in, $p, $a, &$error_msg) use ($form_id, $csrf_guard) {
                    $csrf_guard->log_error(['form_id' => $form_id]);
                    $error_msg = 'Invalid CSRF token. Send the form again. If you did not send this request then close this page.';
                    return false;
                };
                $this->_set_hidden_token($csrf_guard);
            }

            $on_post = isset($extra['on_post']) ? $extra['on_post'] : ($this->_on['on_post'] ?? null);
            // __clone() nulls every non-CLASS_/CONF_ property, so an unset instance value falls back to the configured singleton
            // a POST without this form's id was never token-checked, so it counts as a failed check too
            $skip_on_post = ($csrf_failed || ($csrf_protect && ! $is_current_form)) && ($this->SKIP_ON_POST_ON_CSRF_FAIL ?? _class('form2')->SKIP_ON_POST_ON_CSRF_FAIL);
            if (is_callable($on_post) && ! $skip_on_post) {
                $on_post($extra, $replace, $this);
            }
            $v = $this->_validate;
            if (isset($v) && is_callable($v['func'])) {
                $func = $v['func'];
                $func($v['validate_rules'], $v['post'], $v['extra'], $this);
            }
            $up = $this->_db_change_if_ok;
            if (isset($up) && is_callable($up['func'])) {
                $func = $up['func'];
                $func($up['table'], $up['fields'], $up['type'], $up['extra'], $this);
            }
        }
        if ($csrf_protect) {
            $this->_set_hidden_token($csrf_guard);
        }

        $r = (array) $this->_replace + (array) $replace;

        if ( ! $headless_form) {
            // Call these methods, if not done yet, save 2 api calls
            if ( ! isset($this->_body['form_begin'])) {
                $this->form_begin('', '', $extra + ($extra_override['form_begin'] ?? []), $r);
            }
            if ( ! isset($this->_body['form_end'])) {
                $this->form_end($extra + ($extra_override['form_end'] ?? []), $r);
            }
            // Force form_begin as first array element
            $form_begin = $this->_body['form_begin'];
            unset($this->_body['form_begin']);
            array_unshift($this->_body, $form_begin);

            // Force form_end as last array element
            $form_end = $this->_body['form_end'];
            unset($this->_body['form_end']);
            $this->_body['form_end'] = $form_end;
        }

        $tabbed_mode = false;
        $tabbed_buffer = [];
        $tabs = [];
        $tabs_extra = [];
        $tabs_name = '';
        $tabs_container = '';

        // Create tree of row_start and its children
        $item_row = [];
        $row_items = [];
        $row = false;
        $body_ids_to_extra = [];
        foreach ((array) $this->_body as $k => $v) {
            if ($v['name'] == 'row_start') {
                $row = $k;
            } elseif ($v['name'] == 'row_end') {
                $row = false;
            } elseif ($row) {
                $item_row[$k] = $row;
                $row_items[$row][$k] = ($v['extra']['id'] ?? null) ?: ($v['extra']['name'] ?? null);
            }
        }
        $all_errors = common()->_get_error_messages();

        $_debug_fields = null;
        foreach ((array) $this->_body as $k => $v) {
            if ( ! is_array($v)) {
                continue;
            }
            // dd_table() names a field by its value, which can be a float: PHP truncates float keys, without the deprecation
            $_name = $v['extra']['name'] ?? '';
            $_extra = ($v['extra'] ?? []) + ($extra_override[is_float($_name) ? (int) $_name : $_name] ?? []);
            $_replace = $r + ($v['replace'] ?? []);
            $func = $v['func'];
            if ($v['name'] == 'row_start') {
                // Mark row as containing errors, if children elements has at least one error
                foreach ((array) $row_items[$k] as $_k => $_id) {
                    if ( ! $_id || ! isset($all_errors[$_id])) {
                        continue;
                    }
                    $_extra['errors'][$v['extra']['name']] = $all_errors[$_id];
                }
            }
            if ($this->_stacked_mode_on) {
                $_extra['stacked'] = ($_extra['stacked'] ?? null) ?: true;
            }
            // 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']($_extra, $_replace, $this);
                if ( ! $_display_allowed) {
                    $this->_body[$k] = '';
                    continue;
                }
            }
            if (DEBUG_MODE) {
                $_debug_fields[$k] = [
                    'name' => $v['name'],
                    'extra' => $_extra,
                ];
            }
            $this->_body[$k]['rendered'] = $func($_extra, $_replace, $this);

            if ($this->_tabbed_mode_on) {
                $tabbed_mode = true;
                $tabbed_buffer[$k] = $this->_body[$k]['rendered'];
                if ($v['name'] == 'tab_start') {
                    $this->_tabs_counter++;
                    $tabs_name = $this->_tabs_name ?: 'tabs_' . $this->_tabs_counter;
                    $tabs_extra['by_id'][$tabs_name] = $this->_tabs_extra;
                }
                if ($v['name'] == 'tab_start' && ! $tabs_container) {
                    $tabs_container = $k;
                    $this->_body[$k]['rendered'] = '__TAB_START__';
                } else {
                    unset($this->_body[$k]);
                }
            } elseif ($tabbed_mode) { // switch off
                if ( ! $this->_tabbed_mode_on) {
                    $tabbed_mode = false;
                }
                $tabs[$tabs_name] = implode(PHP_EOL, $tabbed_buffer);
                $tabbed_buffer = [];
                $tabs_name = '';
            }
        }
        if ($tabbed_buffer) {
            $tabs['tab_last'] = implode(PHP_EOL, $tabbed_buffer);
            $tabbed_buffer = [];
        }
        if ($tabs) {
            $this->_body[$tabs_container]['rendered'] = html()->tabs($tabs, (array) $this->_params['tabs'] + (array) $tabs_extra);
        }
        if ($this->_params['show_alerts'] ?? null) {
            $errors = common()->_get_error_messages();
            if ($errors) {
                $e = [];
                foreach ((array) $errors as $msg) {
                    if ($this->_is_bs5()) {
                        $e[] = '<div class="' . $this->CLASS_ERROR . '">' . $msg . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="' . t('Close') . '"></button></div>';
                    } else {
                        $e[] = '<div class="' . $this->CLASS_ERROR . '"><button type="button" class="close" data-dismiss="alert">×</button>' . $msg . '</div>';
                    }
                }
                $this->_body = array_slice($this->_body, 0, 1, true) + ['error_message' => implode(PHP_EOL, $e)] + array_slice($this->_body, 1, null, true);
            }
        }
        if (($this->_params['stpl'] ?? null) || ($this->_params['return_array'] ?? null)) {
            $data = [];
            foreach ($this->_body as $k => $v) {
                $name = fix_html_attr_id(($v['extra']['name'] ?? null) ?: ($v['extra']['id'] ?? null) ?: $k);
                if ($name === 'form_action') {
                    $name = 'form_begin';
                }
                if (isset($data['form'][$name]) && ! empty($data['form'][$name])) {
                    if (in_array($name, ['form_id', '_token', 'token'])) {
                        // allow only once
                    } else {
                        if ( ! is_array($data['form'][$name])) {
                            $tmp = $data['form'][$name];
                            $data['form'][$name] = [];
                            $data['form'][$name][] = $tmp;
                            unset($tmp);
                        }
                        $data['form'][$name][] = $v['rendered'];
                    }
                } else {
                    $_rendered = '';
                    if (is_array($v)) {
                        $_rendered = array_key_exists('rendered', $v) ? (string) $v['rendered'] : '';
                    } else {
                        $_rendered = $v;
                    }
                    $data['form'][$name] = $_rendered;
                }
            }
            // Fixes for easier usage
            if ($data['form']['form_id']) {
                $data['form']['form_begin'] .= PHP_EOL . $data['form']['form_id'];
                unset($data['form']['form_id']);
            }
            if (isset($data['form']['token']) && ! isset($data['form']['_token'])) {
                $data['form']['_token'] = $data['form']['token'];
                unset($data['form']['token']);
            }
            if (isset($data['form']['_token'])) {
                $data['form']['form_begin'] .= PHP_EOL . $data['form']['_token'];
                unset($data['form']['_token']);
            }
            if ( ! isset($data['form']['begin'])) {
                $data['form']['begin'] = $data['form']['form_begin'];
                unset($data['form']['form_begin']);
            }
            if ( ! isset($data['form']['end'])) {
                $data['form']['end'] = $data['form']['form_end'];
                unset($data['form']['form_end']);
            }
            if ($this->_params['return_array']) {
                return $data['form'];
            }
            if (false === strpos($this->_params['stpl'], ' ') && tpl()->exists($this->_params['stpl'])) {
                $this->_rendered = tpl()->parse($this->_params['stpl'], $data);
            } else {
                $this->_rendered = tpl()->parse_string($this->_params['stpl'], $data);
            }

            unset($data);
        } else {
            $rendered = [];
            foreach ($this->_body as $k => $v) {
                if (is_array($v)) {
                    $rendered[$k] = array_key_exists('rendered', $v) ? (string) $v['rendered'] : '';
                } else {
                    $rendered[$k] = $v;
                }
            }
            $this->_rendered = implode(PHP_EOL, $rendered);
            unset($rendered);
        }
        unset($this->_body); // Save some memory

        $css_framework = ($extra['css_framework'] ?? null) ?: (($this->_params['css_framework'] ?? null) ?: conf('css_framework'));
        $extra['css_framework'] = $css_framework;
        $this->_rendered = _class('html5fw')->form_render_out($this->_rendered, $extra, $r, $this);

        $on_after_render = $extra['on_after_render'] ?? $this->_on['on_after_render'] ?? null;
        if (is_callable($on_after_render)) {
            $on_after_render($extra, $replace, $this);
        }
        _class('core_events')->fire('form.after_render', [$extra, $replace, $this]);
        if (DEBUG_MODE) {
            debug('form2[]', [
                'params' => $this->_params,
                'fields' => $_debug_fields,
                'time' => round(microtime(true) - $ts, 5),
                'trace' => main()->trace_string(),
            ]);
        }
        return $this->_rendered;
    }

row_end

public function row_end($extra = [])

Paired with row_start.

    public function row_end($extra = [])
    {
        $replace = [];
        $func = function ($extra, $r, $form) {
            $form->_stacked_mode_on = false;
            return $form->_row_html('', ['only_row_end' => 1] + (array) $extra);
        };
        if ($this->_chained_mode) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }

row_start

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

Shortcut for starting form row, needed to build row with several inlined inputs.

    public function row_start($name = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        $extra['name'] = ($extra['name'] ?? null) ?: $name;
        $replace = [];
        $func = function ($extra, $r, $form) {
            // auto-close row_end(), if not called implicitely
            if ($form->_stacked_mode_on) {
                $form->row_end();
            }
            $form->_stacked_mode_on = true;
            if ( ! isset($extra['id']) && ($extra['name'] ?? null)) {
                $extra['id'] = $extra['name'];
            }
            $extra['class_add_form_group'] = trim($form->CLASS_STACKED_ROW . ' ' . ($extra['class_add_form_group'] ?? ''));
            return $form->_row_html('', ['only_row_start' => 1] + (array) $extra);
        };
        if ($this->_chained_mode) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }

save

public function save($name = '', $desc = '', $extra = [], $replace = [])
    public function save($name = '', $desc = '', $extra = [], $replace = [])
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if ( ! isset($extra['icon'])) {
            $extra['icon'] = $this->CLASS_ICON_SAVE;
        }
        return $this->submit($name, $desc, $extra, $replace);
    }

save_and_back

public function save_and_back($name = '', $desc = '', $extra = [], $replace = [])
    public function save_and_back($name = '', $desc = '', $extra = [], $replace = [])
    {
        if ( ! $name) {
            $name = 'back_link';
            $r = $replace ? $replace : $this->_replace;
            if ( ! isset($r[$name]) && isset($r['back_url'])) {
                $name = 'back_url';
            }
        }
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['link_url'] = $name;
        $extra['link_name'] = $desc ?: 'Back';
        if ( ! isset($extra['icon'])) {
            $extra['icon'] = $this->CLASS_ICON_SAVE;
        }
        return $this->submit($name, $desc, $extra, $replace);
    }
public function save_and_clear($name = '', $desc = '', $extra = [], $replace = [])
    public function save_and_clear($name = '', $desc = '', $extra = [], $replace = [])
    {
        if ( ! $name) {
            $name = 'clear_link';
            $r = $replace ? $replace : $this->_replace;
            if ( ! isset($r[$name]) && isset($r['clear_url'])) {
                $name = 'clear_url';
            }
        }
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['link_url'] = $name;
        $extra['link_name'] = $desc ?: 'Clear';
        if ( ! isset($extra['icon'])) {
            $extra['icon'] = $this->CLASS_ICON_SAVE;
        }
        return $this->submit($name, $desc, $extra, $replace);
    }

select2_box

public function select2_box($name, $values = null, $extra = [], $replace = [])
    public function select2_box($name, $values = null, $extra = [], $replace = [])
    {
        return $this->_html_control($name, $values, $extra, $replace, 'select2_box');
    }

select_box

public function select_box($name, $values, $extra = [], $replace = [])
    public function select_box($name, $values, $extra = [], $replace = [])
    {
        return $this->_html_control($name, $values, $extra, $replace, 'select_box');
    }

stars

public function stars($name = '', $desc = '', $extra = [], $replace = [])
    public function stars($name = '', $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_stars', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }

stars_select

public function stars_select($name = '', $desc = '', $extra = [], $replace = [])

Star selector, got from http://fontawesome.io/examples/#custom.

    public function stars_select($name = '', $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_stars', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }

submit

public function submit($name = '', $value = '', $extra = [], $replace = [])
    public function submit($name = '', $value = '', $extra = [], $replace = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if (is_array($value)) {
            $extra = (array) $extra + $value;
            $value = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['name'] = ($extra['name'] ?? null) ?: $name;
        $extra['value'] = isset($extra['value']) ? $extra['value'] : ($value ?: 'Save');
        $extra['id'] = ($extra['id'] ?? null) ?: (($extra['name'] ?? null) ?: strtolower($extra['value'] ?? ''));
        $func = function ($extra, $r, $form) {
            $form->_prepare_inline_error($extra);
            $extra['link_url'] = ($extra['link_url'] ?? null) ? (isset($r[$extra['link_url']]) ? $r[$extra['link_url']] : ($extra['link_url'] ?? null)) : '';
            if (false === strpos($extra['link_url'], '/')) {
                $extra['link_url'] = '';
            }
            $extra['link_name'] = ($extra['link_name'] ?? null) ?: '';
            $extra['class'] = ($extra['class'] ?? null) ?: $form->CLASS_BTN_SUBMIT . $form->_prepare_css_class('', ($r[$extra['name'] ?? ''] ?? null), $extra);
            $extra['value'] = $extra['value'];
            $extra['type'] = 'submit';
            $button_text = ($extra['desc'] ?? null);
            $extra['desc'] = '';
            $extra['buttons_controls'] = true;
            if (($this->_params['filter'] ?? null) && ! isset($extra['sizing'])) {
                $extra['sizing'] = 'sm';
            }
            // Supported: xs, sm, md, lg
            if ($extra['sizing'] ?? null) {
                $extra['class'] .= ' btn-' . $extra['sizing'];
                $extra['link_class'] .= ' btn-' . $extra['sizing'];
            }
            $attrs_names = ['type', 'name', 'id', 'class', 'style', 'value', 'disabled', 'target'];
            if ( ! ($extra['as_input'] ?? null)) {
                $icon = (($extra['icon'] ?? null) ? '<i class="' . $extra['icon'] . '"></i> ' : '');
                $value = ( ! isset($extra['no_escape']) ? _htmlchars($extra['value']) : $extra['value']);
                $button_text = $icon . t($button_text ?: $value);
                return $form->_row_html('<button' . _attrs($extra, $attrs_names) . '>' . $button_text . '</button>', $extra, $r);
            }
            return $form->_row_html('<input' . _attrs($extra, $attrs_names) . '>', $extra, $r);
        };
        if ($this->_chained_mode) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }

switch_box

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

check_box() as a Bootstrap 5 switch (`switch` option).

    public function switch_box($name, $value = '', $extra = [], $replace = [])
    {
        if (is_array($value)) {
            $extra = (array) $extra + $value;
            $value = '';
        }
        $extra = (array) $extra;
        $extra['switch'] = 1;
        return $this->check_box($name, $value, $extra, $replace);
    }

tab_end

public function tab_end($extra = [])

Paired with tab_start.

    public function tab_end($extra = [])
    {
        $replace = [];
        $func = function ($extra, $r, $form) {
            $form->_tabbed_mode_on = false;
        };
        if ($this->_chained_mode) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }

tab_start

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

Shortcut for making tabbable form.

    public function tab_start($name = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        $replace = [];
        $extra['name'] = ($extra['name'] ?? null) ?: $name;
        $func = function ($extra, $r, $form) {
            // auto-close tab_end(), if not called implicitely
            if ($form->_tabbed_mode_on) {
                $form->tab_end();
            }
            $form->_tabbed_mode_on = true;
            $form->_tabs_name = $extra['name'];
            $form->_tabs_extra = $extra;
        };
        if ($this->_chained_mode) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }

tel

public function tel($name = '', $desc = '', $extra = [], $replace = [])

HTML5.

    public function tel($name = '', $desc = '', $extra = [], $replace = [])
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['type'] = 'tel';
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if ( ! $name) {
            $name = 'tel';
        }
        return $this->input($name, $desc, $extra, $replace);
    }
public function template_select_box($name = '', $desc = '', $extra = [], $replace = [])
    public function template_select_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_boxes', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }

text

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

textarea

public function textarea($name, $desc = '', $extra = [], $replace = [])
    public function textarea($name, $desc = '', $extra = [], $replace = [])
    {
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['name'] = ($extra['name'] ?? null) ?: $name;
        $extra['desc'] = $this->_prepare_desc($extra, $desc);
        $func = function ($extra, $r, $form) {
            $form->_prepare_inline_error($extra);
            $extra['id'] = $form->_prepare_id($extra);
            $extra['placeholder'] = t(isset($extra['placeholder']) ? $extra['placeholder'] : $extra['desc']);
            $extra['value'] = $form->_prepare_value($extra, $r, $form->_params);
            $extra['edit_link'] = ($extra['edit_link'] ?? null) ? (isset($r[$extra['edit_link']]) ? $r[$extra['edit_link']] : ($extra['edit_link'] ?? null)) : '';
            $extra['contenteditable'] = ( ! isset($extra['contenteditable']) || $extra['contenteditable']) ? 'true' : false;
            $use_ckeditor = isset($extra['ckeditor']) ? $extra['ckeditor'] : false;
            $extra['class'] = ($use_ckeditor ? $form->CLASS_CKEDITOR . ' ' : '') . $form->CLASS_FORM_CONTROL . $form->_prepare_css_class('', ($r[$extra['name'] ?? ''] ?? null), $extra);
            if (($extra['sizing'] ?? null) && ($form->_is_bs5() || $form->CONF_SIZING_ON_SELECTS_ALL_FW)) {
                $extra['class'] .= ' ' . str_replace('%size', $extra['sizing'], $form->CLASS_TPL_INPUT_SIZE);
            }
            // a rich editor replaces the textarea, a floating label would sit over it
            if ( ! isset($extra['floating']) && ($form->_params['floating'] ?? null) && ! ($extra['ckeditor'] ?? null) && ! ($extra['tinymce'] ?? null)) {
                $extra['floating'] = 1;
            }
            if ($form->_params['no_label'] ?? null) {
                $extra['desc'] = '';
            }
            $extra = $form->_input_assign_params_from_validate($extra);
            $attrs_names = ['id', 'name', 'placeholder', 'contenteditable', 'class', 'style', 'cols', 'rows', 'title', 'required', 'size', 'disabled', 'readonly', 'autocomplete', 'autofocus'];
            return $form->_row_html('<textarea' . _attrs($extra, $attrs_names) . '>' . _escape_tpl_braces( ! isset($extra['no_escape']) ? _htmlchars($extra['value']) : $extra['value']) . '</textarea>', $extra, $r);
        };
        if ($this->_chained_mode) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }

time

public function time($name = '', $desc = '', $extra = [], $replace = [])

HTML5.

    public function time($name = '', $desc = '', $extra = [], $replace = [])
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['type'] = 'time';
        if (is_array($name)) {
            $extra += $name;
            $name = '';
        }
        if ( ! $name) {
            $name = 'time';
        }
        return $this->input($name, $desc, $extra, $replace);
    }

time_box

public function time_box($name = '', $values = [], $extra = [], $replace = [])
    public function time_box($name = '', $values = [], $extra = [], $replace = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if ( ! $name) {
            $name = 'time';
        }
        return $this->_html_control($name, $values, $extra, $replace, 'time_box2');
    }

timezone_box

public function timezone_box($name = '', $desc = '', $extra = [], $replace = [])
    public function timezone_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_boxes', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }

tpl_row

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

Wrapper for template engine Example template: {form_row('form_begin')} {form_row('text','login')} {form_row('text','password')} {form_row('text','first_name')} {form_row('text','last_name')} {form_row('text','go_after_login','Url after login')} {form_row('box_with_link','group_box','Group','groups_link')} {form_row('active_box')} {form_row('info','add_date','Added')} {form_row('save_and_back')} {form_row('form_end')}.

{catch("field_name")}some_other_field{/catch} {form_row('text','%field_name')}
{catch("t_password")}My password inside replace['t_password']{/catch} {form_row('text','pswd','%t_password')}

    public function tpl_row($type = 'input', $replace = [], $name = '', $desc = '', $extra = [])
    {
        $name = trim($name ?? '');
        if ($name && $name[0] == '%') {
            $_name = substr($name, 1);
            if (isset($replace[$_name])) {
                $name = $replace[$_name];
            }
        }
        $desc = trim($desc ?? '');
        if ($desc && $desc[0] == '%') {
            $_desc = substr($desc, 1);
            if (isset($replace[$_desc])) {
                $desc = _escape_tpl_braces($replace[$_desc]);
            }
        }
        // Allow to pass extra params like this: param1=val1;param2=val2
        if (is_string($extra)) {
            $extra = trim($extra);
            if (false !== strpos($extra, ';') && false !== strpos($extra, '=')) {
                $extra = _attrs_string2array($extra);
            }
        }
        if ( ! is_array($extra)) {
            // Suppose we have 3rd argument as edit link here
            if ( ! empty($extra)) {
                $extra = ['edit_link' => $extra];
            } else {
                $extra = [];
            }
        }
        return $this->$type($name, $desc, $extra, $replace);
    }

ui_range

public function ui_range($name, $desc = '', $extra = [], $replace = [])
    public function ui_range($name, $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_ui_range', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }

update_if_ok

public function update_if_ok($table, $fields, $where_id = null, $extra = [])

Alias.

    public function update_if_ok($table, $fields, $where_id = null, $extra = [])
    {
        return $this->db_update_if_ok($table, $fields, $where_id, $extra);
    }

upload

public function upload($name = '', $desc = '', $extra = [], $replace = [])
    public function upload($name = '', $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_upload', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }

url

public function url($name = '', $desc = '', $extra = [], $replace = [])

HTML5.

    public function url($name = '', $desc = '', $extra = [], $replace = [])
    {
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['type'] = 'url';
        $extra['prepend'] = isset($extra['prepend']) ? $extra['prepend'] : 'url';
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = '';
        }
        if ( ! $name) {
            $name = 'url';
        }
        return $this->input($name, $desc, $extra, $replace);
    }

user_info

public function user_info($name = '', $desc = '', $extra = [], $replace = [])
    public function user_info($name = '', $desc = '', $extra = [], $replace = [])
    {
        return _class('form2_info', 'classes/form2/')->{__FUNCTION__}($name, $desc, $extra, $replace, $this);
    }
public function user_location_box($name = '', $desc = '', $extra = [], $replace = [])
    public function user_location_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['for_type'] = 'user';
        return $this->location_select_box($name, $desc, $extra, $replace);
    }
public function user_method_box($name = '', $desc = '', $extra = [], $replace = [])
    public function user_method_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['for_type'] = 'user';
        return $this->method_select_box($name, $desc, $extra, $replace);
    }
public function user_select_box($name, $values = null, $extra = [], $replace = [])
    public function user_select_box($name, $values = null, $extra = [], $replace = [])
    {
        _class('form_api')->{__FUNCTION__}($name, $values, $extra, $replace);
        return $this->_html_control($name, $values, $extra, $replace, 'select2_box');
    }
public function user_template_box($name = '', $desc = '', $extra = [], $replace = [])
    public function user_template_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['for_type'] = 'user';
        return $this->template_select_box($name, $desc, $extra, $replace);
    }

validate

public function validate($validate_rules = [], $post = [], $extra = [])

Form validation handler. Here we have special rule, called __form_id__ , it is used to track which form need to be validated from $_POST.

    public function validate($validate_rules = [], $post = [], $extra = [])
    {
        $this->_validate_prepare($validate_rules, $extra);

        $func = function ($validate_rules, $post, $extra, $form) {
            $form->_validate_prepare($validate_rules, $extra);
            $form_id = $form->_get_form_id();
            // Do not do validation until data is empty (usually means that form is just displayed and we wait user input)
            $data = (array) ( ! empty($post) ? $post : $_POST);
            foreach ((array) $data as $k => $v) {
                // We fix case when array key is present but have empty values like this: ['cat_id' => []]
                if (is_array($v) && ! empty($v)) {
                    // Convert multi-dimensional arrays into single-dimensional array dot notation: array('k1' => ['k2' => 'v2'])  ==>  ['k1.k2' => 'v2']
                    $dots = array_dot($v);
                    if ( ! empty($dots)) {
                        $data[$k] = $dots;
                    }
                }
            }
            if (empty($data)) {
                return $form;
            }
            // We need this to validate only correct form on page, where there can be several forms with validation at once
            if ($form_id && $data[$this->CONF_FORM_ID_FIELD] != $form_id) {
                return $form;
            }
            $on_before_validate = isset($extra['on_before_validate']) ? $extra['on_before_validate'] : ($form->_on['on_before_validate'] ?? '');
            if (is_callable($on_before_validate)) {
                $on_before_validate($form->_validate_rules, $data, $this);
            }
            $events = _class('core_events');
            $events->fire('form.before_validate', [$form->_validate_rules, $data]);
            // Processing of prepared rules
            $validate_ok = $form->_validate_rules_process($form->_validate_rules, $data, $extra);
            if ($validate_ok) {
                $form->_validate_ok = true;
                $on_validate_ok = isset($extra['on_validate_ok']) ? $extra['on_validate_ok'] : ($form->_on['on_validate_ok'] ?? null);
                if (is_callable($on_validate_ok)) {
                    $on_validate_ok($data, $extra, $form->_validate_rules, $this);
                }
                $events->fire('form.validate_ok', [$form->_validate_rules, $data, $extra]);
            } else {
                $form->_validate_ok = false;
                $on_validate_error = isset($extra['on_validate_error']) ? $extra['on_validate_error'] : ($form->_on['on_validate_error'] ?? '');
                if (is_callable($on_validate_error)) {
                    $on_validate_error($data, $extra, $form->_validate_rules, $this);
                }
                $events->fire('form.validate_error', [$form->_validate_rules, $data, $extra]);
            }
            $on_after_validate = isset($extra['on_after_validate']) ? $extra['on_after_validate'] : ($form->_on['on_after_validate'] ?? '');
            if (is_callable($on_after_validate)) {
                $on_after_validate($form->_validate_ok, $form->_validate_rules, $data, $extra, $this);
            }
            $events->fire('form.after_validate', [$form->_validate_ok, $form->_validate_rules, $data, $extra]);
            $form->_validated_fields = $data;
        };
        if ($this->_chained_mode) {
            $this->_validate = [
                'func' => $func,
                'extra' => $extra,
                'post' => $post,
                'validate_rules' => $validate_rules,
            ];
            return $this;
        }
        return $this;
    }

week

public function week($name = '', $desc = '', $extra = [], $replace = [])

HTML5.

    public function week($name = '', $desc = '', $extra = [], $replace = [])
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        $extra['type'] = 'week';
        if (is_array($name)) {
            $extra += $name;
            $name = '';
        }
        if ( ! $name) {
            $name = 'week';
        }
        return $this->input($name, $desc, $extra, $replace);
    }

yes_no_box

public function yes_no_box($name = '', $desc = '', $extra = [], $replace = [])
    public function yes_no_box($name = '', $desc = '', $extra = [], $replace = [])
    {
        if (is_array($desc)) {
            $extra = (array) $extra + $desc;
            $desc = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if ( ! isset($this->_pair_yes_no)) {
            $this->_pair_yes_no = $this->_bs5_pair(main()->get_data('pair_yes_no'));
        }
        $extra['items'] = $this->_pair_yes_no;
        if (($this->_params['filter'] ?? null) || ($extra['v2'] ?? null)) {
            $extra['no_label'] = 1;
            $extra['label_right'] = 1;
            $extra['renderer'] = 'button_yes_no_box';
        }
        $func = 'active_box';
        return $this->$func($name, $desc, $extra, $replace);
    }

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, $this->_chained_mode);
    }

__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;
            }
            // the class map state goes with the CLASS_ values, so a clone can switch them back
            if (in_array($k, ['BS5_CLASSES', 'BS5_COLORS', '_bs5_switched'], true)) {
                continue;
            }
            $this->$k = null;
        }
    }

__toString

public function __toString(): string

Need to avoid calling render() without params.

    public function __toString()
    {
        try {
            return (string) $this->render();
        } catch (Exception $e) {
            return '';
        }
    }
public function _ace_editor_html($extra = [], $replace = [])
    public function _ace_editor_html($extra = [], $replace = [])
    {
        return _class('form2_ace_editor', 'classes/form2/')->_ace_editor_html($extra, $replace, $this);
    }
public function _apply_existing_func($func, $data)
    public function _apply_existing_func($func, $data)
    {
        if (is_array($data)) {
            $self = __FUNCTION__;
            foreach ($data as $k => $v) {
                $data[$k] = $this->$self($func, $v);
            }
            return $data;
        }
        if ($data === null) {
            $data = '';
        }
        return @$func($data);
    }
public function _bs5_btn_check_class($class)

Label class of a Bootstrap 5 btn-check button from a Bootstrap 3 button class list: btn-xs / btn-mini become btn-sm, a colour its outline variant (the checked state fills it), btn-default goes; without a colour CLASS_BS5_BTN_CHECK.

  • $class string
    public function _bs5_btn_check_class($class)
    {
        $out = ['btn'];
        $has_color = false;
        foreach (preg_split('~\s+~', trim((string) $class), -1, PREG_SPLIT_NO_EMPTY) as $t) {
            if (in_array($t, ['btn', 'btn-default'], true)) {
                continue;
            }
            if (in_array($t, ['btn-xs', 'btn-mini'], true)) {
                $t = 'btn-sm';
            } elseif (preg_match('~^btn-(outline-)?(primary|secondary|success|danger|warning|info|light|dark)$~', $t, $m)) {
                $t = 'btn-outline-' . $m[2];
                $has_color = true;
            }
            $out[] = $t;
        }
        return $has_color ? implode(' ', array_unique($out)) : $this->CLASS_BS5_BTN_CHECK;
    }
public function _bs5_btn_check_items($items)

radio_box() items for a Bootstrap 5 btn-check group: the Bootstrap 3 button classes of an item's `class_add_label_radio` (pair_active_btn_group) become its label class.

    public function _bs5_btn_check_items($items)
    {
        foreach ((array) $items as $k => $item) {
            if (is_array($item) && isset($item['extra']['class_add_label_radio']) && ! isset($item['class'])) {
                $item['class'] = $this->_bs5_btn_check_class($item['extra']['class_add_label_radio']);
                unset($item['extra']['class_add_label_radio']);
                $items[$k] = $item;
            }
        }
        return $items;
    }
public function _bs5_btn_class($class, $add)

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

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

_bs5_color

public function _bs5_color($name)

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

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

_bs5_pair

public function _bs5_pair($data)

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

    public function _bs5_pair($data)
    {
        return $this->_is_bs5() ? html()->_bs5_btn_html($data) : $data;
    }
public function _ckeditor_html($extra = [], $replace = [])

Embedding ckeditor (http://ckeditor.com/) with kcfinder (http://kcfinder.sunhater.com/). Best way to include it into project:.

git submodule add https://github.com/yfix/ckeditor-releases.git www/ckeditor/ && cd www/ckeditor/ && git checkout latest/full
git submodule add https://github.com/yfix//kcfinder.git www/kcfinder

'www/' usually means PROJECT_PATH inside project working copy.
P.S. You can use free CDN for ckeditor as alternate solution: <script src="//cdnjs.cloudflare.com/ajax/libs/ckeditor/4.0.1/ckeditor.js"></script>

    public function _ckeditor_html($extra = [], $replace = [])
    {
        return _class('form2_ckeditor', 'classes/form2/')->_ckeditor_html($extra, $replace, $this);
    }
public function _db_change_if_ok($table, $fields, $type, $extra = [])
    public function _db_change_if_ok($table, $fields, $type, $extra = [])
    {
        $func = function ($table, $fields, $type, $extra, $form) {
            if ( ! $table || ! $type || empty($_POST)) {
                return $form;
            }
            $validate_ok = ($form->_validate_ok || $extra['force']);
            if ( ! $validate_ok) {
                return $form;
            }
            $data = [];
            foreach ((array) $fields as $k => $name) {
                // Example $fields = ['login','email'];
                if (is_numeric($k)) {
                    $db_field_name = $name;
                    // Example $fields = ['pswd' => 'password'];
                } else {
                    $db_field_name = $name;
                    $name = $k;
                }
                if (isset($form->_validated_fields[$name])) {
                    $data[$db_field_name] = $form->_validated_fields[$name];
                }
            }
            // This is non-validated list of fields to add to the insert array
            foreach ((array) $extra['add_fields'] as $db_field_name => $value) {
                $data[$db_field_name] = $value;
            }
            // Callback/hook function implementation
            $on_before_update = isset($extra['on_before_update']) ? $extra['on_before_update'] : $form->_on['on_before_update'] ?? null;
            if ($data && $table && is_callable($on_before_update)) {
                $on_before_update($data, $table, $fields, $type, $extra, $this);
            }
            _class('core_events')->fire('form.before_update', [$data, $table, $fields, $type, $extra]);
            if ($data && $table) {
                $db = is_object($form->_params['db'] ?? null) ? $form->_params['db'] : db();
                $no_writes = (bool) $form->CONF_NO_DB_WRITES;
                if ($type == 'update' && ! $no_writes) {
                    $where_id = ($extra['where_id'] ?? null) ?: $form->_replace[$form->_params['id'] ?: 'id'];
                    if ($where_id) {
                        $db->update_safe($table, $data, $where_id);
                    } else {
                        throw new Exception(__CLASS__ . '::' . __FUNCTION__ . ' where_id param is empty, not updating table: ' . $table);
                    }
                } elseif ($type == 'insert' && ! $no_writes) {
                    $db->insert_safe($table, $data);
                }
                // Callback/hook function implementation
                $on_after_update = isset($extra['on_after_update']) ? $extra['on_after_update'] : $form->_on['on_after_update'] ?? null;
                if (is_callable($on_after_update)) {
                    $on_after_update($data, $table, $fields, $type, $extra, $this);
                }
                _class('core_events')->fire('form.after_update', [$data, $table, $fields, $type, $extra]);
                $on_success_text = isset($extra['on_success_text']) ? $extra['on_success_text'] : $form->_on['on_success_text'] ?? null;
                if ($on_success_text) {
                    common()->set_notice($on_success_text);
                } elseif ($no_writes) {
                    common()->message_success(t('The form is valid. Nothing was saved: this site does not write form data.'));
                }
                $redirect_link = ($extra['redirect_link'] ?? null) ?: ($form->_replace['redirect_link'] ?? null);
                if ( ! $redirect_link) {
                    $redirect_link = $form->_replace['back_link'] ?? null;
                }
                if ( ! $redirect_link || false === strpos($redirect_link, '/')) {
                    $redirect_link = url('/@object/@action/@id');
                }
                if ( ! ($extra['no_redirect'] ?? null) && ! main()->is_console()) {
                    js_redirect($redirect_link);
                }
            }
        };
        if ($this->_chained_mode) {
            $this->_db_change_if_ok = [
                'func' => $func,
                'table' => $table,
                'fields' => $fields,
                'type' => $type,
                'extra' => $extra,
            ];
            return $this;
        }
        return $this;
    }

_dd_row_html

public function _dd_row_html($content, $extra = [], $replace = [])

Generate form row using dl>dt,dd html tags. Useful for user profle and other simple table-like content.

    public function _dd_row_html($content, $extra = [], $replace = [])
    {
        if (($extra['hide_empty'] ?? null) && ! strlen($content)) {
            return '';
        }
        if ($this->_stacked_mode_on) {
            $extra['stacked'] = true;
        }
        $css_framework = ($extra['css_framework'] ?? null) ?: (($this->_params['css_framework'] ?? null) ?: conf('css_framework'));
        $extra['css_framework'] = $css_framework;
        return _class('html5fw')->form_dd_row($content, $extra, $replace, $this);
    }

_extend

public function _extend($name, $func)
    public function _extend($name, $func)
    {
        $this->_extend[$name] = $func;
    }
public function _find_custom_validate_error($name, $func)
    public function _find_custom_validate_error($name, $func)
    {
        if ( ! strlen($name ?? '')) {
            return '';
        }
        $custom_error = '';
        foreach ((array) $this->_body as $a) {
            if ( ! isset($a['extra']) || ! isset($a['extra']['validate_error'])) {
                continue;
            }
            // Now we also support array elements descriptions searching
            if ($a['extra']['name'] != $name && $a['extra']['name'] != $name . '[]') {
                continue;
            }
            $custom_error = $a['extra']['validate_error'];
            break;
        }
        // Support for separate errors by validate functions
        if (is_array($custom_error)) {
            return isset($custom_error[$func]) ? $custom_error[$func] : '';
        }
        return $custom_error;
    }
public function _find_field_desc($name)
    public function _find_field_desc($name)
    {
        if ( ! strlen($name ?? '')) {
            return '';
        }
        $desc = $name;
        foreach ((array) $this->_body as $a) {
            if ( ! isset($a['extra']) || ! strlen($a['extra']['desc'] ?? '')) {
                continue;
            }
            // Now we also support array elements descriptions searching
            if ($a['extra']['name'] != $name && $a['extra']['name'] != $name . '[]') {
                continue;
            }
            $desc = $a['extra']['desc'];
            break;
        }
        return $desc;
    }
public function _get_extra_override($form_id = '')
    public function _get_extra_override($form_id = '')
    {
        if ( ! strlen($form_id)) {
            return [];
        }
        $autoid_prefix = $this->CONF_FORM_AUTOID_PREFIX;
        if (is_unit_test() || (strlen($autoid_prefix) && substr($form_id, 0, strlen($autoid_prefix)) === $autoid_prefix)) {
            return [];
        }
        $extra_override = [];
        // Data from database have highest priority, so we init it first
        $all_attrs_override = main()->get_data('form_attributes');
        $extra_override = $all_attrs_override[$form_id] ?? null;
        // Search for override params inside shared files
        $suffix = $form_id . '.form.php';
        $slen = strlen($suffix);
        $patterns = [
            'framework' => [
                YF_PATH . 'form/' . $form_id . '*' . $suffix,
                YF_PATH . 'plugins/*/form/' . $form_id . '*' . $suffix,
                YF_PATH . 'share/form/' . $form_id . '*' . $suffix,
            ],
            'config' => [
                CONFIG_PATH . 'form/' . $form_id . '*' . $suffix,
                CONFIG_PATH . 'plugins/*/form/' . $form_id . '*' . $suffix,
                CONFIG_PATH . 'share/form/' . $form_id . '*' . $suffix,
            ],
            'project' => [
                PROJECT_PATH . 'form/' . $form_id . '*' . $suffix,
                PROJECT_PATH . 'plugins/*/form/' . $form_id . '*' . $suffix,
                PROJECT_PATH . 'share/form/' . $form_id . '*' . $suffix,
            ],
        ];
        if (SITE_PATH != PROJECT_PATH) {
            $patterns['site'] = [
                SITE_PATH . 'form/' . $form_id . '*' . $suffix,
                SITE_PATH . 'plugins/*/form/' . $form_id . '*' . $suffix,
                SITE_PATH . 'share/form/' . $form_id . '*' . $suffix,
            ];
        }
        $names = [];
        foreach ($patterns as $paths) {
            foreach ($paths as $path) {
                foreach (glob($path) as $matchedPath) {
                    $name = substr(basename($matchedPath), 0, -$slen);
                    $names[$name] = $matchedPath;
                }
            }
        }
        // Allow override framework defaults inside project
        foreach ($names as $name => $path) {
            $data = [];
            include $path;
            foreach ((array) $data as $field => $attrs) {
                $extra_override[$field] = (array) $extra_override[$field] + (array) $attrs;
            }
        }
        return $extra_override;
    }

_get_form_id

public function _get_form_id($extra = [], $replace = [])
    public function _get_form_id($extra = [], $replace = [])
    {
        $form_id = $this->_form_id;
        $replace = $replace ?: $this->_replace;
        $params = &$this->_params;
        if (isset($replace[$this->CONF_FORM_ID_FIELD]) && $replace[$this->CONF_FORM_ID_FIELD]) {
            $form_id = $replace[$this->CONF_FORM_ID_FIELD];
        } elseif (isset($params[$this->CONF_FORM_ID_FIELD]) && $params[$this->CONF_FORM_ID_FIELD]) {
            $form_id = $params[$this->CONF_FORM_ID_FIELD];
        }
        if ( ! $form_id) {
            $form_id = $this->CONF_FORM_AUTOID_PREFIX . strtolower($_GET['object'] . '_' . $_GET['action']) . '_' . (main()->_unique_widget_ids['form'] = (main()->_unique_widget_ids['form'] ?? 0) + 1);
        }
        $this->_form_id = $form_id;
        return $form_id;
    }

_html_control

public function _html_control($name, $values, $extra = [], $replace = [], $func_html_control = '')
    public function _html_control($name, $values, $extra = [], $replace = [], $func_html_control = '')
    {
        if ( ! is_array($extra)) {
            $extra = [];
        }
        if (is_array($name)) {
            $extra = (array) $extra + $name;
        } else {
            $extra['name'] = $name;
        }
        $desc = '';
        $extra['desc'] = $this->_prepare_desc($extra, $desc);
        $extra['values'] = isset($extra['values']) ? $extra['values'] : (array) $values; // Required
        $extra['func_html_control'] = ($extra['func_html_control'] ?? null) ?: $func_html_control;
        $func = function ($extra, $r, $form) {
            $form->_prepare_inline_error($extra);
            $extra['edit_link'] = ($extra['edit_link'] ?? null) ? (isset($r[$extra['edit_link']]) ? $r[$extra['edit_link']] : ($extra['edit_link'] ?? null)) : '';
            $extra['selected'] = $form->_prepare_selected($extra['name'], $extra, $r);
            $extra['id'] = $extra['name'];
            $extra = $form->_input_assign_params_from_validate($extra);
            $func = $extra['func_html_control'];
            // outside Bootstrap 5 an explicit sizing on selects was ignored: applied there only with CONF_SIZING_ON_SELECTS_ALL_FW
            $sizing_select = in_array($func, ['select_box', 'multi_select_box'], true) && ($form->_is_bs5() || $form->CONF_SIZING_ON_SELECTS_ALL_FW);
            $sizing = $sizing_select ? ($extra['sizing'] ?? null) : null;
            if ($this->_params['filter'] ?? null) {
                if ($sizing_select) {
                    $sizing = $sizing ?? 'sm';
                } else {
                    $extra['class_add'] = ($extra['class_add'] ?? '') . ' input-sm';
                }
            }
            if ($sizing) {
                $extra['class_add'] = ($extra['class_add'] ?? '') . ' ' . str_replace('%size', $sizing, $form->CLASS_TPL_SELECT_SIZE);
            }
            if ( ! isset($extra['floating']) && ($form->_params['floating'] ?? null) && $func === 'select_box') {
                $extra['floating'] = 1;
            }
            $content = _class('html')->$func($extra);
            if (($extra['no_label'] ?? null) || ($form->_params['no_label'] ?? null)) {
                $extra['desc'] = '';
            }
            if (($extra['hide_empty'] ?? null) && ! strlen($content)) {
                return '';
            }
            return $form->_row_html($content, $extra, $r);
        };
        if ($this->_chained_mode) {
            $this->_body[] = ['func' => $func, 'extra' => $extra, 'replace' => $replace, 'name' => __FUNCTION__];
            return $this;
        }
        return $func((array) $extra + (array) $this->_extra, (array) $replace + (array) $this->_replace, $this);
    }

_init

public function _init()
    public function _init()
    {
        html()->_bs5_switch_classes($this, $this->_is_bs5());
    }
public function _input_assign_params_from_validate($extra = [])
    public function _input_assign_params_from_validate($extra = [])
    {
        return _class('form2_validate', 'classes/form2/')->_input_assign_params_from_validate($extra, $this);
    }

_is_bs5

public function _is_bs5()

Whether this form renders Bootstrap 5: its css_framework param, else conf('css_framework').

    public function _is_bs5()
    {
        return (($this->_params['css_framework'] ?? null) ?: conf('css_framework')) === 'bs5';
    }
public function _prepare_css_class($default_class, $value, &$extra)
    public function _prepare_css_class($default_class, $value, &$extra)
    {
        $css_class = $default_class;
        if ($extra['badge'] ?? null) {
            $badge = is_array($extra['badge']) && isset($extra['badge'][$value]) ? $extra['badge'][$value] : $extra['badge'];
            if ($badge) {
                $css_class = str_replace('%name', $this->_bs5_color($badge), $this->CLASS_TPL_BADGE);
            }
        } elseif ($extra['label'] ?? null) {
            $label = is_array($extra['label']) && isset($extra['label'][$value]) ? $extra['label'][$value] : $extra['label'];
            if ($label) {
                $css_class = str_replace('%name', $this->_bs5_color($label), $this->CLASS_TPL_LABEL);
            }
        } elseif ($extra['class'] ?? null) {
            $_css_class = is_array($extra['class']) && isset($extra['class'][$value]) ? $extra['class'][$value] : $extra['class'];
            if ($_css_class) {
                $css_class = $_css_class;
            }
        }
        // Needed to not modify original class of element (sometimes complex), but just add css class there
        if (isset($extra['class_add'])) {
            $_css_class_add = is_array($extra['class_add']) && isset($extra['class_add'][$value]) ? $extra['class_add'][$value] : $extra['class_add'];
            if ($_css_class_add) {
                $css_class .= ' ' . $_css_class_add;
            }
        }
        if ($this->_params['big_labels'] ?? null) {
            $css_class .= ' labels-big';
        }
        return $css_class ? ' ' . $css_class : '';
    }
public function _prepare_custom_attr($attr = [])
    public function _prepare_custom_attr($attr = [])
    {
        $body = [];
        foreach ((array) $attr as $k => $v) {
            $body[] = _htmlchars($k) . '="' . _htmlchars($v) . '"';
        }
        return implode(' ', $body);
    }

_prepare_desc

public function _prepare_desc(&$extra, $input = '')
    public function _prepare_desc(&$extra, $input = '')
    {
        $out = ($extra['desc'] ?? null);
        ! $out && $out = $input;
        if ( ! $out) {
            $out = ucfirst(str_replace('_', ' ', $extra['name'] ?? ''));
            $is_html_array = (false !== strpos($out, '['));
            if ($is_html_array) {
                $out = str_replace(['[', ']'], ['.', ''], trim($out, ']['));
            }
        }
        return $out;
    }

_prepare_id

public function _prepare_id(&$extra, $default = '')
    public function _prepare_id(&$extra, $default = '')
    {
        $out = ($extra['id'] ?? null);
        if ( ! $out) {
            $out = $extra['name'] ?? '';
            $is_html_array = (false !== strpos($out, '['));
            if ($is_html_array) {
                $out = str_replace(['[', ']'], ['_', ''], trim($out, ']['));
            }
        }
        ! $out && $out = $default;
        return $out;
    }
public function _prepare_inline_error(&$extra, $name = '')
    public function _prepare_inline_error(&$extra, $name = '')
    {
        $name = $name ?: ($extra['name'] ?? '');
        $is_html_array = (false !== strpos($name, '['));
        if ($is_html_array) {
            $name_orig = $name;
            $name = str_replace(['[', ']'], ['.', ''], trim($name, ']['));
        }
        $extra['errors'] = common()->_get_error_messages();
        if (isset($extra['errors'][$name])) {
            $remove_errors = true;
            $var_name = 'do_not_remove_errors';
            if ( ! empty($extra[$var_name]) || ! empty($this->_params[$var_name]) || ! empty($this->_extra[$var_name]) || ! empty($this->_params['extra'][$var_name])) {
                $remove_errors = false;
            }
            if ($remove_errors) {
                common()->_remove_error_messages($name);
            }
            $extra['inline_help'] = $extra['errors'][$name];
        }
    }
public function _prepare_selected($name, &$extra, &$r)
    public function _prepare_selected($name, &$extra, &$r)
    {
        $is_array = strpos($name, '[');
        if ($is_array !== false) {
            $value = &$r;
            $keys = explode('[', $name);
            foreach ($keys as $key) {
                $key = trim(rtrim($key, ']'));
                if ( ! isset($value[$key])) {
                    $value = null;
                    break;
                }
                $value = &$value[$key];
            }
            $selected = $value;
        } else {
            $selected = $r[$name] ?? null;
        }
        if (isset($extra['selected'])) {
            $selected = $extra['selected'];
        } elseif (isset($this->_params['selected'])) {
            $selected = $this->_params['selected'][$name];
        }
        return $selected;
    }
public function _prepare_value(&$extra, &$replace, &$params)
    public function _prepare_value(&$extra, &$replace, &$params)
    {
        $name = $extra['name'] ?? '';
        $is_html_array = (false !== strpos($name, '['));
        if ($is_html_array) {
            $name_dots = str_replace(['[', ']'], ['.', ''], trim($name, ']['));
            $replace_dots = array_dot($replace);
        }
        $value = '';
        if ($extra['value'] ?? false) {
            $value = $extra['value'];
        } elseif ($replace[$name] ?? false) {
            $value = $replace[$name];
        } elseif ($is_html_array && ($replace_dots[$name_dots] ?? false)) {
            $value = $replace_dots[$name_dots];
        } elseif ($extra['selected'] ?? false) {
            $value = $extra['selected'];
        } elseif ($params['selected'][$name] ?? false) {
            $value = $params['selected'][$name];
        } elseif ($is_html_array && ($params['selected'][$name_dots] ?? false)) {
            $value = $params['selected'][$name_dots];
        }
        return $value;
    }
public function _range_value_html($input, $extra)

A range input with its value shown next to it (`show_value`): an output element the input updates while it moves.

  • $input string the <input> tag
  • $extra array
    public function _range_value_html($input, $extra)
    {
        $value = $extra['value'] ?? '';
        if ( ! is_numeric($value)) {
            // what the browser shows for an empty value: the middle of min..max on the step grid
            $min = is_numeric($extra['min'] ?? null) ? (float) $extra['min'] : 0.0;
            $max = is_numeric($extra['max'] ?? null) ? (float) $extra['max'] : 100.0;
            $step = is_numeric($extra['step'] ?? null) && $extra['step'] > 0 ? (float) $extra['step'] : 1.0;
            $value = $max < $min ? $min : $min + round(($max - $min) / 2 / $step) * $step;
            $value = floor($value) == $value ? (int) $value : $value;
        }
        $input = substr($input, 0, -1) . ' oninput="this.parentNode.querySelector(\'output\').value=this.value">';
        $for = isset($extra['id']) ? ' for="' . _htmlchars(fix_html_attr_id($extra['id'])) . '"' : '';
        return '<div class="' . $this->CLASS_RANGE_VALUE_WRAP . '">' . $input
            . '<output class="' . $this->CLASS_RANGE_VALUE . '"' . $for . '>' . _htmlchars((string) $value) . '</output></div>';
    }
public function _remove_items_by_name($name = '')
    public function _remove_items_by_name($name = '')
    {
        if (is_array($name)) {
            $func = __FUNCTION__;
            foreach ((array) $name as $_name) {
                $this->$func($_name);
            }
            return true;
        }
        if ( ! $name) {
            return false;
        }
        foreach ((array) $this->_body as $k => $v) {
            if ($v['name'] == $name) {
                unset($this->_body[$k]);
            }
        }
        return true;
    }

_row_html

public function _row_html($content, $extra = [], $replace = [])
    public function _row_html($content, $extra = [], $replace = [])
    {
        if ( ! strlen($content) && (($extra['hide_empty'] ?? null) || ($this->_params['hide_empty'] ?? null))) {
            return '';
        }
        if ($this->_params['only_content'] ?? null) {
            return $content;
        }
        if ($this->_params['dd_mode'] ?? null) {
            return $this->_dd_row_html($content, $extra, $replace);
        }
        if (($extra['form_input_no_append'] ?? null) || ($this->_params['form_input_no_append'] ?? null) || conf('form_input_no_append')) {
            $extra['append'] = '';
            $extra['prepend'] = '';
            $extra['append_html'] = '';
            $extra['prepend_html'] = '';
        }
        if ($this->_stacked_mode_on) {
            $extra['stacked'] = ($extra['stacked'] ?? null) ?: true;
        }
        $css_framework = ($extra['css_framework'] ?? null) ?: (($this->_params['css_framework'] ?? null) ?: conf('css_framework'));
        $extra['css_framework'] = $css_framework;
        return _class('html5fw')->form_row($content, $extra, $replace, $this);
    }
public function _set_hidden_form_id($form_id = '')
    public function _set_hidden_form_id($form_id = '')
    {
        if ($this->_isset_hidden_form_id) {
            return true;
        }
        if ( ! $form_id) {
            $form_id = $this->_get_form_id();
        }
        $this->_replace[$this->CONF_FORM_ID_FIELD] = $form_id;
        $this->hidden($this->CONF_FORM_ID_FIELD, ['value' => $form_id]);
        $this->_isset_hidden_form_id = true;
    }
public function _set_hidden_token($csrf_guard)
    public function _set_hidden_token($csrf_guard)
    {
        if ($this->_isset_hidden_token) {
            return true;
        }
        $this->_replace[$this->CONF_CSRF_NAME] = $csrf_guard->generate();
        $this->hidden($this->CONF_CSRF_NAME);
        $this->_isset_hidden_token = true;
    }

_show_tip

public function _show_tip($value = '', $extra = [], $replace = [])
    public function _show_tip($value = '', $extra = [], $replace = [])
    {
        return tip($value, $replace);
    }

_tinymce_html

public function _tinymce_html($extra = [], $replace = [])
    public function _tinymce_html($extra = [], $replace = [])
    {
        return _class('form2_tinymce', 'classes/form2/')->_tinymce_html($extra, $replace, $this);
    }
public function _validate_prepare($validate_rules = [], $extra = [])
    public function _validate_prepare($validate_rules = [], $extra = [])
    {
        $form_global_validate = isset($this->_params['validate']) ? $this->_params['validate'] : (isset($this->_replace['validate']) ? $this->_replace['validate'] : []);
        foreach ((array) $form_global_validate as $name => $rules) {
            $this->_validate_rules[$name] = $rules;
        }
        foreach ((array) $this->_body as $v) {
            $_extra = $v['extra'];
            if (isset($_extra['validate']) && isset($_extra['name'])) {
                $this->_validate_rules[$_extra['name']] = $_extra['validate'];
            }
        }
        foreach ((array) $validate_rules as $name => $rules) {
            $this->_validate_rules[$name] = $rules;
        }
        $form_id = '';
        if (isset($this->_validate_rules[$this->CONF_FORM_ID_FIELD])) {
            $form_id = $this->_validate_rules[$this->CONF_FORM_ID_FIELD];
            unset($this->_validate_rules[$this->CONF_FORM_ID_FIELD]);
        } elseif (isset($this->_params[$this->CONF_FORM_ID_FIELD])) {
            $form_id = $this->_params[$this->CONF_FORM_ID_FIELD];
            unset($this->_params[$this->CONF_FORM_ID_FIELD]);
        }
        if ($form_id) {
            $this->_form_id = $form_id;
        } else {
            $form_id = $this->_get_form_id();
        }
        $this->_set_hidden_form_id($form_id);

        $this->_validate_rules = $this->_validate_rules_cleanup($this->_validate_rules);
        // Prepare array of rules by form method for quick access
        if ($this->_validate_rules) {
            foreach ((array) $this->_validate_rules as $item => $rules) {
                foreach ((array) $rules as $rule) {
                    if (is_string($rule[0])) {
                        $this->_validate_rules_names[$item][$rule[0]] = ($rule[1] ?? null) ?: true;
                    }
                }
            }
        }
    }
public function _validate_rules_array_from_raw($raw = '')
    public function _validate_rules_array_from_raw($raw = '')
    {
        $func = __FUNCTION__;
        return _class('validate')->$func($raw);
    }
public function _validate_rules_cleanup($validate_rules = [])

Examples of validate rules setting: 'name1' => 'trim|required', 'name2' => ['trim', 'required'], 'name3' => ['trim|required', 'other_rule|other_rule2|other_rule3'], 'name4' => ['trim|required', function() { return true; } ], 'name5' => ['trim', 'required', function() { return true; } ], '__before__' => 'trim', '__after__' => 'some_method2|some_method3',.

    public function _validate_rules_cleanup($validate_rules = [])
    {
        $func = __FUNCTION__;
        return _class('validate')->$func($validate_rules);
    }
public function _validate_rules_process($validate_rules, &$data)
    public function _validate_rules_process($validate_rules, &$data)
    {
        $validate_ok = true;
        foreach ((array) $validate_rules as $name => $rules) {
            $is_required = false;
            foreach ((array) $rules as $rule) {
                if (is_string($rule[0]) && substr($rule[0], 0, strlen('required')) === 'required') {
                    $is_required = true;
                    break;
                }
            }
            foreach ((array) $rules as $rule) {
                $is_ok = true;
                $error_msg = '';
                $func = $rule[0];
                $param = $rule[1];
                $value = $data[$name] ?? null;
                // PHP pure function, from core or user
                if (is_string($func) && function_exists($func)) {
                    $data[$name] = $this->_apply_existing_func($func, $data[$name] ?? null);
                } elseif (is_callable($func)) {
                    $is_ok = $func($value, null, $data, $error_msg);
                } else {
                    if (is_array($value) && ! empty($value)) {
                        foreach ($value as $k => $v) {
                            $is_ok = _class('validate')->$func($v, ['param' => $param], $data, $error_msg, ['field' => $name]);
                            if ( ! $is_ok) {
                                break;
                            }
                        }
                    } else {
                        $is_ok = _class('validate')->$func($value, ['param' => $param], $data, $error_msg, ['field' => $name]);
                    }
                    if ( ! $is_ok && empty($error_msg)) {
                        $desc = $this->_find_field_desc($name) ?: $name;
                        $error_param = $this->_find_field_desc($param) ?: $param;
                        // Search for custom error message, also able to divide error by validate func
                        $error_msg = $this->_find_custom_validate_error($name, $func);
                        if ($error_msg) {
                            $error_msg = str_replace(['%field', '%param'], [$desc, $error_param], $error_msg);
                        } else {
                            // Default error message
                            $error_msg = t('form_validate_' . $func, ['%field' => $desc, '%param' => $error_param]);
                        }
                    }
                }
                // In this case we do not track error if field is empty and not required
                if ( ! $is_ok && ! $is_required && ! strlen($data[$name])) {
                    $is_ok = true;
                    $error_msg = '';
                }
                if ( ! $is_ok) {
                    $validate_ok = false;
                    if ( ! $error_msg) {
                        $error_msg = 'Wrong field ' . $name;
                    }
                    _re($error_msg, $name);
                    // In case when we see any validation rule is not OK - we stop checking further for this field
                    continue 2;
                }
            }
        }
        return $validate_ok;
    }