6 private const CONFIG_FD =
'fd:dbx|config';
7 private const SERVER_FD =
'fd:dbxAdmin|server';
8 private const NESTED_SECTIONS = array(
'ftp',
'mail');
9 private const SQL_DB_SECTION =
'db';
10 private const SERVER_FIELD_NAMES = array(
'activ',
'type',
'host',
'dbname',
'user',
'pass',
'port');
12 private array $config = array();
13 private string $activeTab =
'config';
14 private string $activeSubTab =
'';
15 private array $sectionMessages = array();
16 private array $groupMessages = array();
19 private function texts() {
23 $texts = new \dbxForm();
24 $texts->set_form_help_enabled(
false);
25 $texts->_fd = self::CONFIG_FD;
26 $texts->load_fd_messages();
27 $this->texts = $texts;
31 private function text(
string $key,
string $default =
''):
string {
32 return $this->texts()->get_fd_message($key, $default);
35 private function format_text(
string $key, array $values = array(),
string $default =
''):
string {
36 return $this->texts()->format_fd_message($key, $values, $default);
39 public function run() {
40 $this->config =
dbx()->get_config(
'dbx');
41 if (!is_array($this->config)) {
42 $this->config = array();
45 $this->read_active_state();
46 $arraySections = $this->array_sections();
48 if ($this->activeTab !==
'config'
49 && $this->activeTab !== self::SQL_DB_SECTION
50 && !in_array($this->activeTab, $arraySections,
true)) {
51 $this->activeTab =
'config';
58 'content' => $this->render_config_form(),
61 'id' => self::SQL_DB_SECTION,
62 'label' => $this->section_label(self::SQL_DB_SECTION),
63 'content' => $this->render_array_section(self::SQL_DB_SECTION),
67 foreach ($arraySections as $section) {
70 'label' => $this->section_label($section),
71 'content' => $this->render_array_section($section),
75 $tabsHtml = $this->render_tabs($tabs,
'dbx_config', $this->activeTab);
76 $help =
dbx()->get_include_obj(
'dbxAdminHelp',
'dbxAdmin');
78 return $this->get_tpl(
'config-dbx-shell', array_merge(
79 $help->moduleBarTemplateData(
'config',
'',
'',
'', $this->text(
'module_subtitle')),
81 'frame_id' =>
'dbx_target_config_dbx',
82 'frame_panel_class' =>
'dbx-config-dbx',
83 'frame_form_open' =>
'',
84 'frame_form_close' =>
'',
86 'frame_body_class' =>
'',
87 'frame_body_head' =>
'',
88 'frame_body_tail' =>
'',
89 'frame_panel_attrs' =>
'',
90 'content' => $tabsHtml,
95 private function tpl() {
96 return dbx()->get_system_obj(
'dbxTPL');
99 private function get_tpl(
string $tpl, array $data = array()): string {
100 return $this->tpl()->get_tpl(
'dbxAdmin|' .
$tpl, $data);
103 private function get_dbx_tpl(
string $tpl, array $data = array()): string {
104 return $this->tpl()->get_tpl(
'dbx|' .
$tpl, $data);
107 private function base_action(): string {
108 return
'?dbx_modul=dbxAdmin&dbx_run1=config&dbx_run2=edit&xmodul=dbx';
111 private function read_active_state(): void {
112 $tab = (string)(
$_POST[
'activeTab'] ??
dbx()->get_modul_var(
'activeTab',
'config',
'parameter'));
113 $subTab = (string)(
$_POST[
'activeSubTab'] ??
dbx()->get_modul_var(
'activeSubTab',
'',
'parameter'));
119 $this->activeTab = $tab;
120 $this->activeSubTab = $subTab;
123 private function array_sections(): array {
126 foreach (self::NESTED_SECTIONS as $section) {
127 if (isset($this->config[$section]) && is_array($this->config[$section])) {
135 private function render_tabs(array $tabs,
string $idPrefix,
string $activeId): string {
139 foreach ($tabs as $tab) {
140 $tabId = (string)($tab[
'id'] ??
'tab');
141 $id = $this->safe_id($idPrefix .
'_' . $tabId);
142 $label = (string)($tab[
'label'] ??
'');
143 $content = (string)($tab[
'content'] ??
'');
144 $active = ($tabId === $activeId);
146 $tabHtml .= $this->get_tpl(
'config-dbx-tab', array(
148 'label' => $this->h($label),
149 'active' => $active ?
'active' :
'',
150 'selected' => $active ?
'true' :
'false',
153 $paneHtml .= $this->get_tpl(
'config-dbx-pane', array(
156 'active' => $active ?
'show active' :
'',
160 return $this->get_tpl(
'config-dbx-tabs', array(
162 'panes' => $paneHtml,
166 private function render_config_form(): string {
167 $data = $this->config_form_data();
168 $form = $this->new_form(
'config_dbx_general', $data, $this->text(
'config_info'));
169 $this->add_state_fields(
$form);
170 $form->_fd = self::CONFIG_FD;
171 $form->add_flds(self::CONFIG_FD);
172 $form->add_fld(
'default_server', options: $this->sql_server_options());
173 $form->add_obj(
'actions',
'dbxAdmin|config-dbx-save-actions', array(
'label' => $this->text(
'config_save')));
176 $this->apply_config_form_post(
$form);
177 $this->set_form_save_message(
$form);
183 private function config_form_data(): array {
185 'activeTab' =>
'config',
186 'activeSubTab' =>
'',
189 foreach ($this->config_fd_names() as $name) {
190 $value = $this->config[$name] ??
'';
191 if ($name ===
'groups' || $name ===
'accessible_lng') {
195 $data[$name] = $this->value_to_field(
$value);
201 private function config_fd_names(): array {
205 if (is_file(
$file)) {
215 $names[] = (string)
$field[
'name'];
220 private function apply_config_form_post(
$form): void {
221 foreach ($this->config_fd_names() as $name) {
222 if (!array_key_exists($name,
$form->_post)) {
225 $raw =
$form->_post[$name];
226 $oldValue = $this->config[$name] ??
'';
227 if ($name ===
'groups' || $name ===
'accessible_lng') {
228 $this->config[$name] = $this->multiselect_to_array($raw);
231 $this->config[$name] = $this->field_to_top_value($oldValue, (
string) $raw);
235 private function multiselect_to_array($raw): array {
236 if (is_array($raw)) {
237 return array_values(array_filter(array_map(
'trim', $raw),
static function ($item) {
241 $parts = preg_split(
'/\s*,\s*/', (
string) $raw, -1, PREG_SPLIT_NO_EMPTY);
242 return is_array($parts) ? array_values($parts) : array();
245 private function sql_server_options(): string {
247 foreach ($this->sql_db_servers() as $name => $entry) {
248 $options[] = rawurlencode((
string) $name) .
'=' . rawurlencode((
string) $name);
253 private function sql_db_servers(): array {
254 $db = $this->config[
'db'] ?? array();
255 if (!is_array(
$db)) {
259 foreach (
$db as $name => $entry) {
260 if (!is_array($entry) || $this->is_module_db_entry((
string) $name, $entry)) {
268 private function is_module_db_entry(
string $name, array $entry): bool {
269 return
dbx()->get_system_obj(
'dbxConfigStore')->is_module_db_entry($name, $entry);
272 private function strip_module_db_from_config(): void {
273 if (!isset($this->config[
'db']) || !is_array($this->config[
'db'])) {
276 foreach ($this->config[
'db'] as $name => $entry) {
277 if (is_array($entry) && $this->is_module_db_entry((
string) $name, $entry)) {
278 unset($this->config[
'db'][$name]);
283 private function render_array_section(
string $section): string {
284 $value = $this->config[$section] ?? array();
286 return $this->get_dbx_tpl(
'alert-info', array(
'msg' => $this->text(
'no_entries')));
289 if ($this->is_grouped_section(
$value)) {
290 return $this->render_grouped_section($section,
$value);
293 return $this->render_section_form($section,
$value);
296 private function render_grouped_section(
string $section, array
$groups): string {
297 $this->process_group_add($section);
298 $this->process_group_action($section);
300 $groups = $section === self::SQL_DB_SECTION
301 ? $this->sql_db_servers()
302 : ($this->config[$section] ?? array());
307 $addForm = $this->render_group_add_form($section,
$groups);
310 $emptyMsg = $section === self::SQL_DB_SECTION
311 ? $this->text(
'no_sql_servers')
312 : $this->text(
'no_entries');
313 return $this->get_tpl(
'config-dbx-section', array(
314 'add_form' => $addForm,
315 'tabs' => $this->get_dbx_tpl(
'alert-info', array(
'msg' => $emptyMsg)),
319 $activeGroup = ($this->activeTab === $section) ? $this->activeSubTab :
'';
320 if ($activeGroup ===
'' || !isset(
$groups[$activeGroup])) {
322 $activeGroup = (string)reset(
$keys);
326 foreach (
$groups as $group => $values) {
327 if (!is_array($values)) {
332 'id' => (
string)$group,
333 'label' => (
string)$group,
334 'content' => $this->render_group_form($section, (
string)$group, $values),
338 return $this->get_tpl(
'config-dbx-section', array(
339 'add_form' => $addForm,
340 'tabs' => $this->render_tabs($tabs,
'dbx_config_' . $section, $activeGroup),
344 private function render_section_form(
string $section, array $values): string {
346 $fields = $this->collect_fields(
'cfg_' . $section, $values, array(), $used);
348 'activeTab' => $section,
349 'activeSubTab' =>
'',
353 $data[
$field[
'name']] = $this->value_to_field(
$field[
'value']);
356 $label = $this->section_label($section);
357 $form = $this->new_form(
358 'config_dbx_' . $this->safe_token($section),
360 $this->format_text(
'edit_section', array(
'section' => $label))
362 $this->add_state_fields(
$form);
364 $form->add_obj(
'actions',
'dbxAdmin|config-dbx-save-actions', array(
365 'label' => $this->format_text(
'save_section', array(
'section' => $label)),
370 $oldValue =
$field[
'value'];
371 $newValue = (string)(
$form->_post[$name] ??
'');
372 $this->set_path($this->config[$section],
$field[
'path'], $this->field_to_value($oldValue, $newValue));
375 $this->set_form_save_message(
$form);
381 private function render_group_form(
string $section,
string $group, array $values): string {
382 $form = $this->build_group_form($section, $group, $values);
383 $this->apply_group_message(
$form, $section, $group);
388 private function process_group_add(
string $section): void {
389 $form = $this->build_group_add_form($section, $this->config[$section] ?? array());
391 if (!
$form->submit()) {
396 $this->sectionMessages[$section] = array(
'error', $this->text(
'check_new_name'));
400 $entry = trim((
string)(
$form->_post[
'configEntry'] ??
''));
402 $this->sectionMessages[$section] = array(
'error', $this->text(
'enter_new_name'));
406 if (!isset($this->config[$section]) || !is_array($this->config[$section])) {
407 $this->config[$section] = array();
410 if (isset($this->config[$section][$entry])) {
411 $this->sectionMessages[$section] = array(
413 $this->format_text(
'entry_exists', array(
'entry' => $entry))
418 $this->config[$section][$entry] = $section === self::SQL_DB_SECTION
422 'host' =>
'127.0.0.1',
428 : $this->empty_group_template($this->config[$section]);
430 if (!$this->save_config()) {
431 $this->sectionMessages[$section] = array(
'error', $this->text(
'entry_create_error'));
435 $this->activeTab = $section;
436 $this->activeSubTab = $entry;
437 $this->sectionMessages[$section] = array(
439 $this->format_text(
'entry_created', array(
'entry' => $entry))
443 private function process_group_action(
string $section): void {
444 if (($this->activeTab !== $section) || $this->activeSubTab ===
'') {
448 $group = $this->activeSubTab;
449 if (!isset($this->config[$section][$group]) || !is_array($this->config[$section][$group])) {
453 $values = $this->config[$section][$group];
454 $form = $this->build_group_form($section, $group, $values);
456 if (!
$form->submit()) {
460 if (!empty(
$_POST[
'deleteAction'])) {
461 unset($this->config[$section][$group]);
463 if (!$this->save_config()) {
464 $this->sectionMessages[$section] = array(
466 $this->format_text(
'entry_delete_error', array(
'entry' => $group))
471 $keys = array_keys((array)$this->config[$section]);
472 $this->activeSubTab =
$keys ? (string)reset(
$keys) :
'';
473 $this->sectionMessages[$section] = array(
475 $this->format_text(
'entry_deleted', array(
'entry' => $group))
480 if (
$form->errors()) {
481 $this->groupMessages[$section][$group] = array(
'error', $this->text(
'check_input'));
485 if ($section === self::SQL_DB_SECTION) {
486 $record = $this->server_record_from_post(
$form->_post);
487 if (strtolower((
string) ($record[
'type'] ??
'')) ===
'sqlite') {
488 $this->groupMessages[$section][$group] = array(
'error', $this->text(
'module_sqlite_forbidden'));
491 $this->config[$section][$group] = $record;
493 foreach ($this->group_fields($section, $group, $values) as $name =>
$field) {
494 $oldValue =
$field[
'value'];
495 $newValue = (string)(
$form->_post[$name] ??
'');
496 $this->set_path($this->config[$section][$group],
$field[
'path'], $this->field_to_value($oldValue, $newValue));
500 if (!$this->save_config()) {
501 $this->groupMessages[$section][$group] = array(
'error', $this->text(
'entry_save_error'));
505 $this->groupMessages[$section][$group] = array(
'success', $this->text(
'entry_saved'));
508 private function render_group_add_form(
string $section, array
$groups): string {
510 $this->apply_section_message(
$form, $section);
514 private function build_group_add_form(
string $section, array
$groups) {
515 $label = $this->section_label($section);
516 $form = $this->new_form(
'config_dbx_' . $this->safe_token($section) .
'_add', array(
517 'activeTab' => $section,
518 'activeSubTab' =>
'',
519 'configAction' =>
'add',
521 ), $this->format_text(
'new_entry_info', array(
'section' => $label)));
523 $this->add_state_fields(
$form);
524 $form->add_fld(
'configAction',
'dbx|hidden', rules:
'parameter', dd:
'');
528 label: $this->text(
'label_new_entry'),
529 rules:
'parameter|min=1',
530 tooltip: $this->text(
'tooltip_new_entry'),
533 $form->add_obj(
'actions',
'dbxAdmin|config-dbx-save-actions', array(
534 'label' => $this->format_text(
'create_section', array(
'section' => $label)),
540 private function build_group_form(
string $section,
string $group, array $values) {
541 $fields = $this->group_fields($section, $group, $values);
543 'activeTab' => $section,
544 'activeSubTab' => $group,
545 'configAction' =>
'save',
548 if ($section === self::SQL_DB_SECTION) {
549 foreach (self::SERVER_FIELD_NAMES as $name) {
550 $data[$name] = (string) ($values[$name] ?? ($name ===
'activ' ?
'1' :
''));
554 $data[
$field[
'name']] = $this->value_to_field(
$field[
'value']);
558 $label = $this->section_label($section);
559 $form = $this->new_form(
560 'config_dbx_' . $this->safe_token($section) .
'_' . $this->safe_token($group),
564 array(
'section' => $label,
'entry' => $group)
567 $this->add_state_fields(
$form);
568 $form->add_fld(
'configAction',
'dbx|hidden', rules:
'parameter', dd:
'');
569 if ($section === self::SQL_DB_SECTION) {
570 $this->add_server_fields(
$form);
572 $this->add_fields(
$form,
$fields, $label .
' ' . $group);
574 $form->add_obj(
'actions',
'dbxAdmin|config-dbx-group-actions', array(
575 'save_label' => $this->format_text(
'save_section', array(
'section' => $label)),
576 'delete_label' => $this->format_text(
'delete_section', array(
'section' => $label)),
577 'confirm' => $this->format_text(
578 'confirm_delete_entry',
579 array(
'section' => $label,
'entry' => $group)
586 private function group_fields(
string $section,
string $group, array $values): array {
588 return $this->collect_fields(
'cfg_' . $section .
'_' . $group, $values, array(), $used);
591 private function new_form(
string $fid, array $data,
string $info) {
592 $form =
dbx()->get_system_obj(
'dbxForm');
593 $form->init($fid,
'form-config-dbx');
594 $form->_fd = self::CONFIG_FD;
595 $form->load_fd_messages();
596 $form->_action = $this->base_action();
597 $form->_data = array_merge(
$form->_data, $data);
598 $form->_msg_info = $info;
599 $form->_fld_change_state =
'*';
604 private function add_state_fields(
$form): void {
605 $form->add_fld(
'activeTab',
'dbx|hidden', rules:
'parameter', dd:
'');
606 $form->add_fld(
'activeSubTab',
'dbx|hidden', rules:
'parameter', dd:
'');
609 private function add_fields(
$form, array
$fields,
string $tooltipPrefix): void {
613 $this->field_template(
$field),
616 tooltip: $tooltipPrefix .
': ' .
$field[
'label'],
622 private function field_template(array
$field): string {
623 $path = (array)(
$field[
'path'] ?? array());
624 $last = strtolower((
string)end($path));
625 $sensitive = array(
'pass',
'password',
'pwd',
'secret',
'token',
'api_key',
'apikey',
'private_key');
627 foreach ($sensitive as $needle) {
628 if ($last === $needle || str_ends_with($last,
'_' . $needle) || str_contains($last, $needle)) {
629 return 'password-label';
636 private function add_server_fields(
$form): void {
637 foreach (self::SERVER_FIELD_NAMES as $name) {
638 $form->add_fld($name, dd: self::SERVER_FD);
642 private function server_record_from_post(array $post): array {
644 foreach (self::SERVER_FIELD_NAMES as $name) {
645 $record[$name] = trim((
string) ($post[$name] ?? ($name ===
'activ' ?
'1' :
'')));
650 private function set_form_save_message(
$form): void {
651 if ($this->save_config()) {
652 $form->_msg_success =
$form->get_fd_message(
'config_saved');
656 $form->_msg_error =
$form->get_fd_message(
'config_save_error');
659 private function save_config(): bool {
660 $this->strip_module_db_from_config();
664 private function apply_section_message(
$form,
string $section): void {
665 if (empty($this->sectionMessages[$section])) {
669 $this->apply_message(
$form, $this->sectionMessages[$section]);
672 private function apply_group_message(
$form,
string $section,
string $group): void {
673 if (empty($this->groupMessages[$section][$group])) {
677 $this->apply_message(
$form, $this->groupMessages[$section][$group]);
680 private function apply_message(
$form, array $message): void {
681 $mode = (string)($message[0] ??
'info');
682 $text = (string)($message[1] ??
'');
684 if (
$mode ===
'success') {
685 $form->_msg_success = $text;
689 if (
$mode ===
'error') {
690 $form->_msg_error = $text;
694 if (
$mode ===
'warning') {
695 $form->_msg_warning = $text;
699 $form->_msg_info = $text;
702 private function empty_group_template(array
$groups): array {
704 if (is_array($group)) {
705 return $this->empty_like($group);
709 return array(
'name' =>
'');
712 private function empty_like(
$value) {
715 foreach (
$value as $key => $item) {
716 $empty[$key] = $this->empty_like($item);
725 private function collect_fields(
string $prefix, array
$value, array $path, array &$used): array {
728 foreach (
$value as $key => $item) {
729 $nextPath = array_merge($path, array((
string)$key));
731 if (is_array($item)) {
732 $fields = array_merge(
$fields, $this->collect_fields($prefix, $item, $nextPath, $used));
736 $field = $this->field_definition($prefix, $nextPath, $item, $used);
743 private function field_definition(
string $prefix, array $path,
$value, array &$used): array {
744 $name = $this->field_name($prefix, $path);
748 while (isset($used[$name])) {
749 $name = $baseName .
'_' . $counter;
758 'label' => implode(
' / ', $path),
763 private function set_path(array &
$target, array $path,
$value): void {
768 $key = array_shift($path);
782 private function value_to_field(
$value): string {
784 if ($this->is_list_array(
$value)) {
785 return implode(
',', array_map(
'strval',
$value));
788 return http_build_query(
$value);
794 private function field_to_top_value($oldValue,
string $value) {
795 if (is_array($oldValue)) {
796 if ($this->is_list_array($oldValue)) {
800 if (strpos(
$value,
'&') !==
false) {
809 return $this->field_to_value($oldValue,
$value);
812 private function field_to_value($oldValue,
string $value) {
813 if (is_int($oldValue)) {
817 if (is_float($oldValue)) {
821 if (is_bool($oldValue)) {
822 return in_array(strtolower(
$value), array(
'1',
'true',
'yes',
'on'),
true);
828 private function is_list_array(array
$value): bool {
833 return array_keys(
$value) === range(0, count(
$value) - 1);
836 private function is_grouped_section(array
$value): bool {
841 foreach (
$value as $item) {
842 if (!is_array($item)) {
850 private function section_label(
string $section): string {
852 'db' =>
'SQL-Server',
857 return $labels[$section] ?? $section;
860 private function field_name(
string $prefix, array $parts): string {
861 $tokens = array($this->safe_token($prefix));
862 foreach ($parts as $part) {
863 $tokens[] = $this->safe_token((
string)$part);
866 return implode(
'_', $tokens);
869 private function safe_token(
string $value): string {
880 private function safe_id(
string $value): string {
881 return
'dbx_' . strtolower($this->safe_token(
$value));
884 private function h(
$value): string {
885 return htmlspecialchars((string)
$value, ENT_QUOTES,
'UTF-8');