12 private $listCache = array();
13 private $imageItemsCache = array();
14 private $moduleSymbolCache = array();
16 private function imageExtensionAliases(): array {
24 private function blockedImageExtensions(): array {
25 return array(
'php',
'phtml',
'phar',
'svg',
'js',
'html',
'htm');
28 private function allowedImageExtensions(): array {
29 return array(
'jpg',
'jpeg',
'png',
'gif',
'webp',
'bmp',
'avif',
'tiff',
'tif',
'ico',
'heic',
'heif');
32 private function normalizeImageExtension(
string $ext,
string $fallback =
'jpg'): string {
33 $ext = strtolower(preg_replace(
'/[^a-z0-9]+/',
'', $ext));
34 $aliases = $this->imageExtensionAliases();
35 if (isset($aliases[$ext])) {
36 $ext = $aliases[$ext];
38 if ($ext ===
'' || strlen($ext) > 8 || in_array($ext, $this->blockedImageExtensions(),
true)) {
44 private function extensionFromImagePath(
string $path,
string $fallback =
'jpg'): string {
45 $path = (string) $path;
46 if ($path !==
'' && is_file($path) && is_readable($path)) {
47 $info = @getimagesize($path);
48 if (is_array($info)) {
49 $mime = strtolower((
string) ($info[
'mime'] ??
''));
51 'image/jpeg' =>
'jpg',
54 'image/webp' =>
'webp',
56 'image/x-ms-bmp' =>
'bmp',
57 'image/avif' =>
'avif',
58 'image/tiff' =>
'tiff',
59 'image/x-icon' =>
'ico',
60 'image/vnd.microsoft.icon' =>
'ico',
61 'image/heic' =>
'heic',
62 'image/heif' =>
'heif',
64 if (isset($fromMime[$mime])) {
65 return $fromMime[$mime];
68 $ext = strtolower(preg_replace(
'/[^a-z0-9]+/',
'', pathinfo($path, PATHINFO_EXTENSION)));
69 if ($ext !==
'' && strlen($ext) <= 8) {
75 return $this->normalizeImageExtension(pathinfo($path, PATHINFO_EXTENSION), $fallback);
78 private function isImagePath(
string $path): bool {
79 if ($path ===
'' || !is_file($path) || !is_readable($path)) {
83 $info = @getimagesize($path);
84 if (!is_array($info) || (
int) ($info[0] ?? 0) <= 0 || (
int) ($info[1] ?? 0) <= 0) {
88 $mime = strtolower((
string) ($info[
'mime'] ??
''));
89 if ($mime ===
'' || strpos($mime,
'image/') !== 0) {
93 return $mime !==
'image/svg+xml';
96 private function mimeForExtension(
string $ext): string {
97 $ext = $this->normalizeImageExtension($ext);
99 'jpg' =>
'image/jpeg',
100 'png' =>
'image/png',
101 'gif' =>
'image/gif',
102 'webp' =>
'image/webp',
103 'bmp' =>
'image/bmp',
104 'avif' =>
'image/avif',
105 'tiff' =>
'image/tiff',
106 'ico' =>
'image/x-icon',
107 'heic' =>
'image/heic',
108 'heif' =>
'image/heif',
111 return $map[$ext] ?? (
'image/' . $ext);
119 public function relPath(
string $filename): string {
120 $filename = $this->sanitizeFilename($filename);
121 return $filename ===
'' ?
'' : self::REL_DIR . $filename;
125 return
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_images_media';
129 return
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_images_media_folders';
133 return rtrim(
dbx()->get_base_url(),
'/\\') .
'/files/' . self::
REL_DIR;
137 $modul = $this->sanitizeModul($modul);
138 if ($modul ===
'' || !$this->isKnownModul($modul)) {
143 if ($create && !is_dir(
$dir)) {
144 @mkdir(
$dir, 0777,
true);
147 return is_dir(
$dir) ? rtrim(
$dir,
'/\\') . DIRECTORY_SEPARATOR :
$dir;
151 $modul = $this->sanitizeModul($modul);
156 return rtrim(
dbx()->get_base_url(),
'/\\') .
'/dbx/modules/' . rawurlencode($modul) .
'/tpl/img/';
160 $modul = $this->sanitizeModul($modul);
162 return array(
'url' =>
'',
'alt' =>
'',
'badge' =>
'');
164 if (isset($this->moduleSymbolCache[$modul])) {
165 return $this->moduleSymbolCache[$modul];
168 $dir = $this->moduleSymbolDir($modul,
false);
170 $matches = glob(
$dir . $modul .
'.*') ?: array();
171 usort($matches,
function ($a, $b) {
172 $prio = array(
'jpg' => 1,
'jpeg' => 2,
'png' => 3,
'webp' => 4,
'gif' => 5);
173 $ea = strtolower(pathinfo((
string)$a, PATHINFO_EXTENSION));
174 $eb = strtolower(pathinfo((
string)$b, PATHINFO_EXTENSION));
175 $pa = $prio[$ea] ?? 99;
176 $pb = $prio[$eb] ?? 99;
177 return ($pa <=> $pb) ?: strnatcasecmp((
string)$a, (
string)$b);
180 foreach ($matches as $path) {
181 if (!$this->isImageFile(basename((
string)$path), (
string)$path)) {
185 $file = basename((
string)$path);
186 $this->moduleSymbolCache[$modul] = array(
187 'url' => $this->moduleSymbolUrlBase($modul) . rawurlencode($file) .
'?v=' . (
int) @filemtime((
string)$path),
189 'badge' =>
'tpl/img/' . $file,
191 'path' =>
'dbx/modules/' . $modul .
'/tpl/img/' . $file,
193 return $this->moduleSymbolCache[$modul];
197 $this->moduleSymbolCache[$modul] = array(
200 'badge' =>
'tpl/img/' . $modul .
'.*',
202 'path' =>
'dbx/modules/' . $modul .
'/tpl/img/' . $modul .
'.*',
204 return $this->moduleSymbolCache[$modul];
208 $modul = $this->sanitizeModul($modul);
209 if ($modul ===
'' || !$this->isKnownModul($modul)) {
215 $db =
dbx()->get_system_obj(
'dbxDB');
216 if (!is_object(
$db)) {
219 $row =
$db->select1(
'dbxMedia', $mediaId);
220 if (!is_array($row) || (
int)($row[
'active'] ?? 0) !== 1) {
223 $rel = ltrim(str_replace(
'\\',
'/', (
string)($row[
'file_path'] ??
'')),
'/');
224 } elseif ($sourceRel !==
'') {
225 $rel = ltrim(str_replace(
'\\',
'/', trim($sourceRel)),
'/');
228 if ($rel ===
'' || strpos($rel,
'..') !==
false) {
232 $source =
dbx()->os_path(rtrim(
dbx()->get_file_dir(),
'/\\') .
'/' . $rel);
233 if (!$this->isImagePath($source)) {
237 $dir = $this->moduleSymbolDir($modul,
true);
242 $ext = $this->extensionFromImagePath($source);
243 $targetFile = $modul .
'.' . $ext;
244 $target =
$dir . $targetFile;
245 $this->removeModuleSymbolVariants($modul, $targetFile);
247 if ($source === $target && is_file($target)) {
248 return $this->moduleSymbol($modul);
251 if (is_file($target)) {
255 if (!@copy($source, $target)) {
259 return $this->moduleSymbol($modul);
263 $filename = $this->sanitizeFilename($filename);
264 if ($filename ===
'' || !$this->isImageFile($filename)) {
265 http_response_code(404);
269 $dir = realpath($this->absDir());
270 $file = realpath($this->absPath($filename));
271 if (!
$dir || !$file || strpos($file,
$dir) !== 0 || !$this->isImagePath($file)) {
272 http_response_code(404);
276 $mime = function_exists(
'mime_content_type') ? (string) @mime_content_type($file) :
'';
277 if ($mime ===
'' || strpos(strtolower($mime),
'image/') !== 0) {
278 $mime = $this->mimeForExtension(pathinfo($filename, PATHINFO_EXTENSION));
281 if (function_exists(
'session_status') && session_status() === PHP_SESSION_ACTIVE) {
282 session_write_close();
285 $size = (int)@filesize($file);
286 header(
'Content-Type: ' . $mime);
287 header(
'Content-Length: ' . max(0, $size));
288 header(
'Content-Disposition: inline; filename="' . str_replace(
'"',
'', $filename) .
'"');
289 header(
'Cache-Control: public, max-age=86400');
290 header(
'X-Content-Type-Options: nosniff');
298 mkdir(
$dir, 0777,
true);
302 public function getList(
string $modul): array {
307 $modul = $this->sanitizeModul($modul);
311 if (isset($this->listCache[$modul])) {
312 return $this->listCache[$modul];
316 $dir = $this->absDir();
317 if (!is_dir(
$dir) || !is_readable(
$dir)) {
318 $this->listCache[$modul] = array();
323 foreach (glob(
$dir .
'*') ?: array() as $path) {
324 if (!is_file($path) || !is_readable($path)) {
327 $name = $this->sanitizeFilename(basename($path));
328 if ($name ===
'' || !$this->isImageFile($name, $path)) {
331 if ($this->fileBelongsToModul($modul, $name)) {
336 $out = array_values(array_unique($out));
337 sort($out, SORT_NATURAL | SORT_FLAG_CASE);
338 $this->listCache[$modul] = $out;
339 return $this->listCache[$modul];
342 public function saveList(
string $modul, array $files): bool {
343 $modul = $this->sanitizeModul($modul);
348 $allowed = array_flip($this->scanList($modul));
349 foreach ($files as $file) {
350 $name = $this->sanitizeFilename((
string)$file);
351 if ($name !==
'' && !isset($allowed[$name])) {
359 public function addToList(
string $modul,
string $filename): bool {
360 $filename = $this->sanitizeFilename($filename);
361 return $filename !==
''
363 && $this->fileBelongsToModul($modul, $filename);
366 public function removeFromList(
string $modul,
string $filename,
bool $deletePhysical =
true): bool {
367 $filename = $this->sanitizeFilename($filename);
368 if ($filename ===
'' || !$this->fileBelongsToModul($modul, $filename)) {
372 if ($deletePhysical) {
373 $path = $this->absPath($filename);
374 if (is_file($path)) {
382 public function importForModul(
string $modul,
int $mediaId = 0,
string $sourceRel =
'',
string $run1 =
'',
string $run2 =
''): ?string {
383 $modul = $this->sanitizeModul($modul);
384 $run1 = $this->sanitizeRunPart($run1);
385 $run2 = $this->sanitizeRunPart($run2);
386 if ($modul ===
'' || $run1 ===
'') {
392 $db =
dbx()->get_system_obj(
'dbxDB');
393 if (!is_object(
$db)) {
396 $row =
$db->select1(
'dbxMedia', $mediaId);
397 if (!is_array($row) || (
int)($row[
'active'] ?? 0) !== 1) {
400 $rel = ltrim(str_replace(
'\\',
'/', (
string)($row[
'file_path'] ??
'')),
'/');
401 } elseif ($sourceRel !==
'') {
402 $rel = ltrim(str_replace(
'\\',
'/', trim($sourceRel)),
'/');
405 if ($rel ===
'' || strpos($rel,
'..') !==
false) {
409 $source = rtrim(
dbx()->get_file_dir(),
'/\\') .
'/' . $rel;
410 $source =
dbx()->os_path($source);
411 if (!is_file($source) || !is_readable($source)) {
415 if (!$this->isImagePath($source)) {
419 $ext = $this->extensionFromImagePath($source);
420 $targetName = $this->filenameForRuns($modul, $run1, $run2, $ext);
421 if ($targetName ===
'') {
426 $dest = $this->absPath($targetName);
427 if ($dest !==
'' && $source === $dest && is_file($dest)) {
431 return $this->copyFromMediaRel($modul, $rel, $targetName);
434 public function saveFromUpload(
string $modul,
string $run1,
string $run2, array $file): ?string {
435 $modul = $this->sanitizeModul($modul);
436 $run1 = $this->sanitizeRunPart($run1);
437 $run2 = $this->sanitizeRunPart($run2);
438 if ($modul ===
'' || $run1 ===
'' || empty($file) || !is_array($file)) {
441 if (($file[
'error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
445 $tmp = (string)($file[
'tmp_name'] ??
'');
446 if ($tmp ===
'' || !is_uploaded_file($tmp)) {
450 if (!$this->isImagePath($tmp)) {
454 $ext = $this->extensionFromImagePath($tmp, $this->extensionFromImagePath((
string) ($file[
'name'] ??
'')));
455 $targetName = $this->filenameForRuns($modul, $run1, $run2, $ext);
456 if ($targetName ===
'') {
461 $dest = $this->absPath($targetName);
466 if (is_file($dest)) {
470 if (!@move_uploaded_file($tmp, $dest)) {
477 public function stemForRuns(
string $modul,
string $run1,
string $run2 =
''): string {
478 $modul = $this->sanitizeModul($modul);
479 $run1 = $this->sanitizeRunPart($run1);
480 $run2 = $this->sanitizeRunPart($run2);
481 if ($modul ===
'' || $run1 ===
'') {
485 $stem = $modul . self::FILENAME_PARAM_SEP . $run1;
487 $stem .= self::FILENAME_PARAM_SEP . $run2;
493 public function filenameForRuns(
string $modul,
string $run1,
string $run2 =
'',
string $ext =
'jpg'): string {
494 $modul = $this->sanitizeModul($modul);
495 $run1 = $this->sanitizeRunPart($run1);
496 $run2 = $this->sanitizeRunPart($run2);
497 $ext = $this->normalizeImageExtension($ext);
499 if ($modul ===
'' || $run1 ===
'') {
503 return $this->stemForRuns($modul, $run1, $run2) .
'.' . $ext;
506 public function getUrl(
string $filename): string {
507 $filename = $this->sanitizeFilename($filename);
508 if ($filename ===
'') {
511 return $this->urlBase() . rawurlencode($filename);
515 $path = $this->
absPath($filename);
516 return $path !==
'' && is_file($path) && is_readable($path);
519 public function absPath(
string $filename): string {
520 $filename = $this->sanitizeFilename($filename);
521 if ($filename ===
'') {
524 return $this->absDir() . $filename;
527 public function copyFromMediaRel(
string $modul,
string $sourceRel,
string $targetName =
''): ?string {
528 $modul = $this->sanitizeModul($modul);
529 $sourceRel = ltrim(str_replace(
'\\',
'/', trim($sourceRel)),
'/');
530 if ($modul ===
'' || $sourceRel ===
'' || strpos($sourceRel,
'..') !==
false) {
534 $source = rtrim(
dbx()->get_file_dir(),
'/\\') .
'/' . $sourceRel;
535 $source =
dbx()->os_path($source);
536 if (!is_file($source) || !is_readable($source)) {
540 if (!$this->isImagePath($source)) {
544 if ($targetName ===
'') {
545 $targetName = $this->suggestFilename($modul, basename($source));
547 $targetName = $this->sanitizeFilename($targetName);
548 if ($targetName !==
'' && pathinfo($targetName, PATHINFO_EXTENSION) ===
'') {
549 $targetName .=
'.' . $this->extensionFromImagePath($source);
553 if ($targetName ===
'') {
558 $dest = $this->absPath($targetName);
563 if (is_file($dest) && $source !== $dest) {
567 if (!@copy($source, $dest)) {
579 $modul = $this->sanitizeModul($modul);
580 $base = pathinfo(basename($sourceName), PATHINFO_FILENAME);
581 $base = preg_replace(
'/[^A-Za-z0-9_-]+/',
'-', (
string)
$base);
583 $ext = $this->normalizeImageExtension(pathinfo($sourceName, PATHINFO_EXTENSION));
589 $prefix = $modul . self::FILENAME_PARAM_SEP;
590 if (strpos(
$base, $prefix) !== 0 &&
$base !== $modul) {
594 $name =
$base .
'.' . $ext;
595 if (!$this->fileExists($name)) {
601 $candidate =
$base . self::FILENAME_PARAM_SEP . $i .
'.' . $ext;
602 if (!$this->fileExists($candidate)) {
608 return $base . self::FILENAME_PARAM_SEP . substr(md5((
string)microtime(
true)), 0, 6) .
'.' . $ext;
612 $filename = $this->sanitizeFilename($filename);
613 if ($filename ===
'' || !$this->isImageFile($filename) || !$this->
fileExists($filename)) {
617 $modul = $this->resolveModulFromFilename($filename);
622 $item = $this->catalogItem($modul, $filename);
623 return is_array($item) ? $item : array();
627 $filename = $this->sanitizeFilename($filename);
628 if ($filename ===
'') {
632 $base = pathinfo($filename, PATHINFO_FILENAME);
637 $sep = self::FILENAME_PARAM_SEP;
638 if (strpos(
$base, $sep) !==
false) {
639 $modul = $this->sanitizeModul((
string) strstr(
$base, $sep,
true));
640 if ($modul !==
'' && $this->isKnownModul($modul)) {
646 if ($this->isKnownModul(
$base)) {
647 return $this->sanitizeModul(
$base);
654 $modul = $this->sanitizeModul($modul);
659 $scan = $this->scanRuns($modul);
662 foreach ($this->getList($modul) as $file) {
663 $items[] = $this->catalogItem($modul, $file, $scan);
669 public function catalogItem(
string $modul,
string $filename, array $scan =
null): array {
670 $modul = $this->sanitizeModul($modul);
671 $filename = $this->sanitizeFilename($filename);
672 if ($modul ===
'' || $filename ===
'') {
676 if (!is_array($scan)) {
677 $scan = $this->scanRuns($modul);
680 $parsed = $this->parseImageName($modul, $filename, $scan);
681 $label = (string)($parsed[
'label'] ?? $filename);
682 $params = (string)($parsed[
'default_params'] ??
'');
683 $marker =
'[modul=' . $modul .
']' . $params .
'[/modul]';
686 'id' => pathinfo($filename, PATHINFO_FILENAME),
689 'description' => $marker,
690 'url' => $this->getUrl($filename),
691 'default_modul' => $modul,
692 'default_params' => $params,
693 'default_alt' => $modul .
': ' . $label,
694 'run1' => (
string)($parsed[
'run1'] ??
''),
695 'run2' => (
string)($parsed[
'run2'] ??
''),
700 return count($this->
getList($modul));
703 public function primaryGraphic(
string $modul,
string $run1 =
'',
string $run2 =
''): array {
704 $modul = $this->sanitizeModul($modul);
705 $list = $this->
getList($modul);
707 return array(
'url' =>
'',
'alt' => $modul,
'badge' =>
'');
710 $scan = $this->scanRuns($modul);
711 $candidates = array();
714 $candidates[] = $this->stemForRuns($modul, $run1, $run2);
716 $candidates[] = $this->stemForRuns($modul, $run1,
'');
718 $candidates[] = $modul;
720 foreach ($candidates as $stem) {
721 foreach ($list as $file) {
722 $base = pathinfo($file, PATHINFO_FILENAME);
723 if (
$base === $stem) {
725 'url' => $this->getUrl($file),
735 'url' => $this->getUrl($first),
742 $modul = $this->sanitizeModul($modul);
746 if (isset($this->imageItemsCache[$modul])) {
747 return $this->imageItemsCache[$modul];
750 $scan = $this->scanRuns($modul);
752 foreach ($this->getList($modul) as $file) {
753 $item = $this->catalogItem($modul, $file, $scan);
758 $this->imageItemsCache[$modul] = $items;
759 return $this->imageItemsCache[$modul];
763 $modulFilter = $this->sanitizeModul($modulFilter);
766 if (!is_dir(
$dir) || !is_readable(
$dir)) {
771 foreach (glob(
$dir .
'*') ?: array() as $path) {
772 if (!is_file($path) || !is_readable($path)) {
775 $name = $this->sanitizeFilename(basename($path));
776 if ($name ===
'' || !$this->isImageFile($name, $path)) {
779 if ($modulFilter !==
'' && !$this->fileBelongsToModul($modulFilter, $name)) {
783 $rel = $this->relPath($name);
784 $mime = function_exists(
'mime_content_type') ? (string) @mime_content_type($path) :
'';
785 if ($mime ===
'' || strpos(strtolower($mime),
'image/') !== 0) {
786 $mime = $this->mimeForExtension(pathinfo($name, PATHINFO_EXTENSION));
789 $title = pathinfo($name, PATHINFO_FILENAME);
791 'id' => abs(crc32($rel)),
792 'file_name' => $name,
796 'url' => $this->getUrl($name),
797 'thumb_url' => $this->getUrl($name),
799 'media_type' =>
'image',
800 'media_folder' =>
'mod',
802 'size' => (
int)@filesize($path),
806 usort($rows,
function ($a, $b) {
807 return strnatcasecmp((
string)($a[
'file_name'] ??
''), (
string)($b[
'file_name'] ??
''));
813 private function renameForModul(
string $modul,
string $filename): ?string {
814 $filename = $this->sanitizeFilename($filename);
815 if ($filename ===
'' || !$this->fileExists($filename)) {
819 $targetName = $this->suggestFilename($modul, $filename);
820 if ($targetName === $filename) {
824 $source = $this->absPath($filename);
825 $dest = $this->absPath($targetName);
826 if ($source ===
'' || $dest ===
'' || !@rename($source, $dest)) {
827 return $this->copyFromMediaRel($modul, $this->relPath($filename), $targetName);
833 private function fileBelongsToModul(
string $modul,
string $filename): bool {
834 $modul = $this->sanitizeModul($modul);
839 return $this->resolveModulFromFilename($filename) === $modul;
842 private function removeModuleSymbolVariants(
string $modul,
string $keepFile =
''): void {
843 $modul = $this->sanitizeModul($modul);
844 $keepFile = $this->sanitizeFilename($keepFile);
845 $dir = $this->moduleSymbolDir($modul,
false);
846 if ($modul ===
'' ||
$dir ===
'' || !is_dir(
$dir)) {
850 foreach (glob(
$dir . $modul .
'.*') ?: array() as $path) {
851 $file = basename((
string)$path);
852 if ($file === $keepFile) {
855 if (is_file($path)) {
861 private function isImageFile(
string $filename,
string $absPath =
''): bool {
862 $filename = $this->sanitizeFilename($filename);
863 if ($filename ===
'') {
867 if ($absPath ===
'') {
868 $absPath = $this->absPath($filename);
871 $ext = strtolower(preg_replace(
'/[^a-z0-9]+/',
'', pathinfo($filename, PATHINFO_EXTENSION)));
872 if ($ext ===
'' || strlen($ext) > 8 || in_array($ext, $this->blockedImageExtensions(),
true)) {
876 return in_array($ext, $this->allowedImageExtensions(),
true);
879 private function scanRuns(
string $modul): array {
880 static $cache = array();
881 if (isset($cache[$modul])) {
882 return $cache[$modul];
885 $cache[$modul] = $this->scanRunsInline($modul);
886 return $cache[$modul];
889 private function scanRunsInline(
string $modul): array {
892 $base =
dbx()->os_path(
dbx()->get_base_dir() .
'dbx/modules/' . $modul . DIRECTORY_SEPARATOR);
894 $main =
$base . $modul .
'.class.php';
895 if (is_file($main)) {
898 $inc =
$base .
'include' . DIRECTORY_SEPARATOR;
900 foreach (glob($inc .
'*.class.php') ?: array() as $path) {
901 if (is_file($path)) {
907 foreach ($files as $file) {
908 $src = @file_get_contents($file);
909 if (!is_string($src) || $src ===
'') {
912 if (preg_match(
"/get_modul_var\s*\(\s*['\"]dbx_run2['\"]/", $src)) {
915 if (preg_match_all(
"/case\s+['\"]([^'\"]+)['\"]\s*:/", $src, $matches)) {
916 foreach ($matches[1] as $case) {
917 $case = trim((
string)$case);
918 if ($case !==
'' && $case !==
'default') {
925 $run1List = array_keys($run1);
926 usort($run1List,
function ($a, $b) {
927 return strlen($b) <=> strlen($a);
931 'run1' => array_values(array_keys($run1)),
932 'uses_run2' => $usesRun2,
933 'run1_sorted' => $run1List,
937 private function parseImageName(
string $modul,
string $filename, array $scan): array {
938 $base = pathinfo($filename, PATHINFO_FILENAME);
939 if (
$base === $modul) {
943 'default_params' =>
'',
948 $sep = self::FILENAME_PARAM_SEP;
949 $prefix = $modul . $sep;
950 if (strpos(
$base, $prefix) === 0) {
951 $rest = substr(
$base, strlen($prefix));
952 $parts = explode($sep, $rest, 2);
953 $run1 = trim((
string)($parts[0] ??
''));
954 $run2 = trim((
string)($parts[1] ??
''));
955 return $this->buildParsedRuns($run1, $run2,
$base);
961 'default_params' =>
'',
966 private function buildParsedRuns(
string $run1,
string $run2,
string $fallbackLabel): array {
967 $params = $this->buildParams($run1, $run2);
968 $label = $this->runLabel($run1);
970 $label .=
' / ' . $this->runLabel($run2);
973 $label = $fallbackLabel;
979 'default_params' => $params,
984 private function knownModulNames(): array {
985 static $names = null;
986 if (is_array($names)) {
996 foreach (glob(
$dir .
'*', GLOB_ONLYDIR) ?: array() as $path) {
997 $name = basename(str_replace(
'\\',
'/', $path));
998 if (!preg_match(
'/^[A-Za-z][A-Za-z0-9_]*$/', $name)) {
1001 $class =
dbx_os_path_file($path . DIRECTORY_SEPARATOR . $name .
'.class.php');
1002 if (!is_file($class)) {
1008 usort($names,
function ($a, $b) {
1009 return strlen($b) <=> strlen($a);
1015 private function isKnownModul(
string $modul): bool {
1016 $modul = $this->sanitizeModul($modul);
1017 if ($modul ===
'') {
1021 foreach ($this->knownModulNames() as $name) {
1022 if ($name === $modul) {
1030 private function buildParams(
string $run1,
string $run2 =
''): string {
1033 $params[] =
'dbx_run1=' . rawurlencode($run1);
1036 $params[] =
'dbx_run2=' . rawurlencode($run2);
1038 return implode(
'&', $params);
1041 private function runLabel(
string $action): string {
1042 $action = trim($action);
1043 if ($action ===
'') {
1046 return ucfirst(str_replace(
'_',
' ', $action));
1049 private function sanitizeModul(
string $modul): string {
1050 return preg_replace(
'/[^A-Za-z0-9_]+/',
'', trim($modul));
1053 private function sanitizeRunPart(
string $part): string {
1054 return preg_replace(
'/[^A-Za-z0-9_-]+/',
'', trim($part));
1057 private function sanitizeFilename(
string $filename): string {
1058 $filename = basename(str_replace(
'\\',
'/', trim($filename)));
1059 $filename = preg_replace(
'/[^A-Za-z0-9_.-]+/',
'-', $filename);
1060 return trim($filename,
'-.');