5 private const CONFIG_FD =
'cfg:dbx';
6 private const SERVER_FD =
'fd:dbxAdmin|server';
7 private const NESTED_SECTIONS = array(
'ftp',
'mail');
8 private const SQL_DB_SECTION =
'db';
9 private const SERVER_FIELD_NAMES = array(
'activ',
'type',
'host',
'dbname',
'user',
'pass',
'port');
11 private array $config = array();
12 private string $activeTab =
'config';
13 private string $activeSubTab =
'';
14 private array $sectionMessages = array();
15 private array $groupMessages = array();
17 public function run() {
18 $this->config =
dbx()->get_config(
'dbx');
19 if (!is_array($this->config)) {
20 $this->config = array();
23 $this->read_active_state();
24 $arraySections = $this->array_sections();
26 if ($this->activeTab !==
'config'
27 && $this->activeTab !== self::SQL_DB_SECTION
28 && !in_array($this->activeTab, $arraySections,
true)) {
29 $this->activeTab =
'config';
36 'content' => $this->render_config_form(),
39 'id' => self::SQL_DB_SECTION,
40 'label' => $this->section_label(self::SQL_DB_SECTION),
41 'content' => $this->render_array_section(self::SQL_DB_SECTION),
45 foreach ($arraySections as $section) {
48 'label' => $this->section_label($section),
49 'content' => $this->render_array_section($section),
53 $tabsHtml = $this->render_tabs($tabs,
'dbx_config', $this->activeTab);
54 $help =
dbx()->get_include_obj(
'dbxAdminHelp',
'dbxAdmin');
56 return $this->get_tpl(
'config-dbx-shell', array_merge(
57 $help->moduleBarTemplateData(
'config',
'',
'',
'',
'Modul: dbx'),
59 'frame_id' =>
'dbx_target_config_dbx',
60 'frame_panel_class' =>
'dbx-config-dbx',
61 'frame_form_open' =>
'',
62 'frame_form_close' =>
'',
64 'frame_body_class' =>
'',
65 'frame_body_head' =>
'',
66 'frame_body_tail' =>
'',
67 'frame_panel_attrs' =>
'',
68 'content' => $tabsHtml,
73 private function tpl() {
74 return dbx()->get_system_obj(
'dbxTPL');
77 private function get_tpl(
string $tpl, array $data = array()): string {
78 return $this->tpl()->get_tpl(
'dbxAdmin|' . $tpl, $data);
81 private function get_dbx_tpl(
string $tpl, array $data = array()): string {
82 return $this->tpl()->get_tpl(
'dbx|' . $tpl, $data);
85 private function base_action(): string {
86 return
'?dbx_modul=dbxAdmin&dbx_run1=config&dbx_run2=edit&xmodul=dbx';
89 private function read_active_state(): void {
90 $tab = (string)($_POST[
'activeTab'] ??
dbx()->get_modul_var(
'activeTab',
'config',
'parameter'));
91 $subTab = (string)($_POST[
'activeSubTab'] ??
dbx()->get_modul_var(
'activeSubTab',
'',
'parameter'));
97 $this->activeTab = $tab;
98 $this->activeSubTab = $subTab;
101 private function array_sections(): array {
104 foreach (self::NESTED_SECTIONS as $section) {
105 if (isset($this->config[$section]) && is_array($this->config[$section])) {
106 $sections[] = $section;
113 private function render_tabs(array $tabs,
string $idPrefix,
string $activeId): string {
117 foreach ($tabs as $tab) {
118 $tabId = (string)($tab[
'id'] ??
'tab');
119 $id = $this->safe_id($idPrefix .
'_' . $tabId);
120 $label = (string)($tab[
'label'] ??
'');
121 $content = (string)($tab[
'content'] ??
'');
122 $active = ($tabId === $activeId);
124 $tabHtml .= $this->get_tpl(
'config-dbx-tab', array(
126 'label' => $this->h($label),
127 'active' => $active ?
'active' :
'',
128 'selected' => $active ?
'true' :
'false',
131 $paneHtml .= $this->get_tpl(
'config-dbx-pane', array(
133 'content' => $content,
134 'active' => $active ?
'show active' :
'',
138 return $this->get_tpl(
'config-dbx-tabs', array(
140 'panes' => $paneHtml,
144 private function render_config_form(): string {
145 $data = $this->config_form_data();
146 $form = $this->new_form(
'config_dbx_general', $data,
'dbx System-Konfiguration bearbeiten.');
147 $this->add_state_fields($form);
148 $form->_fd = self::CONFIG_FD;
149 $form->add_flds(self::CONFIG_FD);
150 $form->add_fld(
'default_server', options: $this->sql_server_options());
151 $form->add_obj(
'actions',
'dbxAdmin|config-dbx-save-actions', array(
'label' =>
'Config speichern'));
153 if ($form->submit() && !$form->errors()) {
154 $this->apply_config_form_post($form);
155 $this->set_form_save_message($form);
161 private function config_form_data(): array {
163 'activeTab' =>
'config',
164 'activeSubTab' =>
'',
167 foreach ($this->config_fd_names() as $name) {
168 $value = $this->config[$name] ??
'';
169 if ($name ===
'groups' || $name ===
'accessible_lng') {
170 $data[$name] = is_array($value) ? implode(
',', $value) : (string) $value;
173 $data[$name] = $this->value_to_field($value);
179 private function config_fd_names(): array {
183 if (is_file($file)) {
193 $names[] = (string)
$field[
'name'];
198 private function apply_config_form_post($form): void {
199 foreach ($this->config_fd_names() as $name) {
200 if (!array_key_exists($name, $form->_post)) {
203 $raw = $form->_post[$name];
204 $oldValue = $this->config[$name] ??
'';
205 if ($name ===
'groups' || $name ===
'accessible_lng') {
206 $this->config[$name] = $this->multiselect_to_array($raw);
209 $this->config[$name] = $this->field_to_top_value($oldValue, (
string) $raw);
213 private function multiselect_to_array($raw): array {
214 if (is_array($raw)) {
215 return array_values(array_filter(array_map(
'trim', $raw),
static function ($item) {
219 $parts = preg_split(
'/\s*,\s*/', (
string) $raw, -1, PREG_SPLIT_NO_EMPTY);
220 return is_array($parts) ? array_values($parts) : array();
223 private function sql_server_options(): string {
225 foreach ($this->sql_db_servers() as $name => $entry) {
226 $options[] = rawurlencode((
string) $name) .
'=' . rawurlencode((
string) $name);
228 return implode(
'&', $options);
231 private function sql_db_servers(): array {
232 $db = $this->config[
'db'] ?? array();
233 if (!is_array(
$db)) {
237 foreach (
$db as $name => $entry) {
238 if (!is_array($entry) || $this->is_module_db_entry((
string) $name, $entry)) {
241 $servers[(string) $name] = $entry;
246 private function is_module_db_entry(
string $name, array $entry): bool {
247 return function_exists(
'dbx_config_is_module_db_entry')
251 private function strip_module_db_from_config(): void {
252 if (!isset($this->config[
'db']) || !is_array($this->config[
'db'])) {
255 foreach ($this->config[
'db'] as $name => $entry) {
256 if (is_array($entry) && $this->is_module_db_entry((
string) $name, $entry)) {
257 unset($this->config[
'db'][$name]);
262 private function render_array_section(
string $section): string {
263 $value = $this->config[$section] ?? array();
264 if (!is_array($value)) {
265 return $this->get_dbx_tpl(
'alert-info', array(
'msg' =>
'Keine Eintraege vorhanden.'));
268 if ($this->is_grouped_section($value)) {
269 return $this->render_grouped_section($section, $value);
272 return $this->render_section_form($section, $value);
275 private function render_grouped_section(
string $section, array $groups): string {
276 $this->process_group_add($section);
277 $this->process_group_action($section);
279 $groups = $section === self::SQL_DB_SECTION
280 ? $this->sql_db_servers()
281 : ($this->config[$section] ?? array());
282 if (!is_array($groups)) {
286 $addForm = $this->render_group_add_form($section, $groups);
289 $emptyMsg = $section === self::SQL_DB_SECTION
290 ?
'Keine SQL-Server in der Config. Modul-SQLite (*.db3) wird von dbxDB automatisch unter dbx/modules/*/db/ aufgeloest.'
291 :
'Keine Eintraege vorhanden.';
292 return $this->get_tpl(
'config-dbx-section', array(
293 'add_form' => $addForm,
294 'tabs' => $this->get_dbx_tpl(
'alert-info', array(
'msg' => $emptyMsg)),
298 $activeGroup = ($this->activeTab === $section) ? $this->activeSubTab :
'';
299 if ($activeGroup ===
'' || !isset($groups[$activeGroup])) {
300 $keys = array_keys($groups);
301 $activeGroup = (string)reset($keys);
305 foreach ($groups as $group => $values) {
306 if (!is_array($values)) {
311 'id' => (
string)$group,
312 'label' => (
string)$group,
313 'content' => $this->render_group_form($section, (
string)$group, $values),
317 return $this->get_tpl(
'config-dbx-section', array(
318 'add_form' => $addForm,
319 'tabs' => $this->render_tabs($tabs,
'dbx_config_' . $section, $activeGroup),
323 private function render_section_form(
string $section, array $values): string {
325 $fields = $this->collect_fields(
'cfg_' . $section, $values, array(), $used);
327 'activeTab' => $section,
328 'activeSubTab' =>
'',
332 $data[
$field[
'name']] = $this->value_to_field(
$field[
'value']);
335 $label = $this->section_label($section);
336 $form = $this->new_form(
'config_dbx_' . $this->safe_token($section), $data, $label .
' bearbeiten.');
337 $this->add_state_fields($form);
338 $this->add_fields($form,
$fields, $label);
339 $form->add_obj(
'actions',
'dbxAdmin|config-dbx-save-actions', array(
'label' => $label .
' speichern'));
341 if ($form->submit() && !$form->errors()) {
343 $oldValue =
$field[
'value'];
344 $newValue = (string)($form->_post[$name] ??
'');
345 $this->set_path($this->config[$section],
$field[
'path'], $this->field_to_value($oldValue, $newValue));
348 $this->set_form_save_message($form);
354 private function render_group_form(
string $section,
string $group, array $values): string {
355 $form = $this->build_group_form($section, $group, $values);
356 $this->apply_group_message($form, $section, $group);
361 private function process_group_add(
string $section): void {
362 $form = $this->build_group_add_form($section, $this->config[$section] ?? array());
364 if (!$form->submit()) {
368 if ($form->errors()) {
369 $this->sectionMessages[$section] = array(
'error',
'Bitte den neuen Namen pruefen.');
373 $entry = trim((
string)($form->_post[
'configEntry'] ??
''));
375 $this->sectionMessages[$section] = array(
'error',
'Bitte den neuen Namen eingeben.');
379 if (!isset($this->config[$section]) || !is_array($this->config[$section])) {
380 $this->config[$section] = array();
383 if (isset($this->config[$section][$entry])) {
384 $this->sectionMessages[$section] = array(
'error',
'Eintrag "' . $entry .
'" ist bereits vorhanden.');
388 $this->config[$section][$entry] = $section === self::SQL_DB_SECTION
392 'host' =>
'127.0.0.1',
398 : $this->empty_group_template($this->config[$section]);
400 if (!$this->save_config()) {
401 $this->sectionMessages[$section] = array(
'error',
'Eintrag konnte nicht angelegt werden.');
405 $this->activeTab = $section;
406 $this->activeSubTab = $entry;
407 $this->sectionMessages[$section] = array(
'success',
'Eintrag "' . $entry .
'" wurde angelegt.');
410 private function process_group_action(
string $section): void {
411 if (($this->activeTab !== $section) || $this->activeSubTab ===
'') {
415 $group = $this->activeSubTab;
416 if (!isset($this->config[$section][$group]) || !is_array($this->config[$section][$group])) {
420 $values = $this->config[$section][$group];
421 $form = $this->build_group_form($section, $group, $values);
423 if (!$form->submit()) {
427 if (!empty($_POST[
'deleteAction'])) {
428 unset($this->config[$section][$group]);
430 if (!$this->save_config()) {
431 $this->sectionMessages[$section] = array(
'error',
'Eintrag "' . $group .
'" konnte nicht geloescht werden.');
435 $keys = array_keys((array)$this->config[$section]);
436 $this->activeSubTab = $keys ? (string)reset($keys) :
'';
437 $this->sectionMessages[$section] = array(
'success',
'Eintrag "' . $group .
'" wurde geloescht.');
441 if ($form->errors()) {
442 $this->groupMessages[$section][$group] = array(
'error',
'Bitte Eingaben pruefen.');
446 if ($section === self::SQL_DB_SECTION) {
447 $record = $this->server_record_from_post($form->_post);
448 if (strtolower((
string) ($record[
'type'] ??
'')) ===
'sqlite') {
449 $this->groupMessages[$section][$group] = array(
'error',
'Modul-SQLite (*.db3) gehoert nicht in die System-Config.');
452 $this->config[$section][$group] = $record;
454 foreach ($this->group_fields($section, $group, $values) as $name =>
$field) {
455 $oldValue =
$field[
'value'];
456 $newValue = (string)($form->_post[$name] ??
'');
457 $this->set_path($this->config[$section][$group],
$field[
'path'], $this->field_to_value($oldValue, $newValue));
461 if (!$this->save_config()) {
462 $this->groupMessages[$section][$group] = array(
'error',
'Eintrag konnte nicht gespeichert werden.');
466 $this->groupMessages[$section][$group] = array(
'success',
'Eintrag wurde gespeichert.');
469 private function render_group_add_form(
string $section, array $groups): string {
470 $form = $this->build_group_add_form($section, $groups);
471 $this->apply_section_message($form, $section);
475 private function build_group_add_form(
string $section, array $groups) {
476 $label = $this->section_label($section);
477 $form = $this->new_form(
'config_dbx_' . $this->safe_token($section) .
'_add', array(
478 'activeTab' => $section,
479 'activeSubTab' =>
'',
480 'configAction' =>
'add',
482 ),
'Neuen ' . $label .
'-Eintrag anlegen.');
484 $this->add_state_fields($form);
485 $form->add_fld(
'configAction',
'dbx|hidden', rules:
'parameter', dd:
'');
486 $form->add_fld(
'configEntry',
'text-label', label:
'Neuer Eintrag', rules:
'parameter|min=1', tooltip:
'Name fuer den neuen Eintrag', dd:
'');
487 $form->add_obj(
'actions',
'dbxAdmin|config-dbx-save-actions', array(
'label' => $label .
' anlegen'));
492 private function build_group_form(
string $section,
string $group, array $values) {
493 $fields = $this->group_fields($section, $group, $values);
495 'activeTab' => $section,
496 'activeSubTab' => $group,
497 'configAction' =>
'save',
500 if ($section === self::SQL_DB_SECTION) {
501 foreach (self::SERVER_FIELD_NAMES as $name) {
502 $data[$name] = (string) ($values[$name] ?? ($name ===
'activ' ?
'1' :
''));
506 $data[
$field[
'name']] = $this->value_to_field(
$field[
'value']);
510 $label = $this->section_label($section);
511 $form = $this->new_form(
512 'config_dbx_' . $this->safe_token($section) .
'_' . $this->safe_token($group),
514 $label .
' "' . $group .
'" bearbeiten.'
516 $this->add_state_fields($form);
517 $form->add_fld(
'configAction',
'dbx|hidden', rules:
'parameter', dd:
'');
518 if ($section === self::SQL_DB_SECTION) {
519 $this->add_server_fields($form);
521 $this->add_fields($form,
$fields, $label .
' ' . $group);
523 $form->add_obj(
'actions',
'dbxAdmin|config-dbx-group-actions', array(
524 'save_label' => $label .
' speichern',
525 'delete_label' => $label .
' loeschen',
526 'confirm' => $label .
' "' . $group .
'" wirklich loeschen?',
532 private function group_fields(
string $section,
string $group, array $values): array {
534 return $this->collect_fields(
'cfg_' . $section .
'_' . $group, $values, array(), $used);
537 private function new_form(
string $fid, array $data,
string $info) {
538 $form =
dbx()->get_system_obj(
'dbxForm');
539 $form->init($fid,
'form-config-dbx');
540 $form->_action = $this->base_action();
541 $form->_data = array_merge($form->_data, $data);
542 $form->_msg_info = $info;
543 $form->_fld_change_state =
'*';
548 private function add_state_fields($form): void {
549 $form->add_fld(
'activeTab',
'dbx|hidden', rules:
'parameter', dd:
'');
550 $form->add_fld(
'activeSubTab',
'dbx|hidden', rules:
'parameter', dd:
'');
553 private function add_fields($form, array
$fields,
string $tooltipPrefix): void {
557 $this->field_template(
$field),
560 tooltip: $tooltipPrefix .
': ' .
$field[
'label'],
566 private function field_template(array
$field): string {
567 $path = (array)(
$field[
'path'] ?? array());
568 $last = strtolower((
string)end($path));
569 $sensitive = array(
'pass',
'password',
'pwd',
'secret',
'token',
'api_key',
'apikey',
'private_key');
571 foreach ($sensitive as $needle) {
572 if ($last === $needle || str_ends_with($last,
'_' . $needle) || str_contains($last, $needle)) {
573 return 'password-label';
580 private function add_server_fields($form): void {
581 foreach (self::SERVER_FIELD_NAMES as $name) {
582 $form->add_fld($name, dd: self::SERVER_FD);
586 private function server_record_from_post(array $post): array {
588 foreach (self::SERVER_FIELD_NAMES as $name) {
589 $record[$name] = trim((
string) ($post[$name] ?? ($name ===
'activ' ?
'1' :
'')));
594 private function set_form_save_message($form): void {
595 if ($this->save_config()) {
596 $form->_msg_success =
'Die Konfiguration wurde erfolgreich gespeichert.';
600 $form->_msg_error =
'Fehler beim Speichern der Konfiguration.';
603 private function save_config(): bool {
604 $this->strip_module_db_from_config();
605 return (
bool)
dbx()->set_config(
'dbx', $this->config);
608 private function apply_section_message($form,
string $section): void {
609 if (empty($this->sectionMessages[$section])) {
613 $this->apply_message($form, $this->sectionMessages[$section]);
616 private function apply_group_message($form,
string $section,
string $group): void {
617 if (empty($this->groupMessages[$section][$group])) {
621 $this->apply_message($form, $this->groupMessages[$section][$group]);
624 private function apply_message($form, array $message): void {
625 $mode = (string)($message[0] ??
'info');
626 $text = (string)($message[1] ??
'');
628 if ($mode ===
'success') {
629 $form->_msg_success = $text;
633 if ($mode ===
'error') {
634 $form->_msg_error = $text;
638 if ($mode ===
'warning') {
639 $form->_msg_warning = $text;
643 $form->_msg_info = $text;
646 private function empty_group_template(array $groups): array {
648 if (is_array($group)) {
649 return $this->empty_like($group);
653 return array(
'name' =>
'');
656 private function empty_like($value) {
657 if (is_array($value)) {
659 foreach ($value as $key => $item) {
660 $empty[$key] = $this->empty_like($item);
669 private function collect_fields(
string $prefix, array $value, array $path, array &$used): array {
672 foreach ($value as $key => $item) {
673 $nextPath = array_merge($path, array((
string)$key));
675 if (is_array($item)) {
676 $fields = array_merge(
$fields, $this->collect_fields($prefix, $item, $nextPath, $used));
680 $field = $this->field_definition($prefix, $nextPath, $item, $used);
687 private function field_definition(
string $prefix, array $path, $value, array &$used): array {
688 $name = $this->field_name($prefix, $path);
692 while (isset($used[$name])) {
693 $name = $baseName .
'_' . $counter;
702 'label' => implode(
' / ', $path),
707 private function set_path(array &$target, array $path, $value): void {
712 $key = array_shift($path);
715 $target[$key] = $value;
719 if (!isset($target[$key]) || !is_array($target[$key])) {
720 $target[$key] = array();
723 $this->set_path($target[$key], $path, $value);
726 private function value_to_field($value): string {
727 if (is_array($value)) {
728 if ($this->is_list_array($value)) {
729 return implode(
',', array_map(
'strval', $value));
732 return http_build_query($value);
735 return (
string)$value;
738 private function field_to_top_value($oldValue,
string $value) {
739 if (is_array($oldValue)) {
740 if ($this->is_list_array($oldValue)) {
744 if (strpos($value,
'&') !==
false) {
746 parse_str($value, $parsed);
753 return $this->field_to_value($oldValue, $value);
756 private function field_to_value($oldValue,
string $value) {
757 if (is_int($oldValue)) {
761 if (is_float($oldValue)) {
762 return (
float)$value;
765 if (is_bool($oldValue)) {
766 return in_array(strtolower($value), array(
'1',
'true',
'yes',
'on'),
true);
772 private function is_list_array(array $value): bool {
773 if ($value === array()) {
777 return array_keys($value) === range(0, count($value) - 1);
780 private function is_grouped_section(array $value): bool {
785 foreach ($value as $item) {
786 if (!is_array($item)) {
794 private function section_label(
string $section): string {
796 'db' =>
'SQL-Server',
801 return $labels[$section] ?? $section;
804 private function field_name(
string $prefix, array $parts): string {
805 $tokens = array($this->safe_token($prefix));
806 foreach ($parts as $part) {
807 $tokens[] = $this->safe_token((
string)$part);
810 return implode(
'_', $tokens);
813 private function safe_token(
string $value): string {
814 $value = preg_replace(
'/[^a-zA-Z0-9_]+/',
'_', $value);
815 $value = trim((
string)$value,
'_');
824 private function safe_id(
string $value): string {
825 return
'dbx_' . strtolower($this->safe_token($value));
828 private function h($value): string {
829 return htmlspecialchars((string)$value, ENT_QUOTES,
'UTF-8');