dbXapp 2.0
RAD, CMS, Module und Runtime-IDE fuer dbXapp
Loading...
Searching...
No Matches
dbxModuleImages.class.php
Go to the documentation of this file.
1<?php
2namespace dbx\dbxAdmin;
3
8
9 public const REL_DIR = 'mod/';
10 public const FILENAME_PARAM_SEP = '__';
11
12 private $listCache = array();
13 private $imageItemsCache = array();
14 private $moduleSymbolCache = array();
15
16 private function imageExtensionAliases(): array {
17 return array(
18 'jpeg' => 'jpg',
19 'jpe' => 'jpg',
20 'tif' => 'tiff',
21 );
22 }
23
24 private function blockedImageExtensions(): array {
25 return array('php', 'phtml', 'phar', 'svg', 'js', 'html', 'htm');
26 }
27
28 private function allowedImageExtensions(): array {
29 return array('jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'avif', 'tiff', 'tif', 'ico', 'heic', 'heif');
30 }
31
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];
37 }
38 if ($ext === '' || strlen($ext) > 8 || in_array($ext, $this->blockedImageExtensions(), true)) {
39 return $fallback;
40 }
41 return $ext;
42 }
43
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'] ?? ''));
50 $fromMime = array(
51 'image/jpeg' => 'jpg',
52 'image/png' => 'png',
53 'image/gif' => 'gif',
54 'image/webp' => 'webp',
55 'image/bmp' => 'bmp',
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',
63 );
64 if (isset($fromMime[$mime])) {
65 return $fromMime[$mime];
66 }
67
68 $ext = strtolower(preg_replace('/[^a-z0-9]+/', '', pathinfo($path, PATHINFO_EXTENSION)));
69 if ($ext !== '' && strlen($ext) <= 8) {
70 return $ext;
71 }
72 }
73 }
74
75 return $this->normalizeImageExtension(pathinfo($path, PATHINFO_EXTENSION), $fallback);
76 }
77
78 private function isImagePath(string $path): bool {
79 if ($path === '' || !is_file($path) || !is_readable($path)) {
80 return false;
81 }
82
83 $info = @getimagesize($path);
84 if (!is_array($info) || (int) ($info[0] ?? 0) <= 0 || (int) ($info[1] ?? 0) <= 0) {
85 return false;
86 }
87
88 $mime = strtolower((string) ($info['mime'] ?? ''));
89 if ($mime === '' || strpos($mime, 'image/') !== 0) {
90 return false;
91 }
92
93 return $mime !== 'image/svg+xml';
94 }
95
96 private function mimeForExtension(string $ext): string {
97 $ext = $this->normalizeImageExtension($ext);
98 $map = array(
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',
109 );
110
111 return $map[$ext] ?? ('image/' . $ext);
112 }
113
114 public function absDir(): string {
115 $dir = rtrim(dbx()->get_file_dir(), '/\\') . '/' . self::REL_DIR;
116 return dbx()->os_path($dir);
117 }
118
119 public function relPath(string $filename): string {
120 $filename = $this->sanitizeFilename($filename);
121 return $filename === '' ? '' : self::REL_DIR . $filename;
122 }
123
124 public function mediaApiUrl(): string {
125 return '?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_images_media';
126 }
127
128 public function mediaFoldersApiUrl(): string {
129 return '?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_images_media_folders';
130 }
131
132 public function urlBase(): string {
133 return rtrim(dbx()->get_base_url(), '/\\') . '/files/' . self::REL_DIR;
134 }
135
136 public function moduleSymbolDir(string $modul, bool $create = false): string {
137 $modul = $this->sanitizeModul($modul);
138 if ($modul === '' || !$this->isKnownModul($modul)) {
139 return '';
140 }
141
142 $dir = dbx_os_path_file(dbx_get_base_dir() . 'dbx/modules/' . $modul . '/tpl/img/');
143 if ($create && !is_dir($dir)) {
144 @mkdir($dir, 0777, true);
145 }
146
147 return is_dir($dir) ? rtrim($dir, '/\\') . DIRECTORY_SEPARATOR : $dir;
148 }
149
150 public function moduleSymbolUrlBase(string $modul): string {
151 $modul = $this->sanitizeModul($modul);
152 if ($modul === '') {
153 return '';
154 }
155
156 return rtrim(dbx()->get_base_url(), '/\\') . '/dbx/modules/' . rawurlencode($modul) . '/tpl/img/';
157 }
158
159 public function moduleSymbol(string $modul): array {
160 $modul = $this->sanitizeModul($modul);
161 if ($modul === '') {
162 return array('url' => '', 'alt' => '', 'badge' => '');
163 }
164 if (isset($this->moduleSymbolCache[$modul])) {
165 return $this->moduleSymbolCache[$modul];
166 }
167
168 $dir = $this->moduleSymbolDir($modul, false);
169 if ($dir !== '' && is_dir($dir) && is_readable($dir)) {
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);
178 });
179
180 foreach ($matches as $path) {
181 if (!$this->isImageFile(basename((string)$path), (string)$path)) {
182 continue;
183 }
184
185 $file = basename((string)$path);
186 $this->moduleSymbolCache[$modul] = array(
187 'url' => $this->moduleSymbolUrlBase($modul) . rawurlencode($file) . '?v=' . (int) @filemtime((string)$path),
188 'alt' => $modul,
189 'badge' => 'tpl/img/' . $file,
190 'file' => $file,
191 'path' => 'dbx/modules/' . $modul . '/tpl/img/' . $file,
192 );
193 return $this->moduleSymbolCache[$modul];
194 }
195 }
196
197 $this->moduleSymbolCache[$modul] = array(
198 'url' => '',
199 'alt' => $modul,
200 'badge' => 'tpl/img/' . $modul . '.*',
201 'file' => '',
202 'path' => 'dbx/modules/' . $modul . '/tpl/img/' . $modul . '.*',
203 );
204 return $this->moduleSymbolCache[$modul];
205 }
206
207 public function importSymbolForModul(string $modul, int $mediaId = 0, string $sourceRel = ''): ?array {
208 $modul = $this->sanitizeModul($modul);
209 if ($modul === '' || !$this->isKnownModul($modul)) {
210 return null;
211 }
212
213 $rel = '';
214 if ($mediaId > 0) {
215 $db = dbx()->get_system_obj('dbxDB');
216 if (!is_object($db)) {
217 return null;
218 }
219 $row = $db->select1('dbxMedia', $mediaId);
220 if (!is_array($row) || (int)($row['active'] ?? 0) !== 1) {
221 return null;
222 }
223 $rel = ltrim(str_replace('\\', '/', (string)($row['file_path'] ?? '')), '/');
224 } elseif ($sourceRel !== '') {
225 $rel = ltrim(str_replace('\\', '/', trim($sourceRel)), '/');
226 }
227
228 if ($rel === '' || strpos($rel, '..') !== false) {
229 return null;
230 }
231
232 $source = dbx()->os_path(rtrim(dbx()->get_file_dir(), '/\\') . '/' . $rel);
233 if (!$this->isImagePath($source)) {
234 return null;
235 }
236
237 $dir = $this->moduleSymbolDir($modul, true);
238 if ($dir === '' || !is_dir($dir)) {
239 return null;
240 }
241
242 $ext = $this->extensionFromImagePath($source);
243 $targetFile = $modul . '.' . $ext;
244 $target = $dir . $targetFile;
245 $this->removeModuleSymbolVariants($modul, $targetFile);
246
247 if ($source === $target && is_file($target)) {
248 return $this->moduleSymbol($modul);
249 }
250
251 if (is_file($target)) {
252 @unlink($target);
253 }
254
255 if (!@copy($source, $target)) {
256 return null;
257 }
258
259 return $this->moduleSymbol($modul);
260 }
261
262 public function serveFile(string $filename): void {
263 $filename = $this->sanitizeFilename($filename);
264 if ($filename === '' || !$this->isImageFile($filename)) {
265 http_response_code(404);
266 exit;
267 }
268
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);
273 exit;
274 }
275
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));
279 }
280
281 if (function_exists('session_status') && session_status() === PHP_SESSION_ACTIVE) {
282 session_write_close();
283 }
284
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');
291 readfile($file);
292 exit;
293 }
294
295 public function ensureDir(): void {
296 $dir = $this->absDir();
297 if (!is_dir($dir)) {
298 mkdir($dir, 0777, true);
299 }
300 }
301
302 public function getList(string $modul): array {
303 return $this->scanList($modul);
304 }
305
306 public function scanList(string $modul): array {
307 $modul = $this->sanitizeModul($modul);
308 if ($modul === '') {
309 return array();
310 }
311 if (isset($this->listCache[$modul])) {
312 return $this->listCache[$modul];
313 }
314
315 $this->ensureDir();
316 $dir = $this->absDir();
317 if (!is_dir($dir) || !is_readable($dir)) {
318 $this->listCache[$modul] = array();
319 return array();
320 }
321
322 $out = array();
323 foreach (glob($dir . '*') ?: array() as $path) {
324 if (!is_file($path) || !is_readable($path)) {
325 continue;
326 }
327 $name = $this->sanitizeFilename(basename($path));
328 if ($name === '' || !$this->isImageFile($name, $path)) {
329 continue;
330 }
331 if ($this->fileBelongsToModul($modul, $name)) {
332 $out[] = $name;
333 }
334 }
335
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];
340 }
341
342 public function saveList(string $modul, array $files): bool {
343 $modul = $this->sanitizeModul($modul);
344 if ($modul === '') {
345 return false;
346 }
347
348 $allowed = array_flip($this->scanList($modul));
349 foreach ($files as $file) {
350 $name = $this->sanitizeFilename((string)$file);
351 if ($name !== '' && !isset($allowed[$name])) {
352 return false;
353 }
354 }
355
356 return true;
357 }
358
359 public function addToList(string $modul, string $filename): bool {
360 $filename = $this->sanitizeFilename($filename);
361 return $filename !== ''
362 && $this->fileExists($filename)
363 && $this->fileBelongsToModul($modul, $filename);
364 }
365
366 public function removeFromList(string $modul, string $filename, bool $deletePhysical = true): bool {
367 $filename = $this->sanitizeFilename($filename);
368 if ($filename === '' || !$this->fileBelongsToModul($modul, $filename)) {
369 return false;
370 }
371
372 if ($deletePhysical) {
373 $path = $this->absPath($filename);
374 if (is_file($path)) {
375 @unlink($path);
376 }
377 }
378
379 return true;
380 }
381
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 === '') {
387 return null;
388 }
389
390 $rel = '';
391 if ($mediaId > 0) {
392 $db = dbx()->get_system_obj('dbxDB');
393 if (!is_object($db)) {
394 return null;
395 }
396 $row = $db->select1('dbxMedia', $mediaId);
397 if (!is_array($row) || (int)($row['active'] ?? 0) !== 1) {
398 return null;
399 }
400 $rel = ltrim(str_replace('\\', '/', (string)($row['file_path'] ?? '')), '/');
401 } elseif ($sourceRel !== '') {
402 $rel = ltrim(str_replace('\\', '/', trim($sourceRel)), '/');
403 }
404
405 if ($rel === '' || strpos($rel, '..') !== false) {
406 return null;
407 }
408
409 $source = rtrim(dbx()->get_file_dir(), '/\\') . '/' . $rel;
410 $source = dbx()->os_path($source);
411 if (!is_file($source) || !is_readable($source)) {
412 return null;
413 }
414
415 if (!$this->isImagePath($source)) {
416 return null;
417 }
418
419 $ext = $this->extensionFromImagePath($source);
420 $targetName = $this->filenameForRuns($modul, $run1, $run2, $ext);
421 if ($targetName === '') {
422 return null;
423 }
424
425 $this->ensureDir();
426 $dest = $this->absPath($targetName);
427 if ($dest !== '' && $source === $dest && is_file($dest)) {
428 return $targetName;
429 }
430
431 return $this->copyFromMediaRel($modul, $rel, $targetName);
432 }
433
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)) {
439 return null;
440 }
441 if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
442 return null;
443 }
444
445 $tmp = (string)($file['tmp_name'] ?? '');
446 if ($tmp === '' || !is_uploaded_file($tmp)) {
447 return null;
448 }
449
450 if (!$this->isImagePath($tmp)) {
451 return null;
452 }
453
454 $ext = $this->extensionFromImagePath($tmp, $this->extensionFromImagePath((string) ($file['name'] ?? '')));
455 $targetName = $this->filenameForRuns($modul, $run1, $run2, $ext);
456 if ($targetName === '') {
457 return null;
458 }
459
460 $this->ensureDir();
461 $dest = $this->absPath($targetName);
462 if ($dest === '') {
463 return null;
464 }
465
466 if (is_file($dest)) {
467 @unlink($dest);
468 }
469
470 if (!@move_uploaded_file($tmp, $dest)) {
471 return null;
472 }
473
474 return $targetName;
475 }
476
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 === '') {
482 return $modul;
483 }
484
485 $stem = $modul . self::FILENAME_PARAM_SEP . $run1;
486 if ($run2 !== '') {
487 $stem .= self::FILENAME_PARAM_SEP . $run2;
488 }
489
490 return $stem;
491 }
492
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);
498
499 if ($modul === '' || $run1 === '') {
500 return '';
501 }
502
503 return $this->stemForRuns($modul, $run1, $run2) . '.' . $ext;
504 }
505
506 public function getUrl(string $filename): string {
507 $filename = $this->sanitizeFilename($filename);
508 if ($filename === '') {
509 return '';
510 }
511 return $this->urlBase() . rawurlencode($filename);
512 }
513
514 public function fileExists(string $filename): bool {
515 $path = $this->absPath($filename);
516 return $path !== '' && is_file($path) && is_readable($path);
517 }
518
519 public function absPath(string $filename): string {
520 $filename = $this->sanitizeFilename($filename);
521 if ($filename === '') {
522 return '';
523 }
524 return $this->absDir() . $filename;
525 }
526
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) {
531 return null;
532 }
533
534 $source = rtrim(dbx()->get_file_dir(), '/\\') . '/' . $sourceRel;
535 $source = dbx()->os_path($source);
536 if (!is_file($source) || !is_readable($source)) {
537 return null;
538 }
539
540 if (!$this->isImagePath($source)) {
541 return null;
542 }
543
544 if ($targetName === '') {
545 $targetName = $this->suggestFilename($modul, basename($source));
546 } else {
547 $targetName = $this->sanitizeFilename($targetName);
548 if ($targetName !== '' && pathinfo($targetName, PATHINFO_EXTENSION) === '') {
549 $targetName .= '.' . $this->extensionFromImagePath($source);
550 }
551 }
552
553 if ($targetName === '') {
554 return null;
555 }
556
557 $this->ensureDir();
558 $dest = $this->absPath($targetName);
559 if ($dest === '') {
560 return null;
561 }
562
563 if (is_file($dest) && $source !== $dest) {
564 @unlink($dest);
565 }
566
567 if (!@copy($source, $dest)) {
568 return null;
569 }
570
571 return $targetName;
572 }
573
574 public function copyFromMediaId(string $modul, int $mediaId): ?string {
575 return $this->importForModul($modul, $mediaId, '');
576 }
577
578 public function suggestFilename(string $modul, string $sourceName): string {
579 $modul = $this->sanitizeModul($modul);
580 $base = pathinfo(basename($sourceName), PATHINFO_FILENAME);
581 $base = preg_replace('/[^A-Za-z0-9_-]+/', '-', (string)$base);
582 $base = trim((string)$base, '-_');
583 $ext = $this->normalizeImageExtension(pathinfo($sourceName, PATHINFO_EXTENSION));
584
585 if ($base === '') {
586 $base = 'image';
587 }
588
589 $prefix = $modul . self::FILENAME_PARAM_SEP;
590 if (strpos($base, $prefix) !== 0 && $base !== $modul) {
591 $base = $prefix . $base;
592 }
593
594 $name = $base . '.' . $ext;
595 if (!$this->fileExists($name)) {
596 return $name;
597 }
598
599 $i = 2;
600 while ($i < 1000) {
601 $candidate = $base . self::FILENAME_PARAM_SEP . $i . '.' . $ext;
602 if (!$this->fileExists($candidate)) {
603 return $candidate;
604 }
605 $i++;
606 }
607
608 return $base . self::FILENAME_PARAM_SEP . substr(md5((string)microtime(true)), 0, 6) . '.' . $ext;
609 }
610
611 public function resolveFromFilename(string $filename): array {
612 $filename = $this->sanitizeFilename($filename);
613 if ($filename === '' || !$this->isImageFile($filename) || !$this->fileExists($filename)) {
614 return array();
615 }
616
617 $modul = $this->resolveModulFromFilename($filename);
618 if ($modul === '') {
619 return array();
620 }
621
622 $item = $this->catalogItem($modul, $filename);
623 return is_array($item) ? $item : array();
624 }
625
626 public function resolveModulFromFilename(string $filename): string {
627 $filename = $this->sanitizeFilename($filename);
628 if ($filename === '') {
629 return '';
630 }
631
632 $base = pathinfo($filename, PATHINFO_FILENAME);
633 if ($base === '') {
634 return '';
635 }
636
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)) {
641 return $modul;
642 }
643 return '';
644 }
645
646 if ($this->isKnownModul($base)) {
647 return $this->sanitizeModul($base);
648 }
649
650 return '';
651 }
652
653 public function catalogForModul(string $modul): array {
654 $modul = $this->sanitizeModul($modul);
655 if ($modul === '') {
656 return array();
657 }
658
659 $scan = $this->scanRuns($modul);
660 $items = array();
661
662 foreach ($this->getList($modul) as $file) {
663 $items[] = $this->catalogItem($modul, $file, $scan);
664 }
665
666 return $items;
667 }
668
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 === '') {
673 return array();
674 }
675
676 if (!is_array($scan)) {
677 $scan = $this->scanRuns($modul);
678 }
679
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]';
684
685 return array(
686 'id' => pathinfo($filename, PATHINFO_FILENAME),
687 'file' => $filename,
688 'label' => $label,
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'] ?? ''),
696 );
697 }
698
699 public function imageCount(string $modul): int {
700 return count($this->getList($modul));
701 }
702
703 public function primaryGraphic(string $modul, string $run1 = '', string $run2 = ''): array {
704 $modul = $this->sanitizeModul($modul);
705 $list = $this->getList($modul);
706 if (!$list) {
707 return array('url' => '', 'alt' => $modul, 'badge' => '');
708 }
709
710 $scan = $this->scanRuns($modul);
711 $candidates = array();
712 if ($run1 !== '') {
713 if ($run2 !== '') {
714 $candidates[] = $this->stemForRuns($modul, $run1, $run2);
715 }
716 $candidates[] = $this->stemForRuns($modul, $run1, '');
717 }
718 $candidates[] = $modul;
719
720 foreach ($candidates as $stem) {
721 foreach ($list as $file) {
722 $base = pathinfo($file, PATHINFO_FILENAME);
723 if ($base === $stem) {
724 return array(
725 'url' => $this->getUrl($file),
726 'alt' => $modul,
727 'badge' => $file,
728 );
729 }
730 }
731 }
732
733 $first = $list[0];
734 return array(
735 'url' => $this->getUrl($first),
736 'alt' => $modul,
737 'badge' => $first,
738 );
739 }
740
741 public function imageItems(string $modul): array {
742 $modul = $this->sanitizeModul($modul);
743 if ($modul === '') {
744 return array();
745 }
746 if (isset($this->imageItemsCache[$modul])) {
747 return $this->imageItemsCache[$modul];
748 }
749
750 $scan = $this->scanRuns($modul);
751 $items = array();
752 foreach ($this->getList($modul) as $file) {
753 $item = $this->catalogItem($modul, $file, $scan);
754 if ($item) {
755 $items[] = $item;
756 }
757 }
758 $this->imageItemsCache[$modul] = $items;
759 return $this->imageItemsCache[$modul];
760 }
761
762 public function mediaBrowserRows(string $modulFilter = ''): array {
763 $modulFilter = $this->sanitizeModul($modulFilter);
764 $this->ensureDir();
765 $dir = $this->absDir();
766 if (!is_dir($dir) || !is_readable($dir)) {
767 return array();
768 }
769
770 $rows = array();
771 foreach (glob($dir . '*') ?: array() as $path) {
772 if (!is_file($path) || !is_readable($path)) {
773 continue;
774 }
775 $name = $this->sanitizeFilename(basename($path));
776 if ($name === '' || !$this->isImageFile($name, $path)) {
777 continue;
778 }
779 if ($modulFilter !== '' && !$this->fileBelongsToModul($modulFilter, $name)) {
780 continue;
781 }
782
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));
787 }
788
789 $title = pathinfo($name, PATHINFO_FILENAME);
790 $rows[] = array(
791 'id' => abs(crc32($rel)),
792 'file_name' => $name,
793 'file_path' => $rel,
794 'title' => $title,
795 'alt' => $title,
796 'url' => $this->getUrl($name),
797 'thumb_url' => $this->getUrl($name),
798 'mime' => $mime,
799 'media_type' => 'image',
800 'media_folder' => 'mod',
801 'slot' => 'gallery',
802 'size' => (int)@filesize($path),
803 );
804 }
805
806 usort($rows, function ($a, $b) {
807 return strnatcasecmp((string)($a['file_name'] ?? ''), (string)($b['file_name'] ?? ''));
808 });
809
810 return $rows;
811 }
812
813 private function renameForModul(string $modul, string $filename): ?string {
814 $filename = $this->sanitizeFilename($filename);
815 if ($filename === '' || !$this->fileExists($filename)) {
816 return null;
817 }
818
819 $targetName = $this->suggestFilename($modul, $filename);
820 if ($targetName === $filename) {
821 return $filename;
822 }
823
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);
828 }
829
830 return $targetName;
831 }
832
833 private function fileBelongsToModul(string $modul, string $filename): bool {
834 $modul = $this->sanitizeModul($modul);
835 if ($modul === '') {
836 return false;
837 }
838
839 return $this->resolveModulFromFilename($filename) === $modul;
840 }
841
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)) {
847 return;
848 }
849
850 foreach (glob($dir . $modul . '.*') ?: array() as $path) {
851 $file = basename((string)$path);
852 if ($file === $keepFile) {
853 continue;
854 }
855 if (is_file($path)) {
856 @unlink($path);
857 }
858 }
859 }
860
861 private function isImageFile(string $filename, string $absPath = ''): bool {
862 $filename = $this->sanitizeFilename($filename);
863 if ($filename === '') {
864 return false;
865 }
866
867 if ($absPath === '') {
868 $absPath = $this->absPath($filename);
869 }
870
871 $ext = strtolower(preg_replace('/[^a-z0-9]+/', '', pathinfo($filename, PATHINFO_EXTENSION)));
872 if ($ext === '' || strlen($ext) > 8 || in_array($ext, $this->blockedImageExtensions(), true)) {
873 return false;
874 }
875
876 return in_array($ext, $this->allowedImageExtensions(), true);
877 }
878
879 private function scanRuns(string $modul): array {
880 static $cache = array();
881 if (isset($cache[$modul])) {
882 return $cache[$modul];
883 }
884
885 $cache[$modul] = $this->scanRunsInline($modul);
886 return $cache[$modul];
887 }
888
889 private function scanRunsInline(string $modul): array {
890 $run1 = array();
891 $usesRun2 = false;
892 $base = dbx()->os_path(dbx()->get_base_dir() . 'dbx/modules/' . $modul . DIRECTORY_SEPARATOR);
893 $files = array();
894 $main = $base . $modul . '.class.php';
895 if (is_file($main)) {
896 $files[] = $main;
897 }
898 $inc = $base . 'include' . DIRECTORY_SEPARATOR;
899 if (is_dir($inc)) {
900 foreach (glob($inc . '*.class.php') ?: array() as $path) {
901 if (is_file($path)) {
902 $files[] = $path;
903 }
904 }
905 }
906
907 foreach ($files as $file) {
908 $src = @file_get_contents($file);
909 if (!is_string($src) || $src === '') {
910 continue;
911 }
912 if (preg_match("/get_modul_var\s*\‍(\s*['\"]dbx_run2['\"]/", $src)) {
913 $usesRun2 = true;
914 }
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') {
919 $run1[$case] = true;
920 }
921 }
922 }
923 }
924
925 $run1List = array_keys($run1);
926 usort($run1List, function ($a, $b) {
927 return strlen($b) <=> strlen($a);
928 });
929
930 return array(
931 'run1' => array_values(array_keys($run1)),
932 'uses_run2' => $usesRun2,
933 'run1_sorted' => $run1List,
934 );
935 }
936
937 private function parseImageName(string $modul, string $filename, array $scan): array {
938 $base = pathinfo($filename, PATHINFO_FILENAME);
939 if ($base === $modul) {
940 return array(
941 'run1' => '',
942 'run2' => '',
943 'default_params' => '',
944 'label' => $modul,
945 );
946 }
947
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);
956 }
957
958 return array(
959 'run1' => '',
960 'run2' => '',
961 'default_params' => '',
962 'label' => $base,
963 );
964 }
965
966 private function buildParsedRuns(string $run1, string $run2, string $fallbackLabel): array {
967 $params = $this->buildParams($run1, $run2);
968 $label = $this->runLabel($run1);
969 if ($run2 !== '') {
970 $label .= ' / ' . $this->runLabel($run2);
971 }
972 if ($label === '') {
973 $label = $fallbackLabel;
974 }
975
976 return array(
977 'run1' => $run1,
978 'run2' => $run2,
979 'default_params' => $params,
980 'label' => $label,
981 );
982 }
983
984 private function knownModulNames(): array {
985 static $names = null;
986 if (is_array($names)) {
987 return $names;
988 }
989
990 $names = array();
991 $dir = dbx_os_path_file(dbx_get_base_dir() . 'dbx/modules/');
992 if (!is_dir($dir)) {
993 return $names;
994 }
995
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)) {
999 continue;
1000 }
1001 $class = dbx_os_path_file($path . DIRECTORY_SEPARATOR . $name . '.class.php');
1002 if (!is_file($class)) {
1003 continue;
1004 }
1005 $names[] = $name;
1006 }
1007
1008 usort($names, function ($a, $b) {
1009 return strlen($b) <=> strlen($a);
1010 });
1011
1012 return $names;
1013 }
1014
1015 private function isKnownModul(string $modul): bool {
1016 $modul = $this->sanitizeModul($modul);
1017 if ($modul === '') {
1018 return false;
1019 }
1020
1021 foreach ($this->knownModulNames() as $name) {
1022 if ($name === $modul) {
1023 return true;
1024 }
1025 }
1026
1027 return is_file(dbx_os_path_file(dbx_get_base_dir() . 'dbx/modules/' . $modul . '/' . $modul . '.class.php'));
1028 }
1029
1030 private function buildParams(string $run1, string $run2 = ''): string {
1031 $params = array();
1032 if ($run1 !== '') {
1033 $params[] = 'dbx_run1=' . rawurlencode($run1);
1034 }
1035 if ($run2 !== '') {
1036 $params[] = 'dbx_run2=' . rawurlencode($run2);
1037 }
1038 return implode('&', $params);
1039 }
1040
1041 private function runLabel(string $action): string {
1042 $action = trim($action);
1043 if ($action === '') {
1044 return '';
1045 }
1046 return ucfirst(str_replace('_', ' ', $action));
1047 }
1048
1049 private function sanitizeModul(string $modul): string {
1050 return preg_replace('/[^A-Za-z0-9_]+/', '', trim($modul));
1051 }
1052
1053 private function sanitizeRunPart(string $part): string {
1054 return preg_replace('/[^A-Za-z0-9_-]+/', '', trim($part));
1055 }
1056
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, '-.');
1061 }
1062}
Modul-Bilder unter files/mod — Dateiname: {modul}__{run1}[__{run2}].ext.
importSymbolForModul(string $modul, int $mediaId=0, string $sourceRel='')
suggestFilename(string $modul, string $sourceName)
addToList(string $modul, string $filename)
stemForRuns(string $modul, string $run1, string $run2='')
importForModul(string $modul, int $mediaId=0, string $sourceRel='', string $run1='', string $run2='')
mediaBrowserRows(string $modulFilter='')
primaryGraphic(string $modul, string $run1='', string $run2='')
moduleSymbolDir(string $modul, bool $create=false)
saveFromUpload(string $modul, string $run1, string $run2, array $file)
copyFromMediaRel(string $modul, string $sourceRel, string $targetName='')
filenameForRuns(string $modul, string $run1, string $run2='', string $ext='jpg')
saveList(string $modul, array $files)
copyFromMediaId(string $modul, int $mediaId)
removeFromList(string $modul, string $filename, bool $deletePhysical=true)
catalogItem(string $modul, string $filename, array $scan=null)
dbx_os_path_file($path_file)
Definition index.php:164
exit
Definition index.php:532
if( $syncRequest)
Definition index.php:520
dbx_get_base_dir($cut_Data=0)
Definition index.php:157
DBX schema administration.