/** * Process single column * * @param string $column_name * @param array $column_options * @param array $data * @param array $options * @return array */ public static function process_single_column($column_name, $column_options, $data, $options = []) { $result = []; // process domain if (!empty($options['process_domains'])) { $temp = [$column_name => $column_options]; $temp = object_data_common::process_domains($temp); $column_options = $temp[$column_name]; } // if we ignoring not set fields if (!empty($options['ignore_not_set_fields']) && !array_key_exists($column_name, $data)) { return $result; } // processing $value = $data[$column_name] ?? null; if (is_array($value)) { $result2 = []; foreach ($value as $k => $v) { $temp = self::process_single_column_type($column_name, $column_options, $v, ['ignore_defaults' => $options['ignore_defaults'] ?? false]); if (array_key_exists($column_name, $temp) && $temp[$column_name] !== null) { $result2[] = $temp[$column_name]; } } $result[$column_name] = $result2; } else { $result = self::process_single_column_type($column_name, $column_options, $value, ['ignore_defaults' => $options['ignore_defaults'] ?? false]); } return $result; }
/** * Get filtered by permission * * @param array $options * @return array */ public function optmultis_filtered_by_permission($options = []) { $data = $this->get(['where' => ['sm_controller_acl_permission' => 1, 'sm_controller_inactive' => 0]]); $optmultis_map = $this->optmultis_map; return object_data_common::optmultis($data, $optmultis_map, $options); }
/** * Build options based on parameters, this must be used to have * consistencies in selects * * @param array $data * @param array $options_map * @param array $orderby * @param array $options * @return array */ public static function build_options($data, $options_map, $orderby, $options) { $data = object_data_common::options($data, $options_map, $options); // sorting if (!empty($options['i18n']) && $options['i18n'] !== 'skip_sorting') { // mandatory sorting if localized array_key_sort($data, ['name' => SORT_ASC], ['name' => SORT_NATURAL]); } else { if (empty($orderby)) { array_key_sort($data, ['name' => SORT_ASC], ['name' => SORT_NATURAL]); } } return $data; }
/** * Merge data to database * * @param array $data * @param array $options * @param object $form * @return array */ public function merge($data, $options = [], &$form = null) { $result = ['success' => false, 'error' => [], 'warning' => [], 'deleted' => false, 'inserted' => false, 'new_serials' => [], 'options_model' => []]; do { // start transaction $this->primary_model->db_object->begin(); // load data from database $original = []; if (array_key_exists('original', $options)) { $original = $options['original']; } else { // load data from database // assemble primary key $pk = []; $full_pk = true; foreach ($this->data['pk'] as $v) { if (isset($data[$v])) { $pk[$v] = $data[$v]; } else { $full_pk = false; } } // load data if (!empty($pk) && $full_pk) { $original = $this->get(['where' => $pk, 'single_row' => true]); } } // validate optimistic lock if ($this->primary_model->optimistic_lock && !empty($original)) { if (($data[$this->primary_model->optimistic_lock_column] ?? '') !== $original[$this->primary_model->optimistic_lock_column]) { $result['error'][] = object_content_messages::optimistic_lock; break; } } // we need to validate options_model if (!empty($options['options_model'])) { // get existing values foreach ($options['options_model'] as $k => $v) { // current values $value = array_key_get($data, $v['key']); if ($value !== null && (is_string($value) && $value !== '')) { if (is_array($value)) { $value = array_keys($value); } else { $value = [$value]; } $options['options_model'][$k]['current_values'] = $value; } else { $options['options_model'][$k]['current_values'] = null; } // we skip if we have no values if (empty($options['options_model'][$k]['current_values'])) { unset($options['options_model'][$k]); continue; } // existing values $value = array_key_get($original, $v['key']); if ($value !== null) { if (is_array($value)) { $value = array_keys($value); } else { $value = [$value]; } $options['options_model'][$k]['existing_values'] = $value; } else { $options['options_model'][$k]['existing_values'] = null; } } // validate object_data $sql_options = []; foreach ($options['options_model'] as $k => $v) { // we skip inactive model validations if ($v['options_model'] == 'object_data_model_inactive') { continue; } // process models $temp = explode('::', $v['options_model']); $model = factory::model($temp[0], true); if (empty($temp[1])) { $temp[1] = 'options'; } if ($model->initiator_class == 'object_data' || $model->initiator_class == 'object_table' && !in_array($temp[1], ['options', 'options_active'])) { $temp_options = array_keys(object_data_common::process_options($v['options_model'], null, $v['options_params'], $v['existing_values'])); // difference between arrays $diff = array_diff($v['current_values'], $temp_options); if (!empty($diff)) { $result['options_model'][$k] = 1; } } else { if ($model->initiator_class == 'object_table' && in_array($temp[1], ['options', 'options_active'])) { // last element in the pk is a field $pk = $model->pk; $last = array_pop($pk); // handling inactive $options_active = []; if ($temp[1] == 'options_active') { $options_active = $model->options_active ? $model->options_active : [$model->column_prefix . 'inactive' => 0]; } $sql_options[$k] = ['model' => $temp[0], 'field' => $last, 'params' => $v['options_params'], 'values' => $v['current_values'], 'existing_values' => $v['existing_values'], 'options_active' => $options_active]; } } } // validating options if (!empty($sql_options)) { $sql_model = new object_table_validator(); $sql_result = $sql_model->validate_options_multiple($sql_options); if (!empty($sql_result['discrepancies'])) { foreach ($sql_result['discrepancies'] as $k => $v) { $result['options_model'][$k] = 1; } } } // we roll back if we have errors if (!empty($result['options_model'])) { break; } } // comapare main row $this->timestamp = format::now('timestamp'); $temp = $this->compare_one_row($data, $original, $this->data, ['flag_delete_row' => $options['flag_delete_row'] ?? false, 'flag_main_record' => true]); // if we goe an error if (!empty($temp['error'])) { $result['error'] = $temp['error']; break; } // we display warning if form has not been changed if (empty($temp['data']['total'])) { $result['warning'][] = object_content_messages::no_changes; break; } // insert history if (!empty($temp['data']['history'])) { foreach ($temp['data']['history'] as $k => $v) { $temp2 = $this->primary_model->db_object->insert($k, $v); if (!$temp2['success']) { $result['error'] = $temp2['error']; goto error; } } } // audit if (!empty($temp['data']['audit'])) { // we need to put relation into pk if (!empty($this->primary_model->relation['field'])) { $temp['data']['audit']['pk'][$this->primary_model->relation['field']] = $temp['new_serials'][$this->primary_model->relation['field']] ?? $data[$this->primary_model->relation['field']] ?? $original[$this->primary_model->relation['field']]; } // merge $temp2 = factory::model($this->primary_model->audit_model, true)->merge($temp['data']['audit'], ['changes' => $temp['data']['total']]); if (!$temp2['success']) { $result['error'] = $temp2['error']; break; } } // if we got here we can commit $result['success'] = true; $result['deleted'] = $temp['data']['deleted']; $result['inserted'] = $temp['data']['inserted']; $result['updated'] = $temp['data']['updated']; $result['new_serials'] = $temp['new_serials']; // commit transaction $this->primary_model->db_object->commit(); return $result; } while (0); // we roll back on error error: $this->primary_model->db_object->rollback(); return $result; }
/** * 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; }
/** * Multi level options * * @see $this->get() */ public function optmultis($options = []) { if (empty($this->optmultis_map)) { return []; } else { $data = $this->get($options); $optmultis_map = $this->optmultis_map; return object_data_common::optmultis($data, $optmultis_map); } }
/** * Constructor * * @param string $list_link * @param array $options */ public function __construct($options = []) { $this->options = $options; // processing model if (!empty($this->model)) { $this->model_object = factory::model($this->model); } if (empty($this->columns) && !empty($this->model)) { $this->columns = $this->model_object->columns; } // check if we have columns if (empty($this->columns)) { throw new Exception('List must have columns!'); } // process domains foreach ($this->columns as $k => $v) { $temp = object_data_common::process_domains(['options' => $v]); $this->columns[$k] = $temp['options']; } // limit $limit = intval($options['input']['limit'] ?? 0); if ($limit > 0) { $this->limit = $limit; } // we need to set maximum limit if we are exporting if (!empty($this->options['input']['submit_export']) && !empty($this->options['input']['export']['format'])) { $this->limit = PHP_INT_MAX; } // offset $offset = intval($options['input']['offset'] ?? 0); if ($offset > 0) { $this->offset = $offset; } // filter $where = []; if (!empty($this->options['input']['filter'])) { $where = numbers_frontend_html_list_filter::where($this); if (!empty($where)) { $this->filtered = true; } } // sort if (!empty($this->options['input']['sort'])) { $this->orderby = []; foreach ($this->options['input']['sort'] as $k => $v) { if (!empty($v['column']) && !empty($this->columns[$v['column']])) { $this->orderby[$v['column']] = $v['order'] ?? SORT_ASC; } else { if (!empty($v['column']) && $v['column'] == 'full_text_search' && !empty($this->filter['full_text_search'])) { $this->orderby['full_text_search'] = $v['order'] ?? SORT_ASC; } } } } // datasources, count first if (empty($this->datasources['count']) && !empty($this->model)) { $this->datasources['count'] = ['model' => 'numbers_frontend_html_list_model_datasource_count', 'options' => ['model' => $this->model, 'where' => $where]]; } else { if (!empty($this->datasources['count'])) { $this->datasources['count'] = ['model' => $this->datasources['count'], 'options' => ['where' => $where]]; } } // datasources, data second if (empty($this->datasources['data']) && !empty($this->model)) { $this->datasources['data'] = ['model' => 'numbers_frontend_html_list_model_datasource_data', 'options' => ['model' => $this->model, 'offset' => $this->offset, 'limit' => $this->limit, 'orderby' => $this->orderby, 'where' => $where]]; } else { if (!empty($this->datasources['data'])) { $this->datasources['data'] = ['model' => $this->datasources['data'], 'options' => ['offset' => $this->offset, 'limit' => $this->limit, 'orderby' => $this->orderby, 'where' => $where]]; } } // actions if (!empty($this->options['actions'])) { $this->actions = array_merge($this->actions, $this->options['actions']); } }