/** * Call validator method * * @param string $method * @param array $params * @param array $options * @param array $neighbouring_values * @return array */ public static function method($method, $value, $params = [], $options = [], $neighbouring_values = []) { $method = factory::method($method); $params = $params ?? []; $params['options'] = $options; $params['neighbouring_values'] = $neighbouring_values; return factory::model($method[0], true)->{$method[1]}($value, $params); }
/** * Acl handle * * @param string $acl_key * @param string $acl_type * @param array $data * @param array $options * @return boolean */ public function acl_handle($acl_key, $acl_type, &$data, &$options) { // if we have no acls we must return null if (empty($this->data[$acl_key][$acl_type])) { return null; } // sort array_key_sort($this->data[$acl_key][$acl_type], ['order' => SORT_ASC], ['order' => SORT_NUMERIC]); // process one by one foreach ($this->data[$acl_key][$acl_type] as $k => $v) { $method = factory::method($v['method'], null, true); $result = $method[0]->{$method[1]}($acl_key, $acl_type, $data, $options); if (!$result) { debug::$data['acls'][$acl_key][$acl_type] = 'Failed'; return false; } else { debug::$data['acls'][$acl_key][$acl_type] = 'Success'; } } return true; }
/** * Initialize * * @param array $options */ public static function init($options = []) { // default options self::$defaut_options = ['language_code' => 'sys', 'locale' => 'en_CA.UTF-8', 'timezone' => 'America/Toronto', 'server_timezone' => application::get('php.date.timezone'), 'date' => 'Y-m-d', 'time' => 'H:i:s', 'datetime' => 'Y-m-d H:i:s', 'timestamp' => 'Y-m-d H:i:s.u', 'amount_frm' => 20, 'amount_fs' => 40, 'settings' => ['currency_codes' => []], 'locale_locales' => [], 'locale_locale_js' => null, 'locale_set_name' => null, 'locale_options' => [], 'locale_override_class' => null]; // settings from config files $config = application::get('flag.global.format'); // settings from user account $entity = entity::groupped('format'); // merge all of them together self::$options = array_merge_hard(self::$defaut_options, $config, i18n::$options, $entity, $options); // fix utf8 self::$options['locale'] = str_replace(['utf8', 'utf-8'], 'UTF-8', self::$options['locale']); // generate a list of available locales $locale_settings = self::set_locale(self::$options['locale'], self::$defaut_options['locale']); self::$options = array_merge_hard(self::$options, $locale_settings); // fix values self::$options['amount_frm'] = (int) self::$options['amount_frm']; self::$options['amount_fs'] = (int) self::$options['amount_fs']; self::$options['locale_options']['mon_thousands_sep'] = self::$options['locale_options']['mon_thousands_sep'] ?? ','; self::$options['locale_options']['mon_decimal_point'] = self::$options['locale_options']['mon_decimal_point'] ?? '.'; if (empty(self::$options['locale_options']['mon_grouping'])) { self::$options['locale_options']['mon_grouping'] = [3, 3]; } // load data from models if (!empty(self::$options['model'])) { foreach (self::$options['model'] as $k => $v) { $method = factory::method($v, null); self::$options['settings'][$k] = factory::model($method[0], true)->{$method[1]}(); } unset(self::$options['model']); } // push js format version to frontend if (!empty(self::$options['locale_override_class'])) { $locale_override_class = self::$options['locale_override_class']; $locale_override_class::js(); } }
/** * Options * * @param array $data * @param array $options_map * @param array $options * @return array */ public static function options($data, $options_map, $options = []) { $i18n = []; $format = []; $options_map_new = []; $format_methods = []; foreach ($options_map as $k => $v) { if (is_array($v)) { $options_map_new[$k] = $v['field']; if (!empty($options['i18n']) && !empty($v['i18n']) && !array_key_exists('i18n', $v)) { $i18n[$k] = !empty($options['i18n']); } if (!empty($v['format'])) { $format[$k] = $v; $format_methods[$k] = factory::method($v['format'], 'format'); } } else { $options_map_new[$k] = $v; if (!empty($options['i18n'])) { $i18n[$k] = true; } } } // we need to i18n and process formats if (!empty($i18n) || !empty($format)) { foreach ($data as $k => $v) { // localize if (!empty($i18n)) { foreach ($i18n as $k2 => $v2) { // we need to skip few things if (!isset($data[$k][$k2])) { continue; } $data[$k][$k2] = i18n(null, $data[$k][$k2]); } } // format if (!empty($format)) { foreach ($format as $k2 => $v2) { $data[$k][$k2] = call_user_func_array([$format_methods[$k2][0], $format_methods[$k2][1]], [$data[$k][$k2], $v2['format_options'] ?? []]); } } } } // inactive $inactive_message = '*Inactive'; $i18n_inactive = !empty($options['i18n']) ? i18n(null, $inactive_message) : $inactive_message; foreach ($data as $k => $v) { if (!empty($options['column_prefix']) && !empty($v[$options['column_prefix'] . 'inactive'])) { $options_map_new[$options['column_prefix'] . 'inactive'] = 'inactive'; $options_map_new['__prepend'] = 'name'; $data[$k]['__prepend'] = $i18n_inactive; } } return remap($data, $options_map_new); }
/** * Render elements value * * @param array $options * @param mixed $value * @param array $neighbouring_values * @return string * @throws Exception */ public function render_element_value(&$options, $value = null, &$neighbouring_values = []) { // field name and values_key $options['options']['field_name'] = $options['options']['details_field_name'] ?? $options['options']['name']; $options['options']['field_values_key'] = implode('[::]', $options['options']['field_values_key'] ?? [$options['options']['field_name']]); // custom renderer if (!empty($options['options']['custom_renderer'])) { $method = factory::method($options['options']['custom_renderer'], null, true); $options_custom_renderer = $options; call_user_func_array($method, [&$this, &$options, &$value, &$neighbouring_values]); } // handling override_field_value method if (!empty($this->wrapper_methods['override_field_value']['main'])) { call_user_func_array($this->wrapper_methods['override_field_value']['main'], [&$this, &$options, &$value, &$neighbouring_values]); } $result_options = $options['options']; $options['options']['value'] = $value; array_key_extract_by_prefix($result_options, 'label_'); $element_expand = !empty($result_options['expand']); $html_suffix = $result_options['html_suffix'] ?? ''; // unset certain keys unset($result_options['order'], $result_options['required'], $result_options['html_suffix']); // processing options $flag_select_or_autocomplete = !empty($result_options['options_model']) || !empty($result_options['options']); if (!empty($result_options['options_model'])) { if (empty($result_options['options_params'])) { $result_options['options_params'] = []; } if (empty($result_options['options_options'])) { $result_options['options_options'] = []; } $result_options['options_options']['i18n'] = $result_options['options_options']['i18n'] ?? true; $result_options['options_options']['acl'] = $result_options['options_options']['acl'] ?? $this->acl; if (empty($result_options['options_depends'])) { $result_options['options_depends'] = []; } // options depends & params $this->process_params_and_depends($result_options['options_depends'], $neighbouring_values, $options, true); $this->process_params_and_depends($result_options['options_params'], $neighbouring_values, $options, false); $result_options['options_params'] = array_merge_hard($result_options['options_params'], $result_options['options_depends']); // we do not need options for autocomplete if (strpos($result_options['method'], 'autocomplete') === false) { $skip_values = []; if (!empty($options['options']['details_key'])) { if (!empty($options['options']['details_parent_key'])) { $temp_key = $options['options']['details_parent_key'] . '::' . $options['options']['details_key']; if (!empty($this->misc_settings['details_unique_select'][$temp_key][$options['options']['details_field_name']][$options['options']['__parent_row_number']])) { $skip_values = array_keys($this->misc_settings['details_unique_select'][$temp_key][$options['options']['details_field_name']][$options['options']['__parent_row_number']]); } } else { if (!empty($this->misc_settings['details_unique_select'][$options['options']['details_key']][$options['options']['details_field_name']])) { $skip_values = array_keys($this->misc_settings['details_unique_select'][$options['options']['details_key']][$options['options']['details_field_name']]); } } } $result_options['options'] = object_data_common::process_options($result_options['options_model'], $this, $result_options['options_params'], $value, $skip_values, $result_options['options_options']); } else { // we need to inject form id into autocomplete $result_options['form_id'] = "form_{$this->form_link}_form"; } } // by default all selects are searchable if not specified otherwise if ($flag_select_or_autocomplete) { $result_options['searchable'] = $result_options['searchable'] ?? false; } // different handling for different type switch ($options['type']) { case 'container': $options_container = $options; //$options_container['previous_data'] = $v; // todo: pass $form_data_key from parent $options_container['previous_key'] = $options['previous_key']; // render container $temp_container_value = $this->render_container($data['fm_part_child_container_name'], $parents, $options_container); if (!empty($html_expand)) { // get part id $temp_id = $this->id('part_details', ['part_name' => $data['fm_part_name'], 'part_id' => $options_container['previous_id']]); $temp_id_div_inner = $temp_id . '_html_expand_div_inner'; $temp_expand_div_inner = ['id' => $temp_id_div_inner, 'style' => 'display: none;', 'value' => $temp_container_value]; $temp_expand_div_a = ['href' => 'javascript:void(0);', 'onclick' => "numbers.element.toggle('{$temp_id_div_inner}');", 'value' => '+ / -']; $temp_expand_div_outer = ['align' => 'left', 'value' => html::a($temp_expand_div_a) . '<br />' . html::div($temp_expand_div_inner)]; $value = html::div($temp_expand_div_outer); } else { $value = $temp_container_value; } $result_options['value'] = $value; break; case 'field': $element_method = $result_options['method'] ?? 'html::input'; if (strpos($element_method, '::') === false) { $element_method = 'html::' . $element_method; } // value in special order $flag_translated = false; if (in_array($element_method, ['html::a', 'html::submit', 'html::button', 'html::button2'])) { // translate value $result_options['value'] = i18n($result_options['i18n'] ?? null, $result_options['value'] ?? null); // process confirm_message $result_options['onclick'] = $result_options['onclick'] ?? ''; if (!empty($result_options['confirm_message'])) { $result_options['onclick'] .= 'return confirm(\'' . strip_tags(i18n(null, $result_options['confirm_message'])) . '\');'; } // processing onclick for buttons if (in_array($element_method, ['html::submit', 'html::button', 'html::button2'])) { if (!empty($result_options['onclick']) && strpos($result_options['onclick'], 'this.form.submit();') !== false) { $result_options['onclick'] = str_replace('this.form.submit();', "numbers.form.trigger_submit(this.form);", $result_options['onclick']) . ' return true;'; } else { if (empty($result_options['onclick'])) { $result_options['onclick'] .= 'numbers.form.trigger_submit_on_button(this); return true;'; } else { $result_options['onclick'] = 'numbers.form.trigger_submit_on_button(this); ' . $result_options['onclick']; } } } $flag_translated = true; // icon if (!empty($result_options['icon'])) { $result_options['value'] = html::icon(['type' => $result_options['icon']]) . ' ' . $result_options['value']; } // accesskey if (isset($result_options['accesskey'])) { $accesskey = explode('::', i18n(null, 'accesskey::' . $result_options['name'] . '::' . $result_options['accesskey'], ['skip_translation_symbol' => true])); $result_options['accesskey'] = $accesskey[2]; $result_options['title'] = ($result_options['title'] ?? '') . ' ' . i18n(null, 'Shortcut Key: ') . $accesskey[2]; } } else { if (in_array($element_method, ['html::div', 'html::span'])) { if (!empty($result_options['i18n'])) { $result_options['value'] = i18n($result_options['i18n'] ?? null, $result_options['value'] ?? null); $flag_translated = true; } } else { // editable fields $result_options['value'] = $value; // if we need to empty value, mostly for password fields if (!empty($result_options['empty_value'])) { $result_options['value'] = ''; } // we need to empty zero integers and sequences, before format if (($result_options['php_type'] ?? '') == 'integer' && ($result_options['type'] ?? '') != 'boolean' && ($result_options['domain'] ?? '') != 'counter' && 'counter' && empty($result_options['value'])) { $result_options['value'] = ''; } // format, not for selects/autocompletes/presets if (!$flag_select_or_autocomplete) { if (!empty($result_options['format'])) { if (!empty($this->errors['fields'][$result_options['error_name']]) && empty($this->errors['formats'][$result_options['error_name']])) { // nothing } else { $result_options['format_options'] = $result_options['format_options'] ?? []; if (!empty($result_options['format_depends'])) { $this->process_params_and_depends($result_options['format_depends'], $neighbouring_values, $options, true); $result_options['format_options'] = array_merge_hard($result_options['format_options'], $result_options['format_depends']); } $method = factory::method($result_options['format'], 'format'); $result_options['value'] = call_user_func_array([$method[0], $method[1]], [$result_options['value'], $result_options['format_options']]); } } } // align if (!empty($result_options['align'])) { $result_options['style'] = ($result_options['style'] ?? '') . 'text-align:' . $result_options['align'] . ';'; } // processing persistent if (!empty($result_options['persistent']) && $this->values_loaded) { if ($result_options['persistent'] === 'if_set') { $original_value = $detail = array_key_get($this->original_values, $result_options['values_key']); if (!empty($original_value)) { $result_options['readonly'] = true; } } else { if (count($result_options['values_key']) == 1) { // parent record $result_options['readonly'] = true; } else { if (empty($result_options['__new_row'])) { // details $temp = $result_options['values_key']; array_pop($temp); $detail = array_key_get($this->original_values, $temp); if (!empty($detail)) { $result_options['readonly'] = true; } } } } } // maxlength if (in_array($result_options['type'] ?? '', ['char', 'varchar']) && !empty($result_options['length'])) { $result_options['maxlength'] = $result_options['length']; } // global readonly if (!empty($this->misc_settings['global']['readonly']) && empty($result_options['navigation'])) { $result_options['readonly'] = true; } // title if (isset($options['options']['label_name'])) { $result_options['title'] = ($result_options['title'] ?? '') . ' ' . strip_tags(i18n(null, $options['options']['label_name'])); } } } // translate place holder if (array_key_exists('placeholder', $result_options)) { if (!empty($result_options['placeholder'])) { $result_options['placeholder'] = strip_tags(i18n(null, $result_options['placeholder'])); } } else { if (!empty($result_options['validator_method']) && empty($result_options['value'])) { $temp = object_validator_base::method($result_options['validator_method'], $result_options['value'], $result_options['validator_params'] ?? [], $options['options'], $neighbouring_values); if ($flag_select_or_autocomplete) { $placeholder = $temp['placeholder_select']; } else { $placeholder = $temp['placeholder']; } if (!empty($placeholder)) { $result_options['placeholder'] = strip_tags(i18n(null, $placeholder)); } } } // events foreach (numbers_frontend_html_class_html5::$events as $e) { if (!empty($result_options['readonly'])) { // important - readonly emenets cannot have events unset($result_options[$e]); } else { if (!empty($result_options[$e])) { $result_options[$e] = str_replace('this.form.submit();', 'numbers.form.trigger_submit(this);', $result_options[$e]); $result_options[$e] = str_replace('this.form.extended.', $this->misc_settings['extended_js_class'] . '.', $result_options[$e]); } } } break; case 'html': $element_method = null; break; default: throw new Exception('Render detail type: ' . $data['fm_part_type']); } // handling html_method if (isset($element_method)) { $method = factory::method($element_method, 'html'); $field_method_object = factory::model($method[0], true); // todo: unset non html attributes $value = $field_method_object->{$method[1]}($result_options); // building navigation if (!empty($result_options['navigation'])) { $name = 'navigation[' . $result_options['name'] . ']'; $temp = '<table width="100%" dir="ltr">'; // always left to right $temp .= '<tr>'; $temp .= '<td width="1%">' . html::button2(['name' => $name . '[first]', 'value' => html::icon(['type' => 'step-backward']), 'onclick' => 'numbers.form.trigger_submit_on_button(this);', 'title' => i18n(null, 'First')]) . '</td>'; $temp .= '<td width="1%"> </td>'; $temp .= '<td width="1%">' . html::button2(['name' => $name . '[previous]', 'value' => html::icon(['type' => 'caret-left']), 'onclick' => 'numbers.form.trigger_submit_on_button(this);', 'title' => i18n(null, 'Previous')]) . '</td>'; $temp .= '<td width="1%"> </td>'; $temp .= '<td width="90%">' . $value . '</td>'; $temp .= '<td width="1%"> </td>'; $temp .= '<td width="1%">' . html::button2(['name' => $name . '[refresh]', 'value' => html::icon(['type' => 'refresh']), 'onclick' => 'numbers.form.trigger_submit_on_button(this);', 'title' => i18n(null, 'Refresh')]) . '</td>'; $temp .= '<td width="1%"> </td>'; $temp .= '<td width="1%">' . html::button2(['name' => $name . '[next]', 'value' => html::icon(['type' => 'caret-right']), 'onclick' => 'numbers.form.trigger_submit_on_button(this);', 'title' => i18n(null, 'Next')]) . '</td>'; $temp .= '<td width="1%"> </td>'; $temp .= '<td width="1%">' . html::button2(['name' => $name . '[last]', 'value' => html::icon(['type' => 'step-forward']), 'onclick' => 'numbers.form.trigger_submit_on_button(this);', 'title' => i18n(null, 'Last')]) . '</td>'; $temp .= '</tr>'; $temp .= '</table>'; $value = $temp; } } // html suffix and prefix if (!empty($html_suffix)) { $value .= $html_suffix; } // if we need to display settings if (application::get('flag.numbers.frontend.html.form.show_field_settings')) { $id_original = $result_options['id'] . '__settings_original'; $id_modified = $result_options['id'] . '__settings_modified'; $value .= html::a(['href' => 'javascript:void(0);', 'onclick' => "\$('#{$id_original}').toggle();", 'value' => html::label2(['type' => 'primary', 'value' => count($options['options'])])]); $value .= html::a(['href' => 'javascript:void(0);', 'onclick' => "\$('#{$id_modified}').toggle();", 'value' => html::label2(['type' => 'warning', 'value' => count($result_options)])]); $value .= '<div id="' . $id_original . '" style="display:none; position: absolute; text-align: left; width: 500px; z-index: 32000;">' . print_r2($options['options'], true) . '</div>'; $value .= '<div id="' . $id_modified . '" style="display:none; position: absolute; text-align: left; width: 500px; z-index: 32000;">' . print_r2($result_options, true) . '</div>'; } // we need to put original options back if (!empty($options['options']['custom_renderer'])) { $options = $options_custom_renderer; } return $value; }
/** * Data default renderer * * @return string */ private final function render_data_default() { $result = ''; // if we have no rows we display a messsage if ($this->num_rows == 0) { return html::message(['type' => 'warning', 'options' => [i18n(null, object_content_messages::no_rows_found)]]); } $counter = 1; $table = ['header' => [], 'options' => []]; // action flags $actions = []; if (object_controller::can('record_view')) { $actions['view'] = true; } // generate columns foreach ($this->columns as $k => $v) { // if we can not view we skip action column if (empty($actions) && $k == 'action') { continue; } $table['header'][$k] = ['value' => i18n(null, $v['name']), 'nowrap' => true, 'width' => $v['width'] ?? null]; } // generate rows foreach ($this->rows as $k => $v) { // process all columns first $row = []; foreach ($this->columns as $k2 => $v2) { // if we can not view we skip action column if (empty($actions) && $k2 == 'action') { continue; } $value = []; // create cell properties foreach (['width', 'align'] as $v3) { if (isset($v2[$v3])) { $value[$v3] = $v2[$v3]; } } // process rows if ($k2 == 'action') { $value['value'] = []; if (!empty($actions['view'])) { $mvc = application::get('mvc'); $pk = extract_keys($this->model_object->pk, $v); $url = $mvc['controller'] . '/_edit?' . http_build_query2($pk); $value['value'][] = html::a(['value' => i18n(null, 'View'), 'href' => $url]); } $value['value'] = implode(' ', $value['value']); } else { if ($k2 == 'row_number') { $value['value'] = format::id($counter) . '.'; } else { if ($k2 == 'offset_number') { $value['value'] = format::id($this->offset + $counter) . '.'; } else { if (!empty($v2['options_model'])) { if (strpos($v2['options_model'], '::') === false) { $v2['options_model'] .= '::options'; } $params = $v2['options_params'] ?? []; if (!empty($v2['options_depends'])) { foreach ($v2['options_depends'] as $k0 => $v0) { $params[$k0] = $v[$v0]; } } $crypt_object = new crypt(); $hash = $crypt_object->hash($v2['options_model'] . serialize($params)); if (!isset($this->cached_options[$hash])) { $method = factory::method($v2['options_model'], null, true); $this->cached_options[$hash] = call_user_func_array($method, [['where' => $params]]); } if (isset($this->cached_options[$hash][$v[$k2]])) { $value['value'] = $this->cached_options[$hash][$v[$k2]]['name']; } else { $value['value'] = null; } } else { if (!empty($v2['options']) && !is_array($v[$k2])) { if (isset($v2['options'][$v[$k2]])) { $value['value'] = $v2['options'][$v[$k2]]['name']; } else { $value['value'] = null; } } else { if (isset($v[$k2])) { $value['value'] = $v[$k2]; } else { $value['value'] = null; } } } } } } // put value into row if (!empty($v2['format'])) { $format_options = $v2['format_options'] ?? []; if (!empty($v2['format_depends'])) { $format_depends = $v2['format_depends']; $this->process_params_and_depends($format_depends, $v); $format_options = array_merge_hard($format_options, $format_depends); } $method = factory::method($v2['format'], 'format'); $value['value'] = call_user_func_array([$method[0], $method[1]], [$value['value'], $format_options]); } $row[$k2] = $value; } // put processed columns though user defined function if (method_exists($this, 'render_data_rows')) { $table['options'][$counter] = $this->render_data_rows($row, $v); } else { $table['options'][$counter] = $row; } $counter++; } return html::table($table); }