4require_once __DIR__ .
'/dbxModuleRegistry.class.php';
5require_once __DIR__ .
'/dbxModuleImages.class.php';
6require_once __DIR__ .
'/dbxReport_Modules.class.php';
11 static $registry = null;
19 return dbx()->get_include_obj(
'dbxAdminHelp');
22 private function tpl() {
23 return dbx()->get_system_obj(
'dbxTPL');
26 private function save_config_modul($modul, $data) {
27 return dbx()->set_config($modul, $data);
30 private function modul_access() {
31 $xmodul = (string)
dbx()->get_modul_var(
'xmodul');
33 return $this->tpl()->get_tpl(
'dbx',
'alert-warning', array(
'msg' =>
'Kein Modul gewaehlt.'));
36 $data =
dbx()->get_config($xmodul);
37 if (!is_array($data)) {
41 $oForm =
dbx()->get_system_obj(
'dbxForm');
42 $oForm->init(
'form-modul-access');
43 $oForm->add_rep(
'bar_title',
'Zugriff: ' . $xmodul);
44 $oForm->add_obj(
'bar_actions',
'obj-value',
'<button class="btn btn-primary btn-sm" type="submit"><i class="bi bi-save"></i> Speichern</button>');
45 $oForm->_data = $data;
46 $oForm->_msg_info =
'Legen Sie fest, welche Gruppen dieses Modul sehen und nutzen duerfen.';
48 $oForm->add_obj(
'xmodul', $xmodul);
49 $oForm->add_fld(
'groups',
'multi-select', options: $this->registry()->groupOptions(), rules:
'array|parameter', label:
'Gruppen', errormsg:
'Die Eingabe darf keine Sonderzeichen beinhalten.');
51 $this->adminHelp()->attachForm($oForm,
'modules_access');
53 if ($oForm->submit()) {
54 if (!$oForm->errors()) {
59 if (isset($oForm->_post[
'groups'])) {
60 $groups = $oForm->_post[
'groups'];
61 $config[
'groups'] = is_array($groups) ? implode(
',', $groups) : (string)$groups;
63 $ok = $this->save_config_modul($xmodul,
$config);
65 $oForm->_msg_success =
'Zugriffsrechte gespeichert';
67 $oForm->_msg_error =
'Daten konnten nicht gespeichert werden';
70 $oForm->_msg_error =
'Bitte Eingaben pruefen';
74 $content = $oForm->run();
75 return str_replace(
'{xmodul}', $xmodul, $content);
78 private function modul_help() {
79 $xmodul = trim((
string)
dbx()->get_modul_var(
'xmodul'));
81 return $this->tpl()->get_tpl(
'dbx',
'alert-warning', array(
'msg' =>
'Kein Modul gewaehlt.'));
84 $raw = $this->registry()->inspect($xmodul);
85 $title = (string)($raw[
'title'] ?? $xmodul);
86 $moduleHelpHtml = $this->registry()->renderModuleHelp($xmodul, array(
91 return $this->tpl()->get_tpl(
'dbxAdmin|module-help-detail', array(
93 'module_help_html' => $moduleHelpHtml,
97 private function buildInstallHelpHtml(array $record): string {
98 if (empty($record[
'install_url'])) {
102 $modul =
dbx()->esc((
string)($record[
'install_modul'] ??
''));
103 $url =
dbx()->esc((
string)$record[
'install_url']);
104 $title =
dbx()->esc((
string)($record[
'title'] ??
''));
106 return '<h4>Installation</h4>'
107 .
'<p>Dieses Modul stellt einen Installationsdialog bereit (<code>' . $modul .
'</code>).</p>'
108 .
'<p><a class="btn btn-sm btn-outline-secondary dbx-win" href="' . $url .
'" data-url="' . $url .
'" data-title="Installation: ' . $title .
'">'
109 .
'<i class="bi bi-database-gear"></i> Installation starten</a></p>';
112 private function formatRunCases(array $runCases): string {
114 return 'Keine Run-Aktionen erkannt';
116 return implode(
', ', array_map(
'strval', $runCases));
119 private function prepareModuleRecord(array $record): array {
123 private function images(): dbxModuleImages {
125 if (!$obj instanceof dbxModuleImages) {
126 $obj =
new dbxModuleImages();
131 private function modul_images() {
132 $xmodul = (string)
dbx()->get_modul_var(
'xmodul');
133 if ($xmodul ===
'') {
134 return $this->tpl()->get_tpl(
'dbx',
'alert-warning', array(
'msg' =>
'Kein Modul gewaehlt.'));
137 $images = $this->images();
138 $items = $images->imageItems($xmodul);
139 $info = $this->registry()->inspect($xmodul);
140 $report =
new dbxReport_Modules();
141 $galleryHtml = $report->moduleImagesGalleryHtml(array(
143 'image_items' => $items,
144 'default_run1' => (
string)($info[
'default_run1'] ??
'run'),
145 'default_run2' => (
string)($info[
'default_run2'] ??
''),
146 'placeholder_url' => (
string)($info[
'placeholder_url'] ??
''),
148 $previewHtml = $report->moduleImagesPreviewHtml(array(
150 'image_items' => $items,
151 'default_run1' => (
string)($info[
'default_run1'] ??
'run'),
152 'default_run2' => (
string)($info[
'default_run2'] ??
''),
153 'placeholder_url' => (
string)($info[
'placeholder_url'] ??
''),
155 $targetHtml = $report->moduleImagesTargetHtml($info);
158 'xmodul' =>
dbx()->esc($xmodul),
159 'gallery_html' => $galleryHtml,
160 'images_preview_html' => $previewHtml,
161 'images_target_html' => $targetHtml,
162 'uses_run2' => (
string)($info[
'uses_run2'] ??
'0'),
163 'placeholder_url' =>
dbx()->esc((
string)($info[
'placeholder_url'] ??
'')),
164 'placeholder_alt' =>
dbx()->esc((
string)($info[
'placeholder_alt'] ??
'')),
165 'image_count' => count($items),
166 'media_api_url' =>
dbx()->esc($this->images()->mediaApiUrl() .
'&images=1&media_type=image'),
167 'images_add_url' =>
dbx()->esc(
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_images_add'),
168 'images_upload_url' =>
dbx()->esc(
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_images_upload'),
169 'images_remove_url' =>
dbx()->esc(
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_images_remove'),
170 'media_folders_url' =>
dbx()->esc($this->images()->mediaFoldersApiUrl()),
173 return $this->tpl()->get_tpl(
'dbxAdmin|form-modul-images', $data);
176 private function modul_image_serve() {
177 $file = trim((
string)
dbx()->get_modul_var(
'file',
'',
'*'));
178 $this->images()->serveFile($file);
181 private function modul_images_media_json() {
182 $xmodul = trim((
string)
dbx()->get_modul_var(
'xmodul',
''));
183 dbx()->json_response(array(
185 'rows' => $this->images()->mediaBrowserRows($xmodul),
189 private function modul_images_media_folders_json() {
191 $dir = $this->images()->absDir();
192 if (is_dir(
$dir) && is_readable(
$dir)) {
195 dbx()->json_response(array(
198 'folders' => $folders,
199 'root' =>
'files/mod/',
203 private function modul_images_list_json() {
204 $xmodul = (string)
dbx()->get_modul_var(
'xmodul');
205 if ($xmodul ===
'') {
206 dbx()->json_response(array(
'ok' => 0,
'msg' =>
'Kein Modul',
'items' => array()));
208 dbx()->json_response(array(
211 'items' => $this->images()->imageItems($xmodul),
215 private function modul_images_save_json() {
216 $payload = $this->readJsonBody();
217 $xmodul = (string)($payload[
'xmodul'] ??
dbx()->get_modul_var(
'xmodul'));
218 $files = $payload[
'files'] ?? array();
219 if ($xmodul ===
'' || !is_array($files)) {
220 dbx()->json_response(array(
'ok' => 0,
'msg' =>
'Ungueltige Daten'));
222 $ok = $this->images()->saveList($xmodul, $files);
223 dbx()->json_response(array(
226 'items' => $this->images()->imageItems($xmodul),
230 private function modul_images_add_json() {
231 $payload = $this->readJsonBody();
232 $xmodul = (string)($payload[
'xmodul'] ??
dbx()->get_modul_var(
'xmodul'));
233 if ($xmodul ===
'') {
234 dbx()->json_response(array(
'ok' => 0,
'msg' =>
'Kein Modul'));
237 $run1 = trim((
string)($payload[
'dbx_run1'] ?? $payload[
'run1'] ??
''));
238 $run2 = trim((
string)($payload[
'dbx_run2'] ?? $payload[
'run2'] ??
''));
240 dbx()->json_response(array(
'ok' => 0,
'msg' =>
'dbx_run1 ist erforderlich'));
244 $mediaId = (int)($payload[
'media_id'] ?? 0);
245 $filePath = trim((
string)($payload[
'file_path'] ??
''));
247 if ($mediaId > 0 || $filePath !==
'') {
248 $filename = $this->images()->importForModul($xmodul, $mediaId, $filePath, $run1, $run2);
251 if ($filename ===
null || $filename ===
'') {
252 dbx()->json_response(array(
'ok' => 0,
'msg' =>
'Bild konnte nicht uebernommen werden'));
255 dbx()->json_response(array(
258 'filename' => $filename,
259 'items' => $this->images()->imageItems($xmodul),
263 private function modul_symbol_add_json() {
264 $payload = $this->readJsonBody();
265 $xmodul = (string)($payload[
'xmodul'] ??
dbx()->get_modul_var(
'xmodul'));
266 if ($xmodul ===
'') {
267 dbx()->json_response(array(
'ok' => 0,
'msg' =>
'Kein Modul'));
270 $mediaId = (int)($payload[
'media_id'] ?? 0);
271 $filePath = trim((
string)($payload[
'file_path'] ??
''));
272 if ($mediaId <= 0 && $filePath ===
'') {
273 dbx()->json_response(array(
'ok' => 0,
'msg' =>
'Kein Bild gewaehlt'));
276 $symbol = $this->images()->importSymbolForModul($xmodul, $mediaId, $filePath);
277 if (!is_array($symbol) || empty($symbol[
'url'])) {
278 dbx()->json_response(array(
'ok' => 0,
'msg' =>
'Symbolbild konnte nicht uebernommen werden'));
281 dbx()->json_response(array(
288 private function modul_images_upload_json() {
289 $xmodul = trim((
string)($_POST[
'xmodul'] ??
dbx()->get_modul_var(
'xmodul')));
290 $run1 = trim((
string)($_POST[
'dbx_run1'] ?? $_POST[
'run1'] ??
''));
291 $run2 = trim((
string)($_POST[
'dbx_run2'] ?? $_POST[
'run2'] ??
''));
292 if ($xmodul ===
'') {
293 dbx()->json_response(array(
'ok' => 0,
'msg' =>
'Kein Modul'));
296 dbx()->json_response(array(
'ok' => 0,
'msg' =>
'dbx_run1 ist erforderlich'));
299 $file = $_FILES[
'file'] ??
null;
300 if (!is_array($file)) {
301 dbx()->json_response(array(
'ok' => 0,
'msg' =>
'Keine Datei erhalten'));
304 $filename = $this->images()->saveFromUpload($xmodul, $run1, $run2, $file);
305 if ($filename ===
null || $filename ===
'') {
306 dbx()->json_response(array(
'ok' => 0,
'msg' =>
'Upload konnte nicht gespeichert werden'));
309 dbx()->json_response(array(
312 'filename' => $filename,
313 'items' => $this->images()->imageItems($xmodul),
317 private function modul_images_remove_json() {
318 $payload = $this->readJsonBody();
319 $xmodul = (string)($payload[
'xmodul'] ??
dbx()->get_modul_var(
'xmodul'));
320 $file = (string)($payload[
'file'] ??
'');
321 $deleteFile = !array_key_exists(
'delete_file', $payload) || !empty($payload[
'delete_file']);
322 if ($xmodul ===
'' || $file ===
'') {
323 dbx()->json_response(array(
'ok' => 0,
'msg' =>
'Ungueltige Daten'));
326 $ok = $this->images()->removeFromList($xmodul, $file, $deleteFile);
327 dbx()->json_response(array(
330 'items' => $this->images()->imageItems($xmodul),
334 private function modul_access_save_json() {
335 $payload = $this->readJsonBody();
336 $xmodul = trim((
string)($payload[
'xmodul'] ??
dbx()->get_modul_var(
'xmodul')));
337 if ($xmodul ===
'') {
338 dbx()->json_response(array(
'ok' => 0,
'msg' =>
'Kein Modul'));
341 $groups = $payload[
'groups'] ?? array();
342 if (!is_array($groups)) {
343 $groups = preg_split(
'/\s*,\s*/', trim((
string)$groups), -1, PREG_SPLIT_NO_EMPTY);
345 if (!is_array($groups)) {
349 $allowed = array_keys($this->registry()->groupOptions());
351 foreach ($groups as $group) {
352 $group = trim((
string)$group);
353 if ($group !==
'' && in_array($group, $allowed,
true)) {
357 $clean = array_values(array_unique($clean));
363 $config[
'groups'] = implode(
',', $clean);
366 dbx()->json_response(array(
370 'msg' => $ok ?
'Zugriffsrechte gespeichert' :
'Speichern fehlgeschlagen',
374 private function modul_active_toggle_json() {
375 $payload = $this->readJsonBody();
376 $xmodul = trim((
string)($payload[
'xmodul'] ??
dbx()->get_modul_var(
'xmodul')));
377 if ($xmodul ===
'' || !preg_match(
'/^[a-zA-Z][a-zA-Z0-9_]*$/', $xmodul)) {
378 dbx()->json_response(array(
'ok' => 0,
'msg' =>
'Kein Modul'));
387 if (array_key_exists(
'active', $payload)) {
388 $active = !empty($payload[
'active']) ?
'1' :
'0';
392 $current = (string)
$config[
'activ'] ===
'1' ?
'1' :
'0';
393 } elseif (isset(
$config[
'active'])) {
394 $current = (string)
$config[
'active'] ===
'1' ?
'1' :
'0';
396 $active = $current ===
'1' ?
'0' :
'1';
402 dbx()->json_response(array(
406 'active_label' => $active ===
'1' ?
'Aktiv' :
'Inaktiv',
407 'msg' => $ok ?
'Status gespeichert' :
'Status konnte nicht gespeichert werden',
411 private function modul_delete_json() {
412 $payload = $this->readJsonBody();
413 $xmodul = trim((
string)($payload[
'xmodul'] ??
dbx()->get_modul_var(
'xmodul')));
414 if ($xmodul ===
'' || !preg_match(
'/^[a-zA-Z][a-zA-Z0-9_]*$/', $xmodul)) {
415 dbx()->json_response(array(
'ok' => 0,
'msg' =>
'Kein Modul'));
417 if (!$this->registry()->canDeleteModule($xmodul)) {
418 dbx()->json_response(array(
'ok' => 0,
'msg' =>
'Modul darf nicht geloescht werden'));
422 $ok = $this->deleteModuleDirectory(
$dir);
424 if (isset($_SESSION[
'dbx'][
'config'][$xmodul])) {
425 unset($_SESSION[
'dbx'][
'config'][$xmodul]);
427 if (isset($_SESSION[
'dbx'][
'config_file'][$xmodul])) {
428 unset($_SESSION[
'dbx'][
'config_file'][$xmodul]);
432 dbx()->json_response(array(
435 'msg' => $ok ?
'Modul geloescht' :
'Modul konnte nicht geloescht werden',
439 private function deleteModuleDirectory(
string $dir): bool {
445 $items = new \RecursiveIteratorIterator(
446 new \RecursiveDirectoryIterator(
$dir, \FilesystemIterator::SKIP_DOTS),
447 \RecursiveIteratorIterator::CHILD_FIRST
450 foreach ($items as $item) {
451 $path = $item->getPathname();
452 if ($item->isDir()) {
453 if (!@rmdir($path)) {
456 } elseif (!@unlink($path)) {
464 private function readJsonBody(): array {
465 $raw = file_get_contents(
'php://input');
466 if (!is_string($raw) || $raw ===
'') {
469 $data = json_decode($raw,
true);
470 return is_array($data) ? $data : array();
473 private function modul_avatar() {
474 $xmodul = (string)
dbx()->get_modul_var(
'xmodul');
476 if ($xmodul ===
'') {
477 return 'kein Modul gewaehlt';
480 $path =
dbx()->os_path(
dbx()->get_base_dir() .
"dbx/modules/$xmodul/tpl/img/");
481 $url =
dbx()->get_base_url() .
"dbx/modules/$xmodul/tpl/img/";
482 $modul_img =
'modul.gif';
483 $path_img_ext = $path . $modul_img;
484 $url_img_ext = $url . $modul_img;
488 'avatar_upload' => $url_img_ext,
491 $oForm =
dbx()->get_system_obj(
'dbxForm');
492 $oForm->_msg_info =
'Sie koennen ein Modulbild auswaehlen';
493 $oForm->init(
'dbxModules_avatar',
'form-avatar');
494 $oForm->_data = $data;
495 $oForm->add_js_call(
'uploader_img',
'upload');
497 if (!empty($_FILES)) {
498 $oUpload =
dbx()->get_system_obj(
'dbxUpload');
499 $oUpload->upload($_FILES[
'upload_file']);
500 $oUpload->allowed = array(
'image/*');
501 $oUpload->file_new_name_body =
'modul';
502 $oUpload->image_convert =
'gif';
503 $oUpload->file_overwrite =
true;
504 $oUpload->image_resize =
true;
505 $oUpload->image_x = 200;
506 $oUpload->image_y = 200;
507 $oUpload->process($path);
508 if ($oUpload->processed) {
510 $oForm->_data[
'avatar_upload'] = $url . $oUpload->file_dst_name;
511 $oForm->_msg_success =
'Bild erfolgreich hochgeladen.';
513 $oForm->set_msg_error(
'Bild Datei nicht hochgeladen.');
517 $oForm->add_fld(
'avatar_upload',
'avatar_upload', rules:
'alphanum', label:
'Modul-Bild', data:
'msg=Sie koennen ein Bild hochladen.');
518 $content = $oForm->run();
519 return str_replace(
'{xmodul}', $xmodul, $content);
522 Private
function report_modules() {
523 $allModules = $this->registry()->inspectAll();
524 $moduleOptions = array(
'0' =>
'Alle Module');
525 foreach ($allModules as $module) {
526 $name = (string)($module[
'xmodul'] ??
'');
528 $moduleOptions[$name] = $name;
533 foreach ($allModules as $module) {
534 if ((
string)($module[
'active'] ??
'1') ===
'1') {
539 $oReport =
new dbxReport_Modules();
540 $oReport->init(
'dbxModules',
'report-modules');
541 $oReport->_action =
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_list';
542 $oReport->_pages =
true;
543 $oReport->_rrows = 20;
544 $oReport->_but_pagination = 7;
545 $oReport->_fld_id =
'rid';
546 $oReport->_create_row_select =
true;
547 $oReport->_create_sel_flds =
false;
548 $oReport->_data = array(
555 $oReport->add_fld(
'dbx_rmodul',
'select-single-label', label:
'Modul', rules:
'parameter', options: $moduleOptions);
556 $oReport->add_fld(
'dbx_rwhere',
'dbx|search', label:
'Suche', rules:
'sqlsearch|max=80');
557 $oReport->add_fld(
'dbx_rrows',
'integer-label', label:
'Zeilen', rules:
'int');
559 $modulFilter = $oReport->get_fld_val(
'dbx_rmodul',
'0',
'parameter');
560 $search = $oReport->get_fld_val(
'dbx_rwhere',
'',
'sqlsearch|max=80');
561 $rrows = (int)$oReport->get_fld_val(
'dbx_rrows', 20,
'int');
562 $rpos = (int)$oReport->get_fld_val(
'dbx_rpos', 0,
'int');
567 $filtered = $this->filterModuleRows($allModules, $modulFilter, $search);
568 $filteredCount = count($filtered);
569 if ($rpos < 0 || ($filteredCount > 0 && $rpos >= $filteredCount)) {
573 $oReport->_rrows = $rrows;
574 $oReport->_rpos = $rpos;
575 $oReport->_rcount = $filteredCount;
576 $oReport->_rdata = $this->pageModuleRows($filtered, $rpos, $rrows);
578 $oReport->add_rep(
'module_count', count($allModules));
579 $oReport->add_rep(
'module_active_count', $activeCount);
580 $oReport->add_rep(
'module_filtered_count', $filteredCount);
581 $oReport->add_rep(
'modimg_media_url',
dbx()->esc($this->images()->mediaApiUrl() .
'&images=1&media_type=image'));
582 $oReport->add_rep(
'modimg_upload_url',
dbx()->esc(
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_images_upload'));
583 $oReport->add_rep(
'modimg_mediafolders_url',
dbx()->esc($this->images()->mediaFoldersApiUrl()));
584 $oReport->add_rep(
'modimg_mediafoldercreate_url',
'');
585 $oReport->add_rep(
'modimg_mediafolderdelete_url',
'');
586 $oReport->add_rep(
'modimg_deletemedia_url',
'');
587 $oReport->add_rep(
'modimg_uploadmax', (
string)(16 * 1024 * 1024));
589 return $oReport->run();
592 private function filterModuleRows(array $modules,
string $modulFilter,
string $search): array {
593 $modulFilter = trim($modulFilter);
594 $search = strtolower(trim($search));
597 foreach ($modules as $module) {
598 if (!is_array($module)) {
602 $name = (string)($module[
'xmodul'] ??
'');
607 if ($modulFilter !==
'' && $modulFilter !==
'0' && $name !== $modulFilter) {
611 if ($search !==
'') {
612 $ddList = $module[
'dd_list'] ?? array();
613 if (!is_array($ddList)) {
617 $haystack = strtolower(implode(
' ', array(
619 (
string)($module[
'description'] ??
''),
620 (
string)($module[
'default_call'] ??
''),
621 (
string)($module[
'groups_text'] ??
''),
622 (
string)($module[
'version'] ??
''),
623 implode(
' ', $ddList),
626 if (strpos($haystack, $search) ===
false) {
631 $module[
'rid'] = $name;
638 private function pageModuleRows(array $modules,
int $rpos,
int $rrows): array {
646 $slice = array_slice($modules, $rpos, $rrows);
648 foreach ($slice as $module) {
649 $rows[] = $this->prepareModuleRecord($module);
655 public function modul_new() {
656 $obj =
dbx()->get_include_obj(
'dbxWizard');
657 $run =
dbx()->get_modul_var(
'run');
658 return $obj->run($run);
661 public function modul_edit() {
662 $obj =
dbx()->get_include_obj(
'dbxWizard');
663 $run =
dbx()->get_modul_var(
'run');
664 return $obj->run($run);
667 public function run() {
668 $modul =
dbx()->get_modul_var(
'dbx_modul');
669 $action =
dbx()->get_modul_var(
'dbx_run1');
670 $work =
dbx()->get_modul_var(
'dbx_run2');
671 $content =
"dbxAdmin->dbxModules ($work) X<br>";
675 $content = $this->report_modules();
679 $content = $this->modul_avatar();
682 case 'avatar_upload':
683 $content = $this->modul_avatar();
687 $content = $this->modul_new();
691 $content = $this->modul_edit();
695 $content = $this->modul_access();
698 case 'modul_access_save':
699 $this->modul_access_save_json();
702 case 'modul_active_toggle':
703 $this->modul_active_toggle_json();
707 $this->modul_delete_json();
711 $content = $this->modul_help();
715 $content = $this->modul_images();
719 $this->modul_image_serve();
722 case 'modul_images_list':
723 $this->modul_images_list_json();
726 case 'modul_images_save':
727 $this->modul_images_save_json();
730 case 'modul_images_add':
731 $this->modul_images_add_json();
734 case 'modul_symbol_add':
735 $this->modul_symbol_add_json();
738 case 'modul_images_upload':
739 $this->modul_images_upload_json();
742 case 'modul_images_remove':
743 $this->modul_images_remove_json();
746 case 'modul_images_media':
747 $this->modul_images_media_json();
750 case 'modul_images_media_folders':
751 $this->modul_images_media_folders_json();
755 $msg[
'msg'] =
"Modul=($modul) Action=($action) Work=($work) is undef!";
756 $content = $this->tpl()->get_tpl(
'dbx',
'alert-warning', $msg);
Erkennt Modul-Metadaten aus Dateisystem und PHP-Quellen (ohne manuelle Registry).
dbx_os_path_file($path_file)
dbx_get_base_dir($cut_Data=0)
DBX schema administration.