9 private $modulesRoot =
'';
10 private $groupLabels =
null;
11 private $inspectCache = array();
12 private $images =
null;
15 $this->modulesRoot =
dbx()->os_path(
dbx()->get_base_dir() .
'dbx/modules/');
20 if (!is_dir($this->modulesRoot)) {
24 $dh = opendir($this->modulesRoot);
29 while (($file = readdir($dh)) !==
false) {
30 if ($file ===
'.' || $file ===
'..' || $file ===
'tpl') {
33 $path = $this->modulesRoot . $file;
41 sort($names, SORT_NATURAL | SORT_FLAG_CASE);
42 if (($key = array_search(
'dbx', $names,
true)) !==
false) {
43 array_splice($names, $key, 1);
44 array_unshift($names,
'dbx');
50 public function inspect(
string $modul): array {
51 $modul = trim($modul);
52 if (isset($this->inspectCache[$modul])) {
53 return $this->inspectCache[$modul];
56 $base = $this->modulesRoot . $modul . DIRECTORY_SEPARATOR;
62 $runs = $this->scanRuns($modul);
63 $defaultRun1 = (string)($runs[
'default_run1'] ??
'run');
64 $defaultRun2 = (string)($runs[
'default_run2'] ??
'');
65 $install = $this->detectInstall($modul);
66 $images = $this->images();
67 $graphic = $images->moduleSymbol($modul);
68 $imageItems = $images->imageItems($modul);
69 $ddList = $this->detectDdUsage($modul);
70 $ddItems = $this->buildDdItems($ddList);
71 $ddCount = count($ddList);
72 $description = $this->detectDescription($modul,
$base, $ddList);
73 $groups = $this->normalizeGroups(
$config[
'groups'] ??
'*');
74 $active = $this->isActive(
$config);
75 $version = trim((
string)(
$config[
'version'] ??
''));
77 $previewModul = $modul;
78 $previewRun1 = $defaultRun1;
79 $previewRun2 = $defaultRun2;
80 if ($modul ===
'dbxHome') {
85 $previewUrl = $this->buildModulUrl($previewModul, $previewRun1, $previewRun2);
86 $installUrl = $install[
'url'] ??
'';
87 $configUrl = $this->configEditUrl($modul);
88 $accessUrl =
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_access&dbx_run3=edit&xmodul=' . rawurlencode($modul);
89 $helpUrl =
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_help&xmodul=' . rawurlencode($modul);
90 $avatarUrl =
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_avatar&xmodul=' . rawurlencode($modul);
92 $this->inspectCache[$modul] = array(
95 'description' => $description,
96 'version' => $version !==
'' ? $version :
'—',
97 'active' => $active ?
'1' :
'0',
98 'active_label' => $active ?
'Aktiv' :
'Inaktiv',
99 'active_class' => $active ?
'success' :
'secondary',
101 'groups_text' => $this->groupsText($groups),
102 'dd_list' => $ddList,
103 'dd_items' => $ddItems,
104 'dd_count' => $ddCount,
105 'dd_select_id' =>
'dbx_mod_dd_' . preg_replace(
'/[^a-zA-Z0-9_]/',
'_', $modul),
106 'dd_select_size' => 6,
107 'default_run1' => $defaultRun1,
108 'default_run2' => $defaultRun2,
109 'default_call' => $this->formatDefaultCall($modul, $defaultRun1, $defaultRun2),
110 'preview_url' => $previewUrl,
111 'install_url' => $installUrl,
112 'has_install' => !empty($install[
'url']) ?
'1' :
'0',
113 'install_modul' => (
string)($install[
'modul'] ??
''),
114 'config_url' => $configUrl,
115 'access_url' => $accessUrl,
116 'help_url' => $helpUrl,
117 'avatar_url' => $avatarUrl,
118 'images_url' =>
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_images&xmodul=' . rawurlencode($modul),
119 'images_add_url' =>
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_images_add',
120 'images_upload_url' =>
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_images_upload',
121 'images_remove_url' =>
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_images_remove',
122 'symbol_add_url' =>
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_symbol_add',
123 'symbol_media_url' =>
'?dbx_modul=dbxContent_admin&dbx_run1=cms_media&images=1&media_type=image',
124 'symbol_upload_url' =>
'?dbx_modul=dbxContent_admin&dbx_run1=cms_upload',
125 'symbol_mediafolders_url' =>
'?dbx_modul=dbxContent_admin&dbx_run1=cms_media_folders',
126 'symbol_mediafoldercreate_url' =>
'?dbx_modul=dbxContent_admin&dbx_run1=cms_media_folder_create',
127 'symbol_mediafolderdelete_url' =>
'?dbx_modul=dbxContent_admin&dbx_run1=cms_media_folder_delete',
128 'symbol_deletemedia_url' =>
'?dbx_modul=dbxContent_admin&dbx_run1=cms_delete_media',
129 'access_save_url' =>
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_access_save',
130 'active_toggle_url' =>
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_active_toggle',
131 'delete_url' =>
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_delete',
132 'can_delete' => $this->canDeleteModule($modul) ?
'1' :
'0',
133 'graphic_url' => (
string)($graphic[
'url'] ??
''),
134 'graphic_alt' => (
string)($graphic[
'alt'] ?? $modul),
135 'graphic_badge' => (
string)($graphic[
'badge'] ??
''),
136 'placeholder_url' => $this->moduleImageEmptyPlaceholderUrl(),
137 'placeholder_alt' => $this->moduleImageEmptyPlaceholderAlt(),
138 'module_images' => array_values(array_map(
function ($item) {
139 return (
string)($item[
'file'] ??
'');
141 'image_items' => $imageItems,
142 'image_count_cfg' => count($imageItems),
143 'has_class' => is_file(
$base . $modul .
'.class.php') ?
'1' :
'0',
144 'run_cases' => (array)($runs[
'run1'] ?? array()),
145 'uses_run2' => !empty($runs[
'uses_run2']) ?
'1' :
'0',
147 return $this->inspectCache[$modul];
150 private function images(): dbxModuleImages {
151 if (!class_exists(
'dbx\\dbxAdmin\\dbxModuleImages', false)) {
152 require_once __DIR__ .
'/dbxModuleImages.class.php';
154 if (!$this->images instanceof dbxModuleImages) {
155 $this->images =
new dbxModuleImages();
157 return $this->images;
160 public function ddEditUrl(
string $modul,
string $dd): string {
161 return
'?dbx_modul=dbxAdmin&dbx_run1=edit_dd&modul=' . rawurlencode($modul) .
'&dd=' . rawurlencode($dd);
166 foreach ($ddList as $ddRef) {
167 $parts = explode(
'|', (
string)$ddRef, 2);
168 $m = trim((
string)($parts[0] ??
''));
169 $d = trim((
string)($parts[1] ??
''));
170 if ($m ===
'' || $d ===
'') {
180 'edit_title' =>
'DD bearbeiten: ' . $m .
'/' . $d,
189 $rows[] = $this->
inspect($modul);
195 if (is_array($this->groupLabels)) {
196 return $this->groupLabels;
202 'member' =>
'Mitglied',
206 $db =
dbx()->get_system_obj(
'dbxDB');
207 $userGroups =
$db->select(
'dbxUser_groups',
'active = 1');
208 if (is_array($userGroups)) {
209 foreach ($userGroups as $record) {
210 $id = trim((
string)($record[
'name'] ??
''));
214 $options[$id] = trim((
string)($record[
'description'] ?? $id));
218 $this->groupLabels = $options;
219 return $this->groupLabels;
222 private function groupsText(array $groups): string {
227 $labels = $this->groupOptions();
229 foreach ($groups as $group) {
230 $parts[] = $labels[$group] ?? $group;
233 return implode(
', ', $parts);
236 private function normalizeGroups($groups): array {
237 if (is_array($groups)) {
239 foreach ($groups as $value) {
240 $value = trim((
string)$value);
245 return array_values(array_unique($out));
248 $text = trim((
string)$groups);
253 $parts = preg_split(
'/\s*,\s*/', $text, -1, PREG_SPLIT_NO_EMPTY);
254 if (!is_array($parts) || !$parts) {
258 return array_values(array_unique($parts));
261 private function isActive(array
$config): bool {
263 return (
string)
$config[
'activ'] ===
'1';
265 if (isset(
$config[
'active'])) {
266 return (
string)
$config[
'active'] ===
'1';
271 private function configEditUrl(
string $modul): string {
272 if ($modul ===
'dbx') {
273 return '?dbx_modul=dbxAdmin&dbx_run1=config&dbx_run2=edit&xmodul=dbx';
276 $file =
'dbx/modules/' . $modul .
'/cfg/config.php';
277 return '?dbx_modul=dbxEditor&dbx_run1=edit&file=' . rawurlencode($file);
280 private function moduleClassFiles(
string $modul): array {
281 $base = $this->modulesRoot . $modul . DIRECTORY_SEPARATOR;
283 $main =
$base . $modul .
'.class.php';
284 if (is_file($main)) {
287 $inc =
$base .
'include' . DIRECTORY_SEPARATOR;
289 foreach (glob($inc .
'*.class.php') ?: array() as $path) {
290 if (is_file($path)) {
299 return $this->scanRuns($modul);
302 private function scanRuns(
string $modul): array {
308 foreach ($this->moduleClassFiles($modul) as $file) {
309 $src = @file_get_contents($file);
310 if (!is_string($src) || $src ===
'') {
314 if (preg_match(
"/get_modul_var\s*\(\s*['\"]dbx_run2['\"]/", $src)) {
318 if ($defaultRun1 ===
'' && preg_match(
"/get_modul_var\s*\(\s*['\"]dbx_run1['\"][^,]*,\s*['\"]([^'\"]+)['\"]/", $src, $match)) {
319 $candidate = trim((
string)$match[1]);
320 if ($candidate !==
'' && $candidate !==
'parameter') {
321 $defaultRun1 = $candidate;
325 if ($defaultRun2 ===
'' && preg_match(
"/get_modul_var\s*\(\s*['\"]dbx_run2['\"][^,]*,\s*['\"]([^'\"]+)['\"]/", $src, $match)) {
326 $candidate = trim((
string)$match[1]);
327 if ($candidate !==
'' && $candidate !==
'parameter') {
328 $defaultRun2 = $candidate;
332 if (preg_match_all(
"/case\s+['\"]([^'\"]+)['\"]\s*:/", $src, $matches)) {
333 foreach ($matches[1] as $case) {
334 $case = trim((
string)$case);
335 if ($case ===
'' || $case ===
'default') {
342 if (preg_match_all(
'/\$(?:run|action|work)\s*===?\s*[\'"]([^\'"]+)[\'"]/', $src, $ifs)) {
343 foreach ($ifs[1] as $case) {
344 $case = trim((
string)$case);
352 if ($defaultRun1 ===
'') {
353 $keys = array_keys($run1);
354 $defaultRun1 = $keys ? (string)$keys[0] :
'run';
357 if ($modul ===
'dbxAdmin' && isset($run1[
'run'])) {
358 $defaultRun1 =
'run';
360 if ($modul ===
'dbxHome') {
365 'run1' => array_keys($run1),
366 'default_run1' => $defaultRun1,
367 'default_run2' => $defaultRun2,
368 'uses_run2' => $usesRun2,
372 private function detectInstall(
string $modul): array {
373 $candidates = array($modul);
374 if (substr($modul, -6) !==
'_admin') {
375 $candidates[] = $modul .
'_admin';
378 foreach ($candidates as $candidate) {
379 foreach ($this->moduleClassFiles($candidate) as $file) {
380 $src = @file_get_contents($file);
381 if (!is_string($src) || $src ===
'') {
384 if (!preg_match(
"/case\s+['\"]install['\"]\s*:/", $src)
385 && !preg_match(
'/\$(?:run|action|work)\s*===?\s*[\'"]install[\'"]/', $src)) {
390 'modul' => $candidate,
391 'url' => $this->buildModulUrl($candidate,
'install'),
399 private function detectDdUsage(
string $modul): array {
402 $ddDir = $this->modulesRoot . $modul . DIRECTORY_SEPARATOR .
'dd' . DIRECTORY_SEPARATOR;
403 if (is_dir($ddDir)) {
404 foreach (glob($ddDir .
'*.dd.php') ?: array() as $file) {
405 $name = basename($file,
'.dd.php');
406 if ($name ===
'' || $name ===
'new' || str_starts_with($name,
'.')) {
409 $this->registerDdRef($found, $modul, $name);
413 foreach ($this->moduleClassFiles($modul) as $file) {
414 $src = @file_get_contents($file);
415 if (!is_string($src) || $src ===
'') {
419 if (preg_match_all(
"/['\"]" . preg_quote($modul,
'/') .
"\|([a-zA-Z][a-zA-Z0-9_]*)['\"]/", $src, $matches)) {
420 foreach ($matches[1] as $dd) {
421 $this->registerDdRef($found, $modul, $dd);
425 if (preg_match_all(
"/_dd\s*=\s*['\"]([a-zA-Z][a-zA-Z0-9_]*(?:\|[a-zA-Z][a-zA-Z0-9_]*)?)['\"]/", $src, $matches)) {
426 foreach ($matches[1] as $ddRef) {
427 $this->registerDdRefString($found, $modul, $ddRef);
431 if (preg_match_all(
"/load_dd\s*\(\s*['\"]([a-zA-Z][a-zA-Z0-9_]*(?:\|[a-zA-Z][a-zA-Z0-9_]*)?)['\"]/", $src, $matches)) {
432 foreach ($matches[1] as $ddRef) {
433 $this->registerDdRefString($found, $modul, $ddRef);
438 $list = array_keys($found);
439 sort($list, SORT_NATURAL | SORT_FLAG_CASE);
443 private function isValidIdent(
string $name): bool {
444 return (bool)preg_match(
'/^[a-zA-Z][a-zA-Z0-9_]*$/', $name);
447 private function ddFileExists(
string $modul,
string $dd): bool {
448 $path = $this->modulesRoot . $modul . DIRECTORY_SEPARATOR .
'dd' . DIRECTORY_SEPARATOR . $dd .
'.dd.php';
449 return is_file($path);
452 private function registerDdRef(array &$found,
string $modul,
string $dd): void {
453 $modul = trim($modul);
455 if (!$this->isValidIdent($modul) || !$this->isValidIdent($dd)) {
458 if (!$this->ddFileExists($modul, $dd)) {
461 $found[$modul .
'|' . $dd] =
true;
464 private function registerDdRefString(array &$found,
string $modul,
string $ddRef): void {
465 $ddRef = trim($ddRef);
469 if (strpos($ddRef,
'|') !==
false) {
470 $parts = explode(
'|', $ddRef, 2);
471 $this->registerDdRef($found, $parts[0], $parts[1]);
474 $this->registerDdRef($found, $modul, $ddRef);
477 private function detectDescription(
string $modul,
string $base, array $ddList = array()): string {
479 $base .
'cfg' . DIRECTORY_SEPARATOR .
'about.txt',
480 $base .
'cfg' . DIRECTORY_SEPARATOR .
'help.txt',
481 $base . $modul .
'.class.php',
482 $base .
'cfg' . DIRECTORY_SEPARATOR .
'config.php',
485 foreach ($candidates as $file) {
486 if (!is_file($file)) {
489 $text = $this->readLeadComment($file);
491 return $this->trimDescription($text);
495 $runs = $this->scanRuns($modul);
497 $parts[] =
'Standardaufruf: ' . $this->formatDefaultCall($modul, (
string)($runs[
'default_run1'] ??
'run'), (
string)($runs[
'default_run2'] ??
''));
499 $parts[] = count($ddList) .
' DataDic-Datei(en)';
502 return implode(
' · ', $parts);
505 private function readLeadComment(
string $file): string {
506 $src = @file_get_contents($file);
507 if (!is_string($src) || $src ===
'') {
511 if (preg_match(
'/\A\s*<\?php\s*/', $src)) {
512 $src = preg_replace(
'/\A\s*<\?php\s*/',
'', $src, 1);
515 if (preg_match(
'/\A\s*\/\*\*(.*?)\*\//s', $src, $match)) {
516 return $this->cleanCommentBlock($match[1]);
518 if (preg_match(
'/\A\s*\/\*(.*?)\*\//s', $src, $match)) {
519 return $this->cleanCommentBlock($match[1]);
525 private function cleanCommentBlock(
string $text): string {
526 $lines = preg_split(
'/\R/', $text);
528 foreach ($lines as $line) {
529 $line = preg_replace(
'/^\s*\*\s?/',
'', $line);
530 $line = trim((
string)$line);
531 if ($line ===
'' || preg_match(
'/^@/', $line)) {
536 return trim(implode(
' ', $out));
539 private function trimDescription(
string $text): string {
540 $text = preg_replace(
'/\s+/',
' ', trim($text));
541 if (strlen($text) > 320) {
542 $text = substr($text, 0, 317) .
'…';
548 return $this->resolveModGraphic($modul, $run1, $run2);
552 static $cache = array();
554 $lng = function_exists(
'dbx_lng_current')
556 : strtolower(trim((string)
dbx()->get_system_var(
'dbx_lng',
'de')));
560 if (isset($cache[$lng])) {
565 'modul-no-image_' . $lng .
'.svg',
566 'modul-no-image.svg',
567 'modul-no-image_de.svg',
568 'modul-no-image_en.svg',
572 foreach ($candidates as $file) {
581 $path = function_exists(
'dbx_lng_resolve_file')
582 ? dbx_lng_resolve_file(
$dir,
'modul-no-image',
'svg')
586 if ($path ===
'' || !is_file($path)) {
591 $file = basename(str_replace(
'\\',
'/', $path));
592 $cache[$lng] =
dbx()->get_base_url() .
'dbx/modules/dbxAdmin/tpl/img/' . rawurlencode($file);
597 static $cache = array();
598 $lng = function_exists(
'dbx_lng_current')
600 : strtolower(trim((string)
dbx()->get_system_var(
'dbx_lng',
'de')));
601 if (isset($cache[$lng])) {
605 $cache[$lng] =
'No module image selected';
609 $cache[$lng] =
'Sin imagen de módulo seleccionada';
612 $cache[$lng] =
'Kein Modulbild ausgewählt';
616 private function resolveModGraphic(
string $modul,
string $run1,
string $run2): array {
617 $urlBase =
dbx()->get_base_url() .
'dbx/modules/' . rawurlencode($modul) .
'/';
618 $modDir = $this->modulesRoot . $modul . DIRECTORY_SEPARATOR .
'tpl' . DIRECTORY_SEPARATOR .
'mod' . DIRECTORY_SEPARATOR;
620 $candidates = array();
621 if ($run1 !==
'' && $run2 !==
'') {
622 $candidates[] = $modul .
'_' . $run1 .
'_' . $run2 .
'.svg';
625 $candidates[] = $modul .
'_' . $run1 .
'.svg';
627 $candidates[] = $modul .
'.svg';
629 if (is_dir($modDir)) {
630 foreach ($candidates as $name) {
631 if (is_file($modDir . $name)) {
633 'url' => $urlBase .
'tpl/mod/' . rawurlencode($name),
634 'alt' => $modul .
' ' . $run1,
640 $any = glob($modDir .
'*.svg');
641 if (is_array($any) && isset($any[0]) && is_file($any[0])) {
642 $name = basename($any[0]);
644 'url' => $urlBase .
'tpl/mod/' . rawurlencode($name),
651 $imgPath = $this->modulesRoot . $modul . DIRECTORY_SEPARATOR .
'tpl' . DIRECTORY_SEPARATOR .
'img' . DIRECTORY_SEPARATOR .
'modul.gif';
652 if (is_file($imgPath)) {
654 'url' => $urlBase .
'tpl/img/modul.gif',
656 'badge' =>
'modul.gif',
661 'url' =>
dbx()->get_base_url() .
'dbx/modules/dbxAdmin/tpl/mod/dbxAdmin_modul.svg',
663 'badge' =>
'placeholder',
667 public function modulUrl(
string $modul,
string $run1 =
'',
string $run2 =
''): string {
668 return $this->buildModulUrl($modul, $run1, $run2);
672 $modul = trim($modul);
673 if ($modul ===
'' || !preg_match(
'/^[a-zA-Z][a-zA-Z0-9_]*$/', $modul)) {
676 $protected = array(
'dbx',
'dbxAdmin',
'dbxLogin',
'dbxHome');
677 if (in_array($modul, $protected,
true)) {
680 $dir = $this->modulesRoot . $modul;
684 private function buildModulUrl(
string $modul,
string $run1 =
'',
string $run2 =
''): string {
685 $url =
'?dbx_modul=' . rawurlencode($modul);
687 $url .=
'&dbx_run1=' . rawurlencode($run1);
690 $url .=
'&dbx_run2=' . rawurlencode($run2);
695 private function formatDefaultCall(
string $modul,
string $run1,
string $run2): string {
696 $parts = array(
'dbx_modul=' . $modul);
698 $parts[] =
'dbx_run1=' . $run1;
701 $parts[] =
'dbx_run2=' . $run2;
703 return implode(
'&', $parts);
707 $modul = trim($modul);
711 $path = $this->modulesRoot . $modul . DIRECTORY_SEPARATOR .
'tpl' . DIRECTORY_SEPARATOR .
'htm' . DIRECTORY_SEPARATOR .
'modul-help.htm';
712 return is_file($path) ? $path :
'';
716 $modul = trim($modul);
721 $tpl =
dbx()->get_system_obj(
'dbxTPL');
722 if ($this->moduleHelpPath($modul) ===
'') {
723 if (!isset($data[
'modul'])) {
724 $data[
'modul'] =
dbx()->esc($modul);
726 return $tpl->get_tpl(
'dbxAdmin|modul-help-fallback', $data);
729 return $tpl->get_tpl($modul .
'|modul-help', $data);