/** */ function tabs($tabs = [], $extra = []) { $extra['id'] = $extra['id'] ?: __FUNCTION__ . '_' . ++$this->_ids[__FUNCTION__]; $extra_by_id = []; if (isset($extra['by_id'])) { $extra_by_id = (array) $extra['by_id']; unset($extra['by_id']); } $links_prefix = $extra['links_prefix'] ?: 'tab_'; $headers = []; $items = []; foreach ((array) $tabs as $k => $v) { $desc_raw = null; $disabled = null; if (!is_array($v)) { $content = $v; $v = []; } else { $content = $v['content']; $desc_raw = $v['desc_raw']; $disabled = $v['disabled']; } $content = trim($content); $_extra = (array) $extra_by_id[$k] + (array) $extra; if ($_extra['hide_empty'] && !strlen($content)) { continue; } $name = $v['name'] ?: $k; $desc = $v['desc'] ?: (!$_extra['no_auto_desc'] ? ucfirst(str_replace('_', ' ', $name)) : $name); $id = preg_replace('~[^a-z0-9_-]+~i', '', $v['id'] ?: $links_prefix . $k); if (isset($_extra['selected'])) { $is_active = $_extra['selected'] == $k; } else { $is_active = ++$i == 1; } if (isset($_extra['totals'][$name])) { $v['badge'] = intval(isset($_extra['totals'][$name]['total']) ? $_extra['totals'][$name]['total'] : $_extra['totals'][$name]); } $badge = isset($v['badge']) ? ' <sup class="badge badge-' . ($v['class_badge'] ?: 'info') . '">' . $v['badge'] . '</sup>' : ''; if (!$_extra['no_headers']) { $class_head = $v['class_head'] ?: $_extra['class_head']; $class_head .= $_extra['class_add_head'] ? ' ' . $_extra['class_add_head'] : ''; if ($is_active) { $class_head = trim('active ' . $class_head); } $_extra_head = (array) $_extra['tab_head']; $_extra_head['class'] = $_extra_head['class'] ?: $class_head; $headers[] = '<li' . _attrs($_extra_head, ['id', 'class', 'style']) . '> <a ' . (!$disabled ? 'href="#' . fix_html_attr_id($id) . '" ' : '') . 'data-toggle="tab">' . ($desc_raw ?: t($desc)) . $badge . '</a> </li>'; } $class_body = $_extra['class'] ?: $v['class_body'] ?: $_extra['class_body']; $class_body = $class_body ?: 'tab-pane'; $class_body .= $_extra['class_add_body'] ? ' ' . $_extra['class_add_body'] : ''; if ($is_active || $_extra['show_all']) { $class_body = trim('active ' . $class_body); } else { $class_body = trim('fade ' . $class_body); } $_extra_body = (array) $_extra['tab_body']; $_extra_body['id'] = $_extra_body['id'] ?: $id; $_extra_body['class'] = $_extra_body['class'] ?: $class_body; $items[] = '<div' . _attrs($_extra_body, ['id', 'class', 'style']) . '>' . $content . '</div>'; } $body .= $headers ? '<ul id="' . $extra['id'] . '" class="nav nav-tabs">' . implode(PHP_EOL, (array) $headers) . '</ul>' . PHP_EOL : ''; $body .= '<div id="' . $extra['id'] . '_content" class="tab-content">' . implode(PHP_EOL, (array) $items) . '</div>'; return $body; }
function _attrs($extra, $names) { $body = []; $a = []; foreach ((array) $names as $name) { if (strlen($name) && isset($extra[$name])) { $a[$name] = $extra[$name]; } } // Try to find and allow all data-* and ng-* attributes automatically foreach ((array) $extra as $name => $val) { if (strpos($name, 'data-') === 0 || strpos($name, 'ng-') === 0) { $a[$name] = $val; } } // Custom html attributes forced with sub-array "attr" if (is_array($extra['attr'])) { foreach ($extra['attr'] as $name => $val) { if (strlen($name)) { $a[$name] = $val; } } } // Make sure that class attribute contains unique names and also cleanup extra spaces if (isset($a['class']) && strpos($a['class'], ' ') !== false) { $a['class'] = _attr_class_clean($a['class']); } foreach ($a as $name => $val) { if (is_array($val)) { $body[$name] = _htmlchars($name) . '="' . http_build_query(_htmlchars($val)) . '"'; } else { if (!strlen($val)) { continue; } if ($name == 'id') { $val = fix_html_attr_id($val); } $body[$name] = _htmlchars($name) . '="' . _htmlchars($val) . '"'; } } return $body ? ' ' . implode(' ', $body) : ''; }
/** * Render result form html, gathered by row functions * Params here not required, but if provided - will be passed to form_begin() */ function render($extra = [], $replace = []) { if (isset($this->_rendered)) { return $this->_rendered; } if (DEBUG_MODE) { $ts = microtime(true); } _class('core_events')->fire('form.before_render', [$extra, $replace, $this]); $this->_extra = $extra; $on_before_render = isset($extra['on_before_render']) ? $extra['on_before_render'] : $this->_on['on_before_render']; 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'] || $this->_params['no_form']; $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; if ($csrf_protect && $is_current_form && !$csrf_guard->validate($_POST[$this->CONF_CSRF_NAME])) { // 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']; if (is_callable($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 + (array) $extra_override['form_begin'], $r); } if (!isset($this->_body['form_end'])) { $this->form_end($extra + (array) $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'] ?: $v['extra']['name']; } } $all_errors = common()->_get_error_messages(); foreach ((array) $this->_body as $k => $v) { if (!is_array($v)) { continue; } $_extra = (array) $v['extra'] + (array) $extra_override[$v['extra']['name']]; $_replace = (array) $r + (array) $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'] ?: 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'] = _class('html')->tabs($tabs, (array) $this->_params['tabs'] + (array) $tabs_extra); } if ($this->_params['show_alerts']) { $errors = common()->_get_error_messages(); if ($errors) { $e = []; foreach ((array) $errors as $msg) { $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'] || $this->_params['return_array']) { $data = []; foreach ($this->_body as $k => $v) { $name = fix_html_attr_id($v['extra']['name'] ?: $v['extra']['id'] ?: $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']; } else { 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'] ?: ($this->_params['css_framework'] ?: conf('css_framework')); $extra['css_framework'] = $css_framework; $this->_rendered = _class('html5fw')->form_render_out($this->_rendered, $extra, $r, $this); $on_after_render = isset($extra['on_after_render']) ? $extra['on_after_render'] : $this->_on['on_after_render']; 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; }