17 private $dd_media =
'dbxMedia';
18 private $dd_media_usage =
'dbxMediaUsage';
20 private function cms_json_response(array $data):
void {
21 dbx()->json_response($data,
true);
24 private function lng_provision_open_flag(
$db,
string $entity,
int $id):
int {
25 if (!dbxContentLngSync::isMasterLng() || !is_object(
$db)) {
29 if (count(dbxContentLngSync::slaveLngs()) <= 0) {
37 return dbxContentLngSync::hasMissingSlaveLng(
$db, $entity, $id) ? 1 : 0;
40 private function apply_cms_lng_context():
void {
41 $lng = strtolower(trim((
string)
dbx()->get_request_var(
'dbx_lng',
'')));
46 $allowed = dbxContentLngSync::accessibleLngs();
47 if (!in_array($lng, $allowed,
true)) {
51 dbx()->set_system_var(
'dbx_lng', $lng);
52 dbx()->set_remember_var(
'dbx_lng', $lng,
'dbx');
55 private function ini_size_bytes($value) {
56 $value = trim((
string)$value);
57 if ($value ===
'')
return 0;
58 $unit = strtolower(substr($value, -1));
60 if ($unit ===
'g') $num *= 1024 * 1024 * 1024;
61 elseif ($unit ===
'm') $num *= 1024 * 1024;
62 elseif ($unit ===
'k') $num *= 1024;
66 private function upload_max_bytes() {
67 $upload = $this->ini_size_bytes(ini_get(
'upload_max_filesize'));
68 $post = $this->ini_size_bytes(ini_get(
'post_max_size'));
69 if ($upload <= 0)
return $post;
70 if ($post <= 0)
return $upload;
71 return min($upload, $post);
74 private function base_url($action, $params = array()) {
75 $url = $this->app_url() .
'?dbx_modul=dbxContent_admin&dbx_run1=' . rawurlencode((
string)$action);
76 foreach ($params as $key => $value) {
77 $url .=
'&' . rawurlencode((
string)$key) .
'=' . rawurlencode((
string)$value);
82 private function app_url():
string {
83 $script = str_replace(
'\\',
'/', (
string)(
$_SERVER[
'SCRIPT_NAME'] ??
''));
88 $dir = str_replace(
'\\',
'/', dirname($script));
93 return rtrim(
$dir,
'/') .
'/';
96 private function load_page_cache_classes():
void {
97 dbx()->load_content_cache_classes();
100 private function sync_permalink_index(
$db,
int $cid,
string $lng =
''):
void {
102 if ($cid <= 0 || !is_object(
$db)) {
106 $this->load_page_cache_classes();
107 if (!dbxContentPageCache::isConfigEnabled()) {
111 $lng = strtolower(trim($lng));
113 if ($lng !==
'' && $lng !== dbxContentLng::current()) {
114 $prevLng =
dbx()->get_system_var(
'dbx_lng',
'de');
115 dbx()->set_system_var(
'dbx_lng', $lng);
118 $rec =
$db->select1(dbxContentLng::ddContent(), $cid,
'permalink,activ,folder,title,seo_title,description,keywords,meta_robots,seo_image_id,update_date,lng_uid', 0);
119 if (!is_array($rec)) {
120 if ($prevLng !==
null) {
121 dbx()->set_system_var(
'dbx_lng', $prevLng);
126 if ((
int)($rec[
'activ'] ?? 0) !== 1) {
127 dbxContentPermalinkIndex::removeByCid($cid, dbxContentLng::current());
128 if (class_exists(
'dbx\\dbxContent\\dbxContentSitemap')) {
131 if ($prevLng !==
null) {
132 dbx()->set_system_var(
'dbx_lng', $prevLng);
138 $rights = $renderer->getPublicFolderRights((
int)($rec[
'folder'] ?? 0));
139 $permalink = (string)($rec[
'permalink'] ??
'');
141 dbxContentPageCache::writePageMeta($cid, array(
143 'permalink' => $permalink,
145 'activ' => (
int)($rec[
'activ'] ?? 1),
146 'saved_at' => date(
'c'),
147 'seo' => dbxContentRenderer::seoMetaFromRecord($rec),
150 if (trim($permalink) !==
'') {
151 dbxContentPermalinkIndex::upsertPage(
155 (
int)($rec[
'activ'] ?? 1),
156 dbxContentLng::current()
160 dbxContentHome::refreshHomeCache(
$db, $cid, dbxContentLng::current());
162 if (class_exists(
'dbx\\dbxContent\\dbxContentSitemap')) {
166 if ($prevLng !==
null) {
167 dbx()->set_system_var(
'dbx_lng', $prevLng);
171 private function flush_lng_sync_cache(
$db, array $syncResult):
void {
172 if (!is_object(
$db) || !is_array($syncResult)) {
176 $updated = is_array($syncResult[
'updated'] ??
null) ? $syncResult[
'updated'] : array();
177 if (!count($updated)) {
181 $this->load_page_cache_classes();
182 if (!dbxContentPageCache::isConfigEnabled()) {
186 $menuFlushed =
false;
187 foreach ($updated as $item) {
188 if (!is_array($item)) {
192 $id = (int) ($item[
'id'] ?? 0);
193 $lng = trim((
string) ($item[
'lng'] ??
''));
194 $entity = (($item[
'entity'] ??
'page') ===
'folder') ?
'folder' :
'page';
199 if ($entity ===
'page') {
200 dbxContentPageCache::invalidateContent($id);
202 $this->sync_permalink_index(
$db, $id, $lng);
204 } elseif (!$menuFlushed) {
205 dbxContentPageCache::invalidateAllMenus();
211 dbxContentPageCache::invalidateAllMenus();
215 private function flush_saved_page_cache(
$db,
int $cid):
void {
221 $this->load_page_cache_classes();
222 if (!dbxContentPageCache::isConfigEnabled()) {
225 dbxContentPageCache::invalidateContent($cid);
226 dbxContentPageCache::invalidateAllMenus();
227 $this->sync_permalink_index(
$db, $cid);
230 private function flush_deleted_page_cache(
int $cid,
string $lng =
''):
void {
236 $this->load_page_cache_classes();
237 if (!dbxContentPageCache::isConfigEnabled()) {
241 $lng = strtolower(trim($lng));
243 $lng = dbxContentLng::current();
246 dbxContentPageCache::invalidateContent($cid);
247 dbxContentPageCache::invalidateAllMenus();
248 dbxContentPermalinkIndex::removeByCid($cid, $lng);
251 private function copy_page_media_usage(
$db,
int $sourceCid,
int $targetCid,
int $targetFolderId,
bool $replace =
true):
int {
252 $sourceCid = (int) $sourceCid;
253 $targetCid = (int) $targetCid;
254 $targetFolderId = (int) $targetFolderId;
255 if ($sourceCid <= 0 || $targetCid <= 0 || !is_object(
$db)) {
259 $copySlots = array(
'hero',
'gallery',
'header',
'teaser',
'footer');
261 foreach ($copySlots as $slot) {
263 $this->dd_media_usage,
264 array(
'active' => 0),
265 "content_id = " . $targetCid .
" AND slot = '" . str_replace(
"'",
"''", $slot) .
"' AND active = 1",
275 $this->dd_media_usage,
276 "content_id = " . $sourceCid .
" AND active = 1 AND slot <> 'inline'",
285 if (!is_array($rows)) {
290 foreach ($rows as $usage) {
291 if (!is_array($usage)) {
294 $mediaId = (int) ($usage[
'media_id'] ?? 0);
298 $media =
$db->select1($this->dd_media, $mediaId,
'active', 0);
299 if (!is_array($media) || (
int) ($media[
'active'] ?? 0) !== 1) {
303 $slot = $this->valid_media_slot($usage[
'slot'] ??
'gallery');
304 if ($slot ===
'inline') {
308 $usageId = $this->create_media_usage(
314 (
string) ($usage[
'template'] ??
''),
315 (
string) ($usage[
'caption'] ??
''),
316 (
string) ($usage[
'settings'] ??
'')
326 private function sync_lng_page_media_from_master(
$db,
int $masterCid,
int $slaveCid,
string $lng):
int {
327 $masterCid = (int) $masterCid;
328 $slaveCid = (int) $slaveCid;
329 $lng = strtolower(trim($lng));
330 if ($masterCid <= 0 || $slaveCid <= 0 || $lng ===
'' || !is_object(
$db)) {
334 $slaveRow =
$db->select1(dbxContentLng::ddContent($lng), $slaveCid,
'content,folder', 0);
335 if (!is_array($slaveRow)) {
339 $slaveFolder = (int) ($slaveRow[
'folder'] ?? 0);
340 $copied = $this->copy_page_media_usage(
$db, $masterCid, $slaveCid, $slaveFolder,
true);
341 $this->sync_inline_media_usage(
$db, $slaveCid, (
string) ($slaveRow[
'content'] ??
''), $slaveFolder);
345 private function apply_lng_sync_media(
$db,
int $masterCid, array $syncResult):
int {
346 $master = dbxContentLngSync::masterLng();
348 foreach (($syncResult[
'updated'] ?? array()) as $item) {
349 if (!is_array($item) || (($item[
'entity'] ??
'page') !==
'page')) {
352 $lng = strtolower(trim((
string) ($item[
'lng'] ??
'')));
353 $slaveId = (int) ($item[
'id'] ?? 0);
354 if ($slaveId <= 0 || $lng ===
'' || $lng === $master) {
357 $copied += $this->sync_lng_page_media_from_master(
$db, $masterCid, $slaveId, $lng);
362 private function apply_lng_provision_media(
$db,
string $type,
int $masterId, array
$result):
int {
363 if ($type !==
'page' || !is_object(
$db)) {
368 $items = array_merge(
369 is_array(
$result[
'created'] ??
null) ?
$result[
'created'] : array(),
370 is_array(
$result[
'updated'] ??
null) ?
$result[
'updated'] : array()
372 foreach ($items as $item) {
373 if (!is_array($item)) {
376 $lng = strtolower(trim((
string) ($item[
'lng'] ??
'')));
377 $slaveId = (int) ($item[
'id'] ?? 0);
378 if ($slaveId <= 0 || $lng ===
'') {
381 $copied += $this->sync_lng_page_media_from_master(
$db, $masterId, $slaveId, $lng);
386 private function flush_deleted_folder_cache(
$db,
int $folderId,
string $lng =
''):
void {
387 $folderId = (int) $folderId;
388 if ($folderId < 0 || !is_object(
$db)) {
392 $this->load_page_cache_classes();
393 if (!dbxContentPageCache::isConfigEnabled()) {
397 $lng = strtolower(trim($lng));
399 if ($lng !==
'' && $lng !== dbxContentLng::current()) {
400 $prevLng =
dbx()->get_system_var(
'dbx_lng',
'de');
401 dbx()->set_system_var(
'dbx_lng', $lng);
404 dbxContentPageCache::invalidateFolderTree(
$db, $folderId);
405 dbxContentPageCache::invalidateAllMenus();
407 if ($prevLng !==
null) {
408 dbx()->set_system_var(
'dbx_lng', $prevLng);
412 private function flush_folder_cache(
$db,
int $folderId):
void {
413 $folderId = (int) $folderId;
414 if ($folderId < 0 || !is_object(
$db)) {
418 $this->load_page_cache_classes();
419 if (!dbxContentPageCache::isConfigEnabled()) {
422 dbxContentPageCache::invalidateFolderTree(
$db, $folderId);
423 dbxContentPageCache::invalidateAllMenus();
426 private function flush_menu_cache():
void {
427 $this->load_page_cache_classes();
428 if (!dbxContentPageCache::isConfigEnabled()) {
431 dbxContentPageCache::invalidateAllMenus();
434 private function flush_media_cache(
$db,
int $content_id = 0,
int $folder_id = 0):
void {
435 if ($content_id <= 0 && $folder_id <= 0) {
439 $this->load_page_cache_classes();
440 if (!dbxContentPageCache::isConfigEnabled()) {
443 if ($content_id > 0) {
444 dbxContentPageCache::invalidateContent($content_id);
446 if ($folder_id > 0 && is_object(
$db)) {
447 dbxContentPageCache::invalidateFolderTree(
$db, $folder_id);
449 dbxContentPageCache::invalidateAllMenus();
452 private function flush_media_by_media_id(
$db,
int $media_id):
void {
453 $media_id = (int) $media_id;
454 if ($media_id <= 0 || !is_object(
$db)) {
458 $usage_rows =
$db->select($this->dd_media_usage,
'media_id = ' . $media_id .
' AND active = 1',
'*',
'id',
'ASC',
'', 0, 0, 0);
459 if (!is_array($usage_rows)) {
463 foreach ($usage_rows as $usage) {
464 if (!is_array($usage)) {
467 $this->flush_media_cache(
469 (
int)($usage[
'content_id'] ?? 0),
470 (
int)($usage[
'folder_id'] ?? 0)
475 private function request_json() {
478 $raw = file_get_contents(
'php://input');
479 if (is_string($raw) && trim($raw) !==
'') {
480 $decoded = json_decode($raw,
true);
481 if (is_array($decoded)) {
486 if (!count($data) && !empty($_POST) && is_array($_POST)) {
493 private function normalize_content_media_urls($html) {
494 $html = (string)$html;
495 $html = preg_replace_callback(
'/<figure\b([^>]*dbx-cms-inline-video-block[^>]*)>[\s\S]*?<\/figure>/i',
function($m) {
497 if (!preg_match(
'/data-cms-media-id=["\']?([0-9]+)/i', $attrs, $id_match))
return $m[0];
498 $id = (int)$id_match[1];
499 $placeholder = $this->inline_media_placeholder_html($id);
500 if ($placeholder ===
'')
return $m[0];
501 $attrs = preg_replace(
'/\scontenteditable=(["\']).*?\1/i',
'', $attrs);
502 if (stripos($attrs,
'data-cms-media-slot=') ===
false) $attrs .=
' data-cms-media-slot="inline"';
503 return '<figure' . $attrs .
'>' . $placeholder .
'</figure>';
505 $html = preg_replace(
'/<(video|iframe|source)\b[^>]*data-cms-media-id=["\']?([0-9]+)[^>]*>(?:\s*<\/\1>)?/i',
'', $html);
506 $html = preg_replace_callback(
'/<(img|video|iframe|source)\b([^>]*?)>/i',
function($m) {
508 $tag_name = strtolower((
string)($m[1] ??
''));
510 if (preg_match(
'/dbx_mid=([0-9]+)/i', $tag, $id_match)) {
511 $id = (int)$id_match[1];
512 $tag = preg_replace(
'/\sdata-cms-media-id\s*=\s*(?:"[^"]*"|\'[^\']*\'|[^\s>]+)/i',
'', $tag);
513 $tag = preg_match(
'/\/\s*>$/', $tag)
514 ? preg_replace(
'/\s*\/\s*>$/',
' data-cms-media-id="' . $id .
'" />', $tag)
515 : preg_replace(
'/\s*>$/',
' data-cms-media-id="' . $id .
'">', $tag);
516 } elseif (preg_match(
'/data-cms-media-id=["\']?([0-9]+)/i', $tag, $id_match)) {
517 $id = (int)$id_match[1];
519 if ($tag_name ===
'img' && $id > 0 && !$this->inline_media_available($id)) {
520 return $this->inline_media_missing_html($id);
522 if (stripos($tag,
'data-cms-media-slot=') ===
false && $id > 0) {
523 $tag = preg_match(
'/\/\s*>$/', $tag)
524 ? preg_replace(
'/\s*\/\s*>$/',
' data-cms-media-slot="inline" />', $tag)
525 : preg_replace(
'/\s*>$/',
' data-cms-media-slot="inline">', $tag);
529 $html = preg_replace_callback(
'/<(p|figure|div)\b([^>]*\bclass\s*=\s*(?:"[^"]*\bdbx-cms-inline-media\b[^"]*"|\'[^\']*\bdbx-cms-inline-media\b[^\']*\')[^>]*)>([\s\S]*?)<\/\1>/i',
function($m) {
530 $tag = (string)($m[1] ??
'p');
531 $attrs = (string)($m[2] ??
'');
532 $inner = (string)($m[3] ??
'');
533 if (!preg_match(
'/<img\b[^>]*\bsrc\s*=\s*(?:"[^"]*dbx_mid=([0-9]+)[^"]*"|\'[^\']*dbx_mid=([0-9]+)[^\']*\')/i', $inner, $id_match)) {
536 $id = (int)(($id_match[1] ?? 0) ?: ($id_match[2] ?? 0));
537 if ($id <= 0)
return $m[0];
538 $attrs = preg_replace(
'/\sdata-cms-media-id\s*=\s*(?:"[^"]*"|\'[^\']*\'|[^\s>]+)/i',
'', $attrs);
539 if (stripos($attrs,
'data-cms-media-slot=') ===
false) $attrs .=
' data-cms-media-slot="inline"';
540 $inner = preg_replace_callback(
'/<img\b([^>]*)>/i',
function($img) use ($id) {
541 $img_attrs = (string)($img[1] ??
'');
542 if (!preg_match(
'/\bsrc\s*=\s*(?:"[^"]*dbx_mid=' . $id .
'\b[^"]*"|\'[^\']*dbx_mid=' . $id .
'\b[^\']*\')/i', $img_attrs)) {
545 $img_attrs = preg_replace(
'/\sdata-cms-media-id\s*=\s*(?:"[^"]*"|\'[^\']*\'|[^\s>]+)/i',
'', $img_attrs);
546 if (stripos($img_attrs,
'data-cms-media-slot=') ===
false) $img_attrs .=
' data-cms-media-slot="inline"';
547 return '<img' . $img_attrs .
' data-cms-media-id="' . $id .
'">';
549 return '<' . $tag . $attrs .
'>' . $inner .
'</' . $tag .
'>';
551 $html = $this->strip_empty_inline_media_wrappers($html);
554 '?dbx_modul=dbxContent&dbx_run1=media&dbx_mid=',
555 '?dbx_modul=dbxContent&dbx_run1=media&dbx_mid=',
556 'index.phpindex.php?',
559 'index.php?dbx_modul=dbxContent&dbx_run1=media&dbx_mid=',
560 'index.php?dbx_modul=dbxContent&dbx_run1=media&dbx_mid=',
568 private function sanitize_content_html($html) {
569 $html = (string)$html;
574 $html = preg_replace(
'#<(script|style|object|embed|base|meta|link)\b[^>]*>.*?</\1>#is',
'', $html);
575 $html = preg_replace(
'#<(script|style|object|embed|base|meta|link)\b[^>]*\/?>#is',
'', $html);
577 if (!class_exists(
'\DOMDocument')) {
578 $html = preg_replace(
'/\s+on[a-z]+\s*=\s*(["\']).*?\1/is',
'', $html);
579 $html = preg_replace(
'/\s+style\s*=\s*(["\']).*?\1/is',
'', $html);
580 $html = preg_replace(
'/\s+(href|src)\s*=\s*(["\'])\s*javascript:[^"\']*\2/is',
'', $html);
584 $previous = libxml_use_internal_errors(
true);
585 $doc = new \DOMDocument(
'1.0',
'UTF-8');
586 $wrapped =
'<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><div id="dbx_cms_sanitize_root">' . $html .
'</div></body></html>';
587 $doc->loadHTML($wrapped, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
588 libxml_clear_errors();
589 libxml_use_internal_errors($previous);
591 $root = $doc->getElementById(
'dbx_cms_sanitize_root');
596 $walker =
function($node) use (&$walker) {
597 if ($node instanceof \DOMElement) {
599 foreach ($node->attributes as $attr) {
600 $name = strtolower($attr->name);
601 $value = trim((
string)$attr->value);
602 if (strpos($name,
'on') === 0 || $name ===
'style') {
603 $remove[] = $attr->name;
606 if (($name ===
'href' || $name ===
'src' || $name ===
'xlink:href') && preg_match(
'/^\s*javascript:/i', $value)) {
607 $remove[] = $attr->name;
610 if ($name ===
'data-dbx') {
611 if (stripos($value,
'lib=openWin') ===
false || stripos($value,
'javascript:') !==
false) {
612 $remove[] = $attr->name;
616 foreach ($remove as $name) {
617 $node->removeAttribute($name);
621 foreach (iterator_to_array($node->childNodes) as $child) {
628 foreach (
$root->childNodes as $child) {
629 $out .= $doc->saveHTML($child);
634 private function normalize_and_sanitize_content($html) {
635 return $this->sanitize_content_html($this->normalize_content_media_urls($html));
638 private function inline_media_available($id) {
640 if ($id <= 0)
return false;
641 $db =
dbx()->get_system_obj(
'dbxDB');
642 $row =
$db->select1($this->dd_media, $id);
643 if (!is_array($row) || (
int)($row[
'active'] ?? 0) !== 1)
return false;
644 return $this->media_file_exists($row);
647 private function inline_media_missing_html($id) {
649 if ($id <= 0)
return '';
650 $label =
'Mediendatei #' . $id .
' nicht verfuegbar';
651 return '<p class="dbx-cms-inline-media dbx-cms-inline-media-missing-wrap" data-cms-media-id="' . $id .
'" data-cms-media-slot="inline" contenteditable="false" tabindex="0" title="Fehlende Mediendatei auswaehlen, Entf zum Loeschen"><span class="dbx-cms-inline-media-missing" aria-hidden="true">' . htmlspecialchars($label, ENT_QUOTES,
'UTF-8') .
'</span></p>';
654 private function inline_media_placeholder_html($id) {
656 if ($id <= 0)
return '';
657 $db =
dbx()->get_system_obj(
'dbxDB');
658 $row =
$db->select1($this->dd_media, $id);
659 if (!is_array($row) || (
int)($row[
'active'] ?? 0) !== 1)
return '';
660 $row = $this->normalize_media_row($row);
661 $title = htmlspecialchars((
string)($row[
'title'] ?? $row[
'alt'] ?? $row[
'file_name'] ??
'Video'), ENT_QUOTES,
'UTF-8');
662 $attr =
' data-cms-media-id="' . $id .
'" data-cms-media-slot="inline"';
663 if (!in_array($this->media_type($row), array(
'video',
'external_video'),
true))
return '';
664 $thumb = htmlspecialchars((
string)($row[
'thumb_url'] ??
''), ENT_QUOTES,
'UTF-8');
665 if ($thumb !==
'')
return '<img class="dbx-cms-inline-video-thumb" src="' . $thumb .
'" alt="' . $title .
'" title="' . $title .
'"' . $attr .
'><span class="dbx-cms-inline-video-play" aria-hidden="true"><i class="bi bi-play-fill"></i></span>';
666 return '<span class="dbx-cms-inline-video-empty"' . $attr .
'>' . $title .
'</span><span class="dbx-cms-inline-video-play" aria-hidden="true"><i class="bi bi-play-fill"></i></span>';
669 private function inline_media_embed_html(array $row) {
670 $id = (int)($row[
'id'] ?? 0);
671 if ($id <= 0)
return '';
672 $row = $this->normalize_media_row($row);
673 $attr =
' data-cms-media-id="' . $id .
'" data-cms-media-slot="inline" contenteditable="false" tabindex="0"';
674 if (in_array($this->media_type($row), array(
'video',
'external_video'),
true)) {
675 $inner = $this->inline_media_placeholder_html($id);
676 return $inner !==
'' ?
'<figure class="dbx-cms-inline-media dbx-cms-inline-video-block"' . $attr .
'>' . $inner .
'</figure><p></p>' :
'';
678 $url = htmlspecialchars((
string)($row[
'url'] ?? $this->media_url($row)), ENT_QUOTES,
'UTF-8');
679 if ($url ===
'')
return '';
680 $alt = htmlspecialchars((
string)($row[
'alt'] ?? $row[
'title'] ?? $row[
'file_name'] ??
''), ENT_QUOTES,
'UTF-8');
681 $title = htmlspecialchars((
string)($row[
'title'] ?? $row[
'alt'] ?? $row[
'file_name'] ??
''), ENT_QUOTES,
'UTF-8');
682 return '<p class="dbx-cms-inline-media"' . $attr .
'><img class="dbx-cms-inline-image" src="' . $url .
'" alt="' . $alt .
'" title="' . $title .
'"' . $attr .
'></p><p></p>';
685 private function bool_int($value, $default = 1) {
686 if ($value ===
'' || $value ===
null)
return $default;
687 return (
int)((string)$value ===
'1' || $value === 1 || $value ===
true || $value ===
'on');
690 private function clean_text($value, $max = 254) {
691 $value = trim((
string)$value);
692 if ($max > 0 && strlen($value) > $max) {
693 $value = substr($value, 0, $max);
698 private function slug($text) {
699 return dbxContent_permalink::slug($text);
702 private function page_permalink(
$db, $folder_id, $title, $permalink) {
703 $permalink = dbxContent_permalink::normalize($this->clean_text($permalink, 254));
704 if ($permalink ===
'') {
705 $permalink = dbxContent_permalink::build(
$db, dbxContentLng::ddFolder(), $folder_id, $title);
710 private function page_permalink_exists(
$db,
string $permalink):
bool {
711 $permalink = dbxContent_permalink::normalize($permalink);
712 if ($permalink ===
'' || !is_object(
$db)) {
716 $escaped = str_replace(
"'",
"''", $permalink);
718 dbxContentLng::ddContent(),
719 "permalink = '" . $escaped .
"'",
728 return is_array($rows) && isset($rows[0][
'id']);
731 private function duplicate_page_permalink(
$db,
int $folderId,
string $title,
string $permalink):
string {
732 $base = dbxContent_permalink::normalize($this->clean_text($permalink, 254));
734 $base = dbxContent_permalink::build(
$db, dbxContentLng::ddFolder(), $folderId, $title);
742 $suffix = $copyNumber === 1 ?
'-kopie' :
'-kopie-' . $copyNumber;
743 $maxBaseLength = max(1, 254 - strlen($suffix));
744 $candidateBase = rtrim(substr(
$base, 0, $maxBaseLength),
'/-');
745 if ($candidateBase ===
'') {
746 $candidateBase =
'seite';
748 $candidate = dbxContent_permalink::normalize($candidateBase . $suffix);
750 }
while ($this->page_permalink_exists(
$db, $candidate));
755 private function resolve_cms_page_id():
int {
756 $id = (int)
dbx()->get_modul_var(
'cid', 0,
'int');
758 $id = (int)
dbx()->get_modul_var(
'dbx_cid', 0,
'int');
761 $id = (int)
dbx()->get_modul_var(
'rid', 0,
'int');
764 $id = (int)
dbx()->get_modul_var(
'id', 0,
'int');
770 $permalink = dbxContent_permalink::normalize((
string)
dbx()->get_modul_var(
'permalink',
'',
'parameter'));
771 if ($permalink ===
'') {
772 $permalink = dbxContent_permalink::normalize((
string)
dbx()->get_request_var(
'permalink',
''));
774 if ($permalink ===
'') {
778 $db =
dbx()->get_system_obj(
'dbxDB');
779 $rec =
$db->select1(dbxContentLng::ddContent(), array(
'permalink' => $permalink),
'id', 0);
780 return is_array($rec) ? (int)($rec[
'id'] ?? 0) : 0;
783 private function attach_unreachable_tree_nodes(array $tree): array {
784 $reachablePageIds = array();
785 $reachableFolderIds = array();
786 foreach (is_array($tree[
'flat'] ??
null) ? $tree[
'flat'] : array() as $node) {
787 if (!is_array($node)) {
790 if (($node[
'_type'] ??
'') ===
'page') {
791 $reachablePageIds[(int)($node[
'_id'] ?? 0)] =
true;
792 } elseif (($node[
'_type'] ??
'') ===
'folder') {
793 $reachableFolderIds[(int)($node[
'_id'] ?? 0)] =
true;
797 $items = is_array($tree[
'items'] ??
null) ? $tree[
'items'] : array();
798 $orphanPages = array();
799 foreach ($items as $row) {
800 if (!is_array($row)) {
803 $id = (int)($row[
'id'] ?? 0);
804 if ($id > 0 && !isset($reachablePageIds[$id])) {
805 $orphanPages[] = $row;
808 if (!count($orphanPages)) {
813 foreach ($orphanPages as $row) {
814 $id = (int)($row[
'id'] ?? 0);
818 $parent = (int)($row[
'folder'] ?? 0);
819 $children[] = $row + array(
820 '_node_id' =>
'page-' . $id,
823 '_parent' => $parent,
824 '_title' => (
string)($row[
'title'] ?? (
'Seite ' . $id)),
825 '_rights' => (
string)($row[
'group_read'] ??
''),
826 '_children' => array(),
831 if (!count($children)) {
835 $orphanFolder = array(
837 'name' =>
'Nicht im Baum',
838 '_node_id' =>
'folder--999001',
842 '_title' =>
'Nicht im Baum',
844 '_children' => $children,
848 $tree[
'nodes'] = array_merge(is_array($tree[
'nodes'] ??
null) ? $tree[
'nodes'] : array(), array($orphanFolder));
849 foreach ($children as $child) {
850 $tree[
'flat'][] = $child;
852 $tree[
'flat'][] = $orphanFolder;
857 private function cms_tree() {
858 $db =
dbx()->get_system_obj(
'dbxDB');
859 $this->ensure_cms_schema(
$db);
860 $tree =
$db->select_tree(dbxContentLng::ddFolder(), dbxContentLng::ddContent(), array(
861 'folder_order' =>
'sorter,name,id',
862 'item_order' =>
'sorter,title,id',
863 'verify_access' => 0,
865 $tree = $this->attach_unreachable_tree_nodes($tree);
866 return $this->decorate_tree($tree);
869 private function dd_server($dd, $fallback) {
870 $db =
dbx()->get_system_obj(
'dbxDB');
871 if (method_exists(
$db,
'load_dd')) {
872 $sys =
$db->load_dd($dd);
873 $mod = $sys[
'dd_modul'] ??
'dbx';
874 $name = $sys[
'dd_name'] ??
'';
875 if ($name && isset($_SESSION[
'dbx'][
'cache'][
'dd'][$mod][$name][
'table'][
'server'])) {
876 return $_SESSION[
'dbx'][
'cache'][
'dd'][$mod][$name][
'table'][
'server'];
882 private function clear_cms_dd_cache() {
883 if (!isset($_SESSION[
'dbx'][
'cache'][
'dd']) || !is_array($_SESSION[
'dbx'][
'cache'][
'dd']))
return;
884 foreach ($_SESSION[
'dbx'][
'cache'][
'dd'] as $modul => $dds) {
885 if (!is_array($dds))
continue;
886 unset($_SESSION[
'dbx'][
'cache'][
'dd'][$modul][dbxContentLng::ddContent()]);
887 unset($_SESSION[
'dbx'][
'cache'][
'dd'][$modul][dbxContentLng::ddFolder()]);
888 unset($_SESSION[
'dbx'][
'cache'][
'dd'][$modul][$this->dd_media]);
889 unset($_SESSION[
'dbx'][
'cache'][
'dd'][$modul][$this->dd_media_usage]);
893 private function clear_cms_tpl_cache() {
894 if (!isset($_SESSION[
'dbx'][
'cache'][
'tpl'][
'dbxContent_admin']) || !is_array($_SESSION[
'dbx'][
'cache'][
'tpl'][
'dbxContent_admin']))
return;
895 foreach (array(
'cms-admin',
'cms-admin-frame',
'cms-admin-right',
'cms-admin-media-panel',
'cms-admin-settings-panels') as $tpl) {
896 unset($_SESSION[
'dbx'][
'cache'][
'tpl'][
'dbxContent_admin'][$tpl]);
902 $table = preg_replace(
'/[^A-Za-z0-9_]+/',
'', (
string)
$table);
903 $name = preg_replace(
'/[^A-Za-z0-9_]+/',
'', (
string)$name);
907 if (is_array($cols)) {
908 foreach ($cols as $col) {
909 if (isset($col[
'name']) && strtolower((
string)$col[
'name']) === strtolower($name))
return;
913 $db->exec(
$server,
'ALTER TABLE ' .
$table .
' ADD COLUMN ' . $name .
' ' . $type);
918 $table = preg_replace(
'/[^A-Za-z0-9_]+/',
'', (
string)
$table);
920 $exists =
$db->select_query(
$server,
"SELECT name FROM sqlite_master WHERE type='table' AND name='" . str_replace(
"'",
"''",
$table) .
"'");
921 if (is_array($exists) && !empty($exists))
return;
925 private function ensure_index(
$db,
$server, $name, $sql) {
927 $name = preg_replace(
'/[^A-Za-z0-9_]+/',
'', (
string)$name);
928 if (
$server ===
'' || $name ===
'')
return;
929 $exists =
$db->select_query(
$server,
"SELECT name FROM sqlite_master WHERE type='index' AND name='" . str_replace(
"'",
"''", $name) .
"'");
930 if (is_array($exists) && !empty($exists))
return;
935 $this->ensure_cms_schema(
$db);
938 private function ensure_cms_schema(
$db) {
939 static $done =
false;
943 $this->clear_cms_dd_cache();
945 $content_server = $this->dd_server(dbxContentLng::ddContent(),
'dbx|dbxContent.db3');
946 $folder_server = $this->dd_server(dbxContentLng::ddFolder(),
'dbx|dbxContent.db3');
947 $content_table =
$db->get_dd_table(dbxContentLng::ddContent());
948 $folder_table =
$db->get_dd_table(dbxContentLng::ddFolder());
949 $media_server = $this->dd_server($this->dd_media,
'dbx|dbxMedia.db3');
950 $usage_server = $this->dd_server($this->dd_media_usage,
'dbx|dbxMedia.db3');
952 $content_text_cols = array(
961 'gallery_visible_count',
962 'gallery_image_size',
964 'gallery_click_behavior',
967 $folder_text_cols = array(
977 foreach ($content_text_cols as $col) {
978 $this->ensure_column(
$db, $content_server, $content_table, $col,
'TEXT');
980 $this->ensure_column(
$db, $content_server, $content_table,
'gallery_lightbox_width',
"TEXT DEFAULT '100vw'");
981 $this->ensure_column(
$db, $content_server, $content_table,
'meta_robots',
"TEXT DEFAULT 'index,follow'");
982 $this->ensure_column(
$db, $content_server, $content_table,
'seo_title',
"TEXT DEFAULT ''");
983 $this->ensure_column(
$db, $content_server, $content_table,
'seo_image_id',
'INTEGER DEFAULT 0');
985 foreach ($folder_text_cols as $col) {
986 $this->ensure_column(
$db, $folder_server, $folder_table, $col,
'TEXT');
988 $this->ensure_column(
$db, $folder_server, $folder_table,
'sorter',
"TEXT DEFAULT ''");
990 $this->ensure_column(
$db, $media_server,
'media',
'thumb_file_path',
'TEXT');
991 $this->ensure_column(
$db, $media_server,
'media',
'thumb_width',
'INTEGER');
992 $this->ensure_column(
$db, $media_server,
'media',
'thumb_height',
'INTEGER');
993 $this->ensure_column(
$db, $media_server,
'media',
'media_type',
'TEXT');
994 $this->ensure_column(
$db, $media_server,
'media',
'storage_type',
"TEXT DEFAULT 'local'");
995 $this->ensure_column(
$db, $media_server,
'media',
'media_folder',
"TEXT DEFAULT ''");
996 $this->ensure_column(
$db, $media_server,
'media',
'provider',
"TEXT DEFAULT ''");
997 $this->ensure_column(
$db, $media_server,
'media',
'provider_id',
"TEXT DEFAULT ''");
998 $this->ensure_column(
$db, $media_server,
'media',
'external_url',
"TEXT DEFAULT ''");
999 $this->ensure_column(
$db, $media_server,
'media',
'embed_url',
"TEXT DEFAULT ''");
1001 $this->ensure_table(
$db, $usage_server,
'media_usage',
'CREATE TABLE media_usage (
1002 id INTEGER PRIMARY KEY AUTOINCREMENT,
1004 create_uid INTEGER DEFAULT 0,
1006 update_uid INTEGER DEFAULT 0,
1008 active INTEGER DEFAULT 1,
1009 media_id INTEGER DEFAULT 0,
1010 content_id INTEGER DEFAULT 0,
1011 folder_id INTEGER DEFAULT 0,
1012 slot TEXT DEFAULT "gallery",
1013 sorter TEXT DEFAULT "",
1014 template TEXT DEFAULT "",
1015 caption TEXT DEFAULT "",
1016 settings TEXT DEFAULT ""
1018 $this->ensure_index(
$db, $usage_server,
'idx_media_usage_media',
'CREATE INDEX idx_media_usage_media ON media_usage(media_id)');
1019 $this->ensure_index(
$db, $usage_server,
'idx_media_usage_content_slot',
'CREATE INDEX idx_media_usage_content_slot ON media_usage(content_id,slot)');
1020 $this->ensure_index(
$db, $usage_server,
'idx_media_usage_folder_slot',
'CREATE INDEX idx_media_usage_folder_slot ON media_usage(folder_id,slot)');
1022 $this->normalize_content_gallery_defaults(
$db, $content_server, $content_table);
1023 dbxContentLngSync::ensureSchema(
$db);
1026 private function normalize_content_gallery_defaults(
$db,
$server,
$table =
'content') {
1032 'gallery_template' =>
'image-gallery',
1033 'gallery_visible_count' =>
'3',
1034 'gallery_image_size' =>
'original',
1035 'gallery_lightbox_width' =>
'100vw',
1036 'gallery_overflow' =>
'grid',
1037 'gallery_click_behavior' =>
'lightbox',
1040 foreach ($defaults as
$field => $value) {
1041 $value = str_replace(
"'",
"''", $value);
1042 $db->exec(
$server,
"UPDATE $table SET $field = '$value' WHERE $field IS NULL OR TRIM($field) = '' OR LOWER(TRIM($field)) = 'parent'");
1046 private function normalize_gallery_row(array $row) {
1048 'gallery_template' =>
'image-gallery',
1049 'gallery_visible_count' =>
'3',
1050 'gallery_image_size' =>
'original',
1051 'gallery_lightbox_width' =>
'100vw',
1052 'gallery_overflow' =>
'grid',
1053 'gallery_click_behavior' =>
'lightbox',
1056 foreach ($defaults as
$field => $value) {
1057 $current = trim((
string)($row[
$field] ??
''));
1058 if ($current ===
'' || strtolower($current) ===
'parent') {
1066 private function tree_row_html(array $node) {
1067 $tpl = dbx()->get_system_obj(
'dbxTPL');
1068 $type = ($node[
'_type'] ??
'') ===
'folder' ?
'folder' :
'page';
1069 $id = (int)($node[
'_id'] ?? 0);
1070 $title = htmlspecialchars((
string)($node[
'_title'] ??
''), ENT_QUOTES,
'UTF-8');
1071 $rights = htmlspecialchars((
string)($node[
'_rights'] ??
''), ENT_QUOTES,
'UTF-8');
1074 '_id' => (
string)$id,
1075 '_parent' => (
string)($node[
'_parent'] ?? ($type ===
'folder' ? $id : 0)),
1076 'icon' => $type ===
'folder' ?
'<i class="bi bi-folder2-open"></i>' :
'<i class="bi bi-file-earmark-text"></i>',
1077 'folder_edit_btn' => $type ===
'folder'
1078 ?
'<button type="button" class="dbx-cms-tree-edit" data-cms-folder-edit-btn title="Ordner bearbeiten" aria-label="Ordner bearbeiten"><i class="bi bi-pencil-square"></i></button>'
1080 'folder_id_label' =>
'',
1081 'title_label' => $type ===
'page' ?
'<span class="dbx-cms-page-id">(' . $id .
')</span> ' . $title : $title,
1082 'rights_label' => ($type ===
'folder' && $rights !==
'') ?
'<span class="dbx-cms-rights" data-cms-folder-edit title="Ordnerrechte bearbeiten">' . $rights .
'</span>' :
'',
1083 'lng_badges' => (
string)($node[
'_lng_badges'] ??
''),
1085 return $tpl->get_tpl(
'dbxContent_admin|cms-tree-row', $data);
1088 private function attach_lng_coverage(array $node,
$db): array {
1089 $type = ($node[
'_type'] ??
'') ===
'folder' ?
'folder' :
'page';
1090 $id = (int)($node[
'_id'] ?? 0);
1091 $dd = $type ===
'folder' ? dbxContentLng::ddFolder() : dbxContentLng::ddContent();
1092 $lngUid = trim((
string)($node[
'lng_uid'] ?? $node[
'_lng_uid'] ??
''));
1094 if ($lngUid ===
'' && $id > 0) {
1095 $lngUid = dbxContentLngSync::ensureRecordUid(
$db, $dd, $id, $type ===
'folder' ?
'f' :
'p');
1098 $node[
'_lng_uid'] = $lngUid;
1099 if ($lngUid !==
'') {
1100 $coverage = dbxContentLngSync::coverageForUid(
$db, $type, $lngUid);
1101 $node[
'_lng_coverage'] = $coverage;
1102 $node[
'_lng_badges'] = dbxContentLngSync::badgesHtml($coverage);
1108 private function decorate_tree_nodes(array $nodes, array &$flat) {
1109 $db = dbx()->get_system_obj(
'dbxDB');
1111 foreach ($nodes as $node) {
1112 if (!is_array($node))
continue;
1113 if (($node[
'_type'] ??
'') ===
'folder' && (
int)($node[
'_id'] ?? 0) > 0) {
1114 $full =
$db->select1(dbxContentLng::ddFolder(), (
int)$node[
'_id'],
'*', 0);
1115 if (is_array($full)) {
1116 foreach (array(
'template',
'group_read',
'hero_template',
'hero_image_id',
'hero_margin_top',
'hero_height',
'hero_variant',
'hero_sticky',
'hero_scroll_layer') as $key) {
1117 if (array_key_exists($key, $full)) {
1118 $node[$key] = $full[$key];
1119 $node[
'_' . $key] = $full[$key];
1122 $node[
'_template'] = $full[
'template'] ?? ($node[
'_template'] ??
'');
1123 $node[
'_rights'] = $full[
'group_read'] ?? ($node[
'_rights'] ??
'');
1124 $node[
'lng_uid'] = $full[
'lng_uid'] ??
'';
1127 if (($node[
'_type'] ??
'') ===
'page' && (
int)($node[
'_id'] ?? 0) > 0) {
1128 $full =
$db->select1(dbxContentLng::ddContent(), (
int)$node[
'_id'],
'lng_uid,lng_sync', 0);
1129 if (is_array($full)) {
1130 $node[
'lng_uid'] = $full[
'lng_uid'] ??
'';
1133 $node = $this->attach_lng_coverage($node,
$db);
1134 $node[
'_row_html'] = $this->tree_row_html($node);
1136 if (isset($node[
'_children']) && is_array($node[
'_children'])) {
1137 $node[
'_children'] = $this->decorate_tree_nodes($node[
'_children'], $flat);
1144 private function render_tree_report(array $flat) {
1145 $report = dbx()->get_system_obj(
'dbxReport');
1146 $report->init(
'cms-content-tree',
'cms-tree-row');
1147 $report->_mode =
'tpl';
1148 $report->_rdata = $flat;
1149 $report->_rcount = count($flat);
1150 $report->_rrows = max(1, count($flat));
1151 $report->_rflds = array();
1152 return $report->run();
1155 private function decorate_tree(array $tree) {
1157 $tree[
'nodes'] = $this->decorate_tree_nodes(is_array($tree[
'nodes'] ??
null) ? $tree[
'nodes'] : array(), $flat);
1158 $tree[
'flat'] = $flat;
1159 $tree[
'tree_html'] = $this->render_tree_report($flat);
1163 private function media_url($row, $thumb =
false) {
1164 $id = (int)($row[
'id'] ?? 0);
1165 if ($id <= 0)
return '';
1166 $url =
'index.php?dbx_modul=dbxContent&dbx_run1=media&dbx_mid=' . $id;
1167 if ($thumb && !empty($row[
'thumb_file_path'])) $url .=
'&dbx_thumb=1';
1168 $v = (int)($row[
'size'] ?? 0) .
'-' . (int)($row[
'width'] ?? 0) .
'x' . (int)($row[
'height'] ?? 0);
1169 if ($thumb) $v .=
'-' . (int)($row[
'thumb_width'] ?? 0) .
'x' . (int)($row[
'thumb_height'] ?? 0);
1170 $url .=
'&_v=' . rawurlencode($v);
1174 private function external_video_thumb_url($row) {
1175 $provider = strtolower(trim((
string)($row[
'provider'] ??
'')));
1176 $provider_id = trim((
string)($row[
'provider_id'] ??
''));
1177 if ($provider ===
'youtube' && preg_match(
'~^[A-Za-z0-9_-]{11}$~', $provider_id)) {
1178 return 'https://img.youtube.com/vi/' . $provider_id .
'/hqdefault.jpg';
1183 private function external_video_json_meta($file) {
1184 $raw = @file_get_contents((
string)$file);
1185 if ($raw ===
false || $raw ===
'')
return array();
1186 $data = json_decode($raw,
true);
1187 if (!is_array($data))
return array();
1188 $provider = strtolower(trim((
string)($data[
'provider'] ??
'youtube')));
1189 $provider_id = trim((
string)($data[
'provider_id'] ?? pathinfo((
string)$file, PATHINFO_FILENAME)));
1190 $title = trim((
string)($data[
'title'] ?? (
'YouTube ' . $provider_id)));
1191 $external_url = trim((
string)($data[
'external_url'] ??
''));
1192 $embed_url = trim((
string)($data[
'embed_url'] ??
''));
1193 if ($embed_url ===
'' && preg_match(
'~^[A-Za-z0-9_-]{11}$~', $provider_id)) {
1194 $embed_url =
'https://www.youtube.com/embed/' . $provider_id;
1197 'provider' => $provider,
1198 'provider_id' => $provider_id,
1201 'external_url' => $external_url,
1202 'embed_url' => $embed_url,
1206 private function media_type($row) {
1207 $stored = strtolower(trim((
string)($row[
'media_type'] ??
'')));
1208 if (in_array($stored, array(
'image',
'video',
'external_video',
'file'),
true))
return $stored;
1209 $storage = strtolower(trim((
string)($row[
'storage_type'] ??
'')));
1210 if ($storage ===
'external' || trim((
string)($row[
'provider'] ??
'')) !==
'' || trim((
string)($row[
'embed_url'] ??
'')) !==
'')
return 'external_video';
1211 $mime = strtolower((
string)($row[
'mime'] ??
''));
1212 $name = strtolower((
string)($row[
'file_name'] ?? $row[
'file_path'] ??
''));
1213 if (strpos($mime,
'video/') === 0 || preg_match(
'/\.(mp4|webm|ogv|ogg|mov|m4v)$/i', $name))
return 'video';
1214 if (strpos($mime,
'image/') === 0 || preg_match(
'/\.(jpg|jpeg|png|gif|webp|svg)$/i', $name))
return 'image';
1218 private function media_file_exists($row) {
1219 $row = is_array($row) ? $row : array();
1220 if (strtolower((
string)($row[
'storage_type'] ??
'')) ===
'external')
return trim((
string)($row[
'embed_url'] ?? $row[
'external_url'] ??
'')) !==
'';
1221 $rel = ltrim(str_replace(
'\\',
'/', (
string)($row[
'file_path'] ??
'')),
'/');
1222 if ($rel ===
'' || strpos($rel,
'..') !==
false)
return false;
1227 if (!
$base)
return false;
1229 $file =
$root . $rel;
1231 $real = realpath($file);
1232 return $real && strpos($real,
$base) === 0 && is_file($real) && is_readable($real);
1235 private function media_thumb_exists($row) {
1236 $row = is_array($row) ? $row : array();
1237 if (strtolower((
string)($row[
'storage_type'] ??
'')) ===
'external' && trim((
string)($row[
'thumb_file_path'] ??
'')) ===
'')
return false;
1238 $rel = ltrim(str_replace(
'\\',
'/', (
string)($row[
'thumb_file_path'] ??
'')),
'/');
1239 if ($rel ===
'' || strpos($rel,
'..') !==
false)
return false;
1242 $file =
$root . $rel;
1243 if (function_exists(
'dbx_os_path_file')) {
1249 $real = realpath($file);
1250 return $base && $real && strpos($real,
$base) === 0 && is_file($real) && is_readable($real);
1253 private function valid_media_slot($slot, $default =
'gallery') {
1254 $slot = strtolower(trim((
string)$slot));
1255 $allowed = array(
'hero',
'gallery',
'inline',
'header',
'teaser',
'footer',
'shop');
1256 return in_array($slot, $allowed,
true) ? $slot : $default;
1259 private function media_rel_dir($slot) {
1260 return 'media/' . $this->valid_media_slot($slot) .
'/';
1263 private function video_media_base_folder() {
1266 if (is_dir(
$root .
'videos'))
return 'videos';
1267 if (is_dir(
$root .
'video'))
return 'video';
1271 private function media_standard_bases() {
1272 return array(
'img' =>
'image', $this->video_media_base_folder() =>
'video',
'youtube' =>
'external_video',
'file' =>
'file');
1275 private function canonical_media_folder($folder) {
1276 $folder = strtolower(trim(str_replace(
'\\',
'/', (
string)$folder),
'/'));
1277 if (preg_match(
'~^youtube/[a-z0-9_-]{11}$~', $folder))
return 'youtube';
1281 private function clean_media_folder($folder, $media_type =
'image') {
1282 $media_type = $this->media_type(array(
'media_type' => $media_type));
1283 $folder = $this->canonical_media_folder($folder);
1284 $folder = strtolower(str_replace(
'\\',
'/', trim((
string)$folder)));
1285 $folder = preg_replace(
'~/+~',
'/', $folder);
1286 $folder = trim($folder,
'/');
1288 foreach (explode(
'/', $folder) as $part) {
1289 $part = preg_replace(
'/[^A-Za-z0-9_-]+/',
'-', $part);
1290 $part = trim($part,
'-_');
1291 if ($part !==
'' && $part !==
'.' && $part !==
'..') $parts[] = $part;
1293 $folder = implode(
'/', $parts);
1295 if ($folder ===
'module') {
1299 if ($media_type ===
'video') {
1300 $base = $this->video_media_base_folder();
1301 if ($folder ===
'' || $folder ===
'video' || $folder ===
'videos' || $folder ===
'img' || $folder ===
'img/video')
return $base;
1302 if (substr($folder, 0, 10) ===
'img/video/')
return $base .
'/' . substr($folder, 10);
1303 if (substr($folder, 0, 6) ===
'video/')
return $base .
'/' . substr($folder, 6);
1304 if (substr($folder, 0, 7) ===
'videos/')
return $base .
'/' . substr($folder, 7);
1307 if ($media_type ===
'external_video') {
1308 if ($folder ===
'' || $folder ===
'youtube')
return 'youtube';
1309 if (substr($folder, 0, 8) !==
'youtube/')
return 'youtube';
1312 if ($media_type ===
'file') {
1313 if ($folder ===
'' || $folder ===
'file')
return 'file/allgemein';
1314 if (substr($folder, 0, 5) !==
'file/')
return 'file/allgemein';
1317 if ($folder ===
'' || $folder ===
'img' || $folder ===
'image')
return 'img/allgemein';
1318 if (substr($folder, 0, 4) !==
'img/')
return 'img/allgemein';
1322 private function media_type_from_folder($folder, $fallback =
'image') {
1323 $folder = $this->canonical_media_folder($folder);
1324 if (strpos($folder,
'img/video') === 0)
return 'video';
1325 if (strpos($folder,
'videos/') === 0 || $folder ===
'videos')
return 'video';
1326 if (strpos($folder,
'video/') === 0 || $folder ===
'video')
return 'video';
1327 if (strpos($folder,
'youtube/') === 0 || $folder ===
'youtube')
return 'external_video';
1328 if (strpos($folder,
'file/') === 0 || $folder ===
'file')
return 'file';
1332 private function media_folder_from_path($rel, $media_type =
'image') {
1333 $rel = ltrim(str_replace(
'\\',
'/', (
string)$rel),
'/');
1334 if (preg_match(
'~^media/module/[^/]+$~i', $rel)) {
1337 if (preg_match(
'~^media/youtube/[^/]+\.json$~i', $rel)) {
1340 if (preg_match(
'~^media/(img|video|youtube|external|file)/([^/]+)/[^/]+$~i', $rel, $m)) {
1341 return $this->clean_media_folder($m[1] .
'/' . $m[2], $media_type);
1343 if (preg_match(
'~^media/(img|video|file)/([^/]+)/?$~i', $rel, $m)) {
1344 return $this->clean_media_folder($m[1] .
'/' . $m[2], $media_type);
1346 return $this->clean_media_folder(
'', $media_type);
1349 private function media_folder_rel_dir($folder, $media_type =
'image') {
1350 return 'media/' . $this->clean_media_folder($folder, $media_type) .
'/';
1353 private function media_thumb_rel_dir($slot) {
1354 $slot_text = (string)$slot;
1355 $folder_type =
'image';
1356 if (preg_match(
'~^(youtube|external)(/|$)~', $slot_text)) $folder_type =
'external_video';
1357 elseif (preg_match(
'~^(videos|video|img/video)(/|$)~', $slot_text)) $folder_type =
'video';
1358 elseif (preg_match(
'~^file(/|$)~', $slot_text)) $folder_type =
'file';
1359 $folder = $this->clean_media_folder($slot, $folder_type);
1360 return 'media/_thumbs/' . $folder .
'/';
1363 private function cms_media_dir($slot =
'') {
1364 $slot_text = (string)$slot;
1365 $folder_type =
'image';
1366 if (preg_match(
'~^(youtube|external)(/|$)~', $slot_text)) $folder_type =
'external_video';
1367 elseif (preg_match(
'~^(videos|video|img/video)(/|$)~', $slot_text)) $folder_type =
'video';
1368 elseif (preg_match(
'~^file(/|$)~', $slot_text)) $folder_type =
'file';
1369 $rel = $slot ===
'' ?
'media/' : $this->media_folder_rel_dir($slot, $folder_type);
1374 private function cms_media_base_dir(
$base) {
1375 $base = strtolower(trim(str_replace(
'\\',
'/', (
string)
$base),
'/'));
1376 if (!in_array(
$base, array(
'img',
'video',
'videos',
'youtube',
'external',
'file',
'module'),
true))
return '';
1377 $rel =
'media/' .
$base .
'/';
1382 private function cms_media_thumb_dir($slot) {
1387 private function unique_media_name($name, $prefix =
'') {
1388 $name = basename((
string)$name);
1389 $name = preg_replace(
'/[^A-Za-z0-9_.-]+/',
'-', $name);
1390 $prefix = $prefix !==
'' ? rtrim($prefix,
'-') .
'-' :
'';
1391 return date(
'YmdHis') .
'-' . substr(md5($name . microtime(
true)), 0, 8) .
'-' . $prefix . $name;
1394 private function media_slots() {
1395 return array(
'hero',
'gallery',
'inline',
'header',
'teaser',
'footer');
1398 private function media_allowed_extensions() {
1399 return array(
'jpg',
'jpeg',
'png',
'gif',
'webp',
'svg',
'mp4',
'webm',
'ogv',
'ogg',
'mov',
'm4v');
1402 private function file_from_media_rel($rel) {
1403 $rel = ltrim(str_replace(
'\\',
'/', (
string)$rel),
'/');
1404 if ($rel ===
'' || strpos($rel,
'..') !==
false)
return '';
1406 return function_exists(
'dbx_os_path_file') ?
dbx_os_path_file($file) : $file;
1409 private function relative_file_exists($rel) {
1410 $file = $this->file_from_media_rel($rel);
1411 return $file !==
'' && is_file($file) && is_readable($file);
1414 private function detect_media_mime($file, $ext) {
1415 $mime = function_exists(
'mime_content_type') ? (string)@mime_content_type($file) :
'';
1416 if ($mime !==
'')
return $mime;
1417 $ext = strtolower((
string)$ext);
1418 if (in_array($ext, array(
'mp4',
'webm',
'ogv',
'ogg',
'mov',
'm4v'),
true)) {
1419 return $ext ===
'webm' ?
'video/webm' : ($ext ===
'mov' ?
'video/quicktime' :
'video/mp4');
1421 return $ext ===
'svg' ?
'image/svg+xml' :
'image/' . ($ext ===
'jpg' ?
'jpeg' : $ext);
1424 private function media_file_meta($file, $name) {
1425 $ext = strtolower(pathinfo((
string)$name, PATHINFO_EXTENSION));
1426 $mime = $this->detect_media_mime($file, $ext);
1429 $img = @getimagesize($file);
1430 if (is_array($img)) {
1431 $width = (int)($img[0] ?? 0);
1432 $height = (int)($img[1] ?? 0);
1436 'size' => (
int)@filesize($file),
1438 'height' => $height,
1439 'media_type' => $this->media_type(array(
'mime' => $mime,
'file_name' => $name)),
1443 private function is_excluded_cms_media_folder($folder) {
1444 $folder = $this->canonical_media_folder(strtolower(trim(str_replace(
'\\',
'/', (
string)$folder),
'/')));
1445 return $folder ===
'module' || strpos($folder,
'module/') === 0;
1448 private function cms_media_exclude_sql() {
1449 return " AND media_folder <> 'module' AND file_path NOT LIKE 'media/module/%'";
1452 private function collect_media_files() {
1454 $allowed = $this->media_allowed_extensions();
1456 foreach (array(
'img',
'video',
'youtube',
'file') as $base_folder) {
1457 $dir = $this->cms_media_base_dir($base_folder);
1458 if (!is_dir(
$dir) || !is_readable(
$dir))
continue;
1459 $it = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(
$dir, \FilesystemIterator::SKIP_DOTS));
1460 foreach ($it as $file) {
1461 if (!$file->isFile() || !$file->isReadable())
continue;
1462 $name = $file->getFilename();
1463 $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
1464 $is_external_base = $base_folder ===
'youtube';
1465 if (!in_array($ext, $allowed,
true) && !($is_external_base && $ext ===
'json'))
continue;
1466 $path = str_replace(
'\\',
'/', $file->getPathname());
1468 if (strpos($path,
$root) !== 0)
continue;
1469 $rel = substr($path, strlen(
$root));
1470 $type = $base_folder ===
'video' ?
'video' : ($is_external_base ?
'external_video' : ($base_folder ===
'file' ?
'file' :
'image'));
1471 if ($ext ===
'json' && $is_external_base) $type =
'external_video';
1472 $files[$rel] = array(
1474 'slot' =>
'gallery',
1475 'folder' => $this->media_folder_from_path($rel, $type),
1476 'file' => $file->getPathname(),
1484 private function collect_media_thumb_files() {
1486 foreach ($this->media_slots() as $slot) {
1489 if (!is_dir(
$dir) || !is_readable(
$dir))
continue;
1490 $it = new \DirectoryIterator(
$dir);
1491 foreach ($it as $file) {
1492 if (!$file->isFile() || !$file->isReadable())
continue;
1493 $rel = $this->media_thumb_rel_dir($slot) . $file->getFilename();
1494 $files[$rel] = array(
1496 'file' => $file->getPathname(),
1503 private function active_media_usage_map(
$db) {
1505 if (!is_object(
$db))
return $map;
1506 $rows =
$db->select($this->dd_media_usage,
'active = 1',
'media_id,content_id,folder_id,slot',
'id',
'ASC',
'', 0, 0, 0);
1507 if (is_array($rows)) {
1508 foreach ($rows as $row) {
1509 if (!is_array($row))
continue;
1510 $media_id = (int)($row[
'media_id'] ?? 0);
1511 if ($media_id <= 0)
continue;
1512 if (!isset($map[$media_id])) $map[$media_id] = array();
1513 $map[$media_id][] = $row;
1517 $content_rows =
$db->select(dbxContentLng::ddContent(),
"content LIKE '%dbx_mid=%' OR content LIKE '%data-cms-media-id=%'",
'id,folder,content',
'id');
1518 if (is_array($content_rows)) {
1519 foreach ($content_rows as $row) {
1520 if (!is_array($row))
continue;
1521 $content_id = (int)($row[
'id'] ?? 0);
1522 foreach ($this->inline_media_ids($row[
'content'] ??
'') as $media_id) {
1523 $media_id = (int)$media_id;
1524 if ($media_id <= 0)
continue;
1525 if (!isset($map[$media_id])) $map[$media_id] = array();
1526 $map[$media_id][] = array(
1527 'media_id' => $media_id,
1528 'content_id' => $content_id,
1529 'folder_id' => (
int)($row[
'folder'] ?? 0),
1538 private function find_relocated_media_row(array $existing,
string $rel,
string $name, array $meta,
string $media_folder, array $usage_map = array()) {
1539 $name_l = strtolower(trim($name));
1540 if ($name_l ===
'')
return null;
1541 $size = (int)($meta[
'size'] ?? 0);
1542 $width = (int)($meta[
'width'] ?? 0);
1543 $height = (int)($meta[
'height'] ?? 0);
1544 $media_folder = $this->canonical_media_folder($media_folder);
1547 foreach ($existing as $row) {
1548 if (!is_array($row))
continue;
1549 $id = (int)($row[
'id'] ?? 0);
1550 $storage = trim((
string)($row[
'storage_type'] ??
''));
1551 if ($storage ===
'external' || (
string)($row[
'media_type'] ??
'') ===
'external_video')
continue;
1552 if (strtolower((
string)($row[
'file_name'] ??
'')) !== $name_l)
continue;
1553 $existing_path = ltrim(str_replace(
'\\',
'/', (
string)($row[
'file_path'] ??
'')),
'/');
1554 if ($existing_path === $rel)
continue;
1555 if ($existing_path !==
'' && $this->relative_file_exists($existing_path))
continue;
1556 $active = (int)($row[
'active'] ?? 0) === 1;
1557 $used = $id > 0 && !empty($usage_map[$id]);
1558 if (!$active && !$used)
continue;
1560 if ($used) $score += 10;
1561 if ($active) $score += 3;
1562 if ($size > 0 && (
int)($row[
'size'] ?? 0) === $size) $score += 2;
1563 if ($width > 0 && $height > 0 && (
int)($row[
'width'] ?? 0) === $width && (
int)($row[
'height'] ?? 0) === $height) $score += 2;
1564 $old_folder = $this->canonical_media_folder(trim((
string)($row[
'media_folder'] ??
'')));
1565 if ($old_folder !==
'' && $old_folder === $media_folder) $score += 1;
1566 if ($score > $best_score) {
1567 $best_score = $score;
1574 private function sync_cms_media_files(
$db) {
1575 $existing =
$db->select($this->dd_media,
'',
'*',
'id');
1576 if (!is_array($existing)) $existing = array();
1577 $usage_map = $this->active_media_usage_map(
$db);
1580 foreach ($existing as $row) {
1581 if (!is_array($row))
continue;
1582 $path = ltrim(str_replace(
'\\',
'/', (
string)($row[
'file_path'] ??
'')),
'/');
1583 if ($path !==
'') $by_path[$path] = $row;
1587 $relocated_ids = array();
1588 foreach ($this->collect_media_files() as $rel => $file_info) {
1589 $file = (string)($file_info[
'file'] ??
'');
1590 if ($file ===
'' || !is_file($file) || !is_readable($file))
continue;
1591 $name = basename($file);
1592 $slot = $this->valid_media_slot($file_info[
'slot'] ?? $this->slot_from_media_rel($rel));
1594 $meta = $this->media_file_meta($file, $name);
1595 $media_type = $meta[
'media_type'];
1596 if ($media_type ===
'file' && preg_match(
'~^media/youtube/~', $rel)) $media_type =
'external_video';
1597 $media_folder = $this->media_folder_from_path($rel, $media_type);
1598 $storage_type = $media_type ===
'external_video' ?
'external' :
'local';
1599 $external_meta = $media_type ===
'external_video' ? $this->external_video_json_meta($file) : array();
1601 $existing_key = $rel;
1602 if (!isset($by_path[$existing_key])) {
1605 $row = isset($by_path[$existing_key]) ? $by_path[$existing_key] :
null;
1606 if (!is_array($row)) {
1607 $row = $this->find_relocated_media_row($existing, $rel, $name, $meta, $media_folder, $usage_map);
1609 if (is_array($row)) {
1610 $id = (int)($row[
'id'] ?? 0);
1611 if ($id <= 0)
continue;
1613 $needs_thumb = $storage_type ===
'local' && (empty($row[
'thumb_file_path']) || !$this->media_thumb_exists($row));
1614 $thumb = $needs_thumb ? $this->create_media_thumbnail($file, $media_folder, $name, $meta[
'mime']) : array();
1617 'mime' => $meta[
'mime'],
1618 'size' => $meta[
'size'],
1619 'width' => $meta[
'width'],
1620 'height' => $meta[
'height'],
1621 'media_type' => $media_type,
1622 'storage_type' => $storage_type,
1623 'media_folder' => $media_folder,
1624 'file_name' => $name,
1625 'file_path' => $rel,
1627 if ($external_meta) $update = array_merge($update, $external_meta);
1628 if ($thumb) $update = array_merge($update, $thumb);
1629 $db->update($this->dd_media, $update, $id);
1630 if (($row[
'file_path'] ??
'') !== $rel) $this->flush_media_by_media_id(
$db, $id);
1631 $by_path[$rel] = array_merge($row, $update);
1632 if (($row[
'file_path'] ??
'') !== $rel) $relocated_ids[$id] = 1;
1636 $thumb = $storage_type ===
'local' ? $this->create_media_thumbnail($file, $media_folder, $name, $meta[
'mime']) : array();
1637 $title = pathinfo($name, PATHINFO_FILENAME);
1638 if (!empty($external_meta[
'title'])) $title = (string)$external_meta[
'title'];
1645 'sorter' => $this->next_media_sorter(
$db, 0, $slot),
1649 'file_name' => $name,
1650 'file_path' => $rel,
1651 'mime' => $meta[
'mime'],
1652 'size' => $meta[
'size'],
1653 'width' => $meta[
'width'],
1654 'height' => $meta[
'height'],
1655 'media_type' => $media_type,
1656 'storage_type' => $storage_type,
1657 'media_folder' => $media_folder,
1659 if ($external_meta) $insert = array_merge($insert, $external_meta);
1660 if ($thumb) $insert = array_merge($insert, $thumb);
1661 $id = (
$db->insert($this->dd_media, $insert) === 1) ?
$db->get_insert_id() : 0;
1664 foreach ($existing as $row) {
1665 if (!is_array($row))
continue;
1666 $id = (int)($row[
'id'] ?? 0);
1667 $path = ltrim(str_replace(
'\\',
'/', (
string)($row[
'file_path'] ??
'')),
'/');
1668 if ($id <= 0 || (
int)($row[
'active'] ?? 0) !== 1)
continue;
1669 if (isset($relocated_ids[$id]))
continue;
1670 if (preg_match(
'~^media/(hero|gallery|inline|header|teaser|footer)/~', $path) && !isset($seen[$path])) {
1671 $db->update($this->dd_media, array(
'active' => 0), $id);
1674 if (!isset($seen[$path]) && !$this->relative_file_exists($path)) {
1675 $db->update($this->dd_media, array(
'active' => 0), $id);
1677 if ((
int)($row[
'active'] ?? 0) === 1 && ($this->is_excluded_cms_media_folder((
string)($row[
'media_folder'] ??
'')) || preg_match(
'~^media/module/~', $path))) {
1678 $db->update($this->dd_media, array(
'active' => 0), $id);
1683 private function normalize_media_row($row) {
1684 $row = is_array($row) ? $row : array();
1685 $file_path = ltrim(str_replace(
'\\',
'/', (
string)($row[
'file_path'] ??
'')),
'/');
1686 $row[
'url'] = $this->media_url($row);
1687 $row[
'thumb_url'] = $this->media_url($row,
true);
1688 $row[
'media_type'] = $this->media_type($row);
1689 $row[
'storage_type'] = trim((
string)($row[
'storage_type'] ??
'')) ?: ($row[
'media_type'] ===
'external_video' ?
'external' :
'local');
1690 $row[
'media_folder'] = $this->canonical_media_folder(trim((
string)($row[
'media_folder'] ??
'')) ?: $this->media_folder_from_path((
string)($row[
'file_path'] ??
''), $row[
'media_type']));
1691 if ($row[
'media_type'] ===
'external_video') {
1692 $row[
'url'] = (string)($row[
'embed_url'] ?? $row[
'external_url'] ??
'');
1693 $row[
'thumb_url'] = $this->external_video_thumb_url($row);
1698 private function normalize_usage_row($row) {
1699 $row = is_array($row) ? $row : array();
1700 $row[
'id'] = (int)($row[
'id'] ?? 0);
1701 $row[
'media_id'] = (int)($row[
'media_id'] ?? 0);
1702 $row[
'content_id'] = (int)($row[
'content_id'] ?? 0);
1703 $row[
'folder_id'] = (int)($row[
'folder_id'] ?? 0);
1704 $row[
'slot'] = $this->valid_media_slot($row[
'slot'] ??
'gallery');
1708 private function usage_where($media_id = 0, $content_id = 0, $folder_id = 0, $slot =
'') {
1709 $where =
'active = 1';
1710 if ((
int)$media_id > 0) $where .=
' AND media_id = ' . (int)$media_id;
1711 if ((
int)$content_id > 0) $where .=
' AND content_id = ' . (int)$content_id;
1712 if ((
int)$folder_id > 0) $where .=
' AND folder_id = ' . (int)$folder_id;
1713 if ((
string)$slot !==
'') $where .=
" AND slot = '" . str_replace(
"'",
"''", $this->valid_media_slot($slot)) .
"'";
1717 private function next_media_usage_sorter(
$db, $content_id, $folder_id, $slot) {
1718 $where = $this->usage_where(0, $content_id, $folder_id, $slot);
1719 $rows =
$db->select($this->dd_media_usage, $where,
'*',
'sorter,id',
'DESC',
'', 1, 0, 0);
1721 if (is_array($rows) && isset($rows[0]) && is_array($rows[0])) $max = (int)($rows[0][
'sorter'] ?? 0);
1722 return sprintf(
'%04d', $max + 10);
1725 private function create_media_usage(
$db, $media_id, $content_id, $folder_id, $slot, $template =
'', $caption =
'', $settings =
'') {
1726 $media_id = (int)$media_id;
1727 $content_id = (int)$content_id;
1728 $folder_id = (int)$folder_id;
1729 $slot = $this->valid_media_slot($slot);
1730 if ($media_id <= 0 || ($content_id <= 0 && $folder_id <= 0))
return 0;
1731 if ($slot ===
'hero') {
1732 if ($content_id > 0)
$db->update($this->dd_media_usage, array(
'active' => 0),
"content_id = " . $content_id .
" AND slot = 'hero' AND active = 1", 0, 1, 1, 0);
1733 elseif ($folder_id > 0)
$db->update($this->dd_media_usage, array('active' => 0),
"folder_id = " . $folder_id .
" AND slot = 'hero' AND active = 1", 0, 1, 1, 0);
1735 return (
$db->insert($this->dd_media_usage, array(
1737 'media_id' => $media_id,
1738 'content_id' => $content_id,
1739 'folder_id' => $folder_id,
1741 'sorter' => $this->next_media_usage_sorter(
$db, $content_id, $folder_id, $slot),
1742 'template' => $this->clean_text($template, 80),
1743 'caption' => $this->clean_text($caption, 0),
1744 'settings' => $this->clean_text($settings, 0),
1745 )) === 1) ?
$db->get_insert_id() : 0;
1748 private function get_media_process_state($token) {
1749 $token = preg_replace(
'/[^A-Za-z0-9_-]+/',
'', (
string)$token);
1750 if ($token ===
'')
return array();
1751 return $_SESSION[
'dbx'][
'dbxContent_admin'][
'media_process'][$token] ?? array();
1754 private function set_media_process_state($token, array $state) {
1755 $token = preg_replace(
'/[^A-Za-z0-9_-]+/',
'', (
string)$token);
1756 if ($token ===
'')
return;
1757 if (!isset($_SESSION[
'dbx']) || !is_array($_SESSION[
'dbx'])) $_SESSION[
'dbx'] = array();
1758 if (!isset($_SESSION[
'dbx'][
'dbxContent_admin']) || !is_array($_SESSION[
'dbx'][
'dbxContent_admin'])) $_SESSION[
'dbx'][
'dbxContent_admin'] = array();
1759 if (!isset($_SESSION[
'dbx'][
'dbxContent_admin'][
'media_process']) || !is_array($_SESSION[
'dbx'][
'dbxContent_admin'][
'media_process'])) $_SESSION[
'dbx'][
'dbxContent_admin'][
'media_process'] = array();
1760 $_SESSION[
'dbx'][
'dbxContent_admin'][
'media_process'][$token] = $state;
1763 private function slot_from_media_rel($rel, $fallback =
'gallery') {
1764 $rel = ltrim(str_replace(
'\\',
'/', (
string)$rel),
'/');
1765 if (preg_match(
'~^media/(hero|gallery|inline|header|teaser|footer)/~', $rel, $m)) {
1766 return $this->valid_media_slot($m[1], $fallback);
1768 return $this->valid_media_slot($fallback);
1771 private function build_media_process_state(
$db, $token) {
1772 $rows =
$db->select($this->dd_media,
'',
'*',
'id');
1773 if (!is_array($rows)) $rows = array();
1776 $referenced_thumbs = array();
1778 $needs_content_cleanup =
false;
1780 foreach ($rows as $row) {
1781 if (!is_array($row))
continue;
1782 $id = (int)($row[
'id'] ?? 0);
1783 $path = ltrim(str_replace(
'\\',
'/', (
string)($row[
'file_path'] ??
'')),
'/');
1784 if ($path !==
'') $by_path[$path] = $id;
1785 $thumb = ltrim(str_replace(
'\\',
'/', (
string)($row[
'thumb_file_path'] ??
'')),
'/');
1786 if ($thumb !==
'') $referenced_thumbs[$thumb] = 1;
1787 if ($id > 0 && preg_match(
'~^media/(hero|gallery|inline|header|teaser|footer)/~', $path)) {
1788 $tasks[] = array(
'type' =>
'record',
'id' => $id);
1789 if (!$this->media_file_exists($row)) $needs_content_cleanup =
true;
1792 && (
int)($row[
'active'] ?? 0) === 1
1793 && $this->media_file_exists($row)
1794 && preg_match(
'~^media/(img|video|file|module)/~', $path)
1795 && (empty($row[
'thumb_file_path']) || !$this->media_thumb_exists($row))) {
1796 $tasks[] = array(
'type' =>
'thumb',
'id' => $id);
1798 if ($id > 0 && (
int)($row[
'active'] ?? 0) !== 1) $needs_content_cleanup =
true;
1801 foreach ($this->collect_media_files() as $rel => $file) {
1802 if (!isset($by_path[$rel])) {
1803 $tasks[] = array(
'type' =>
'import',
'rel' => $rel,
'slot' => $file[
'slot']);
1807 foreach ($this->collect_media_thumb_files() as $rel => $file) {
1808 if (!isset($referenced_thumbs[$rel])) {
1809 $tasks[] = array(
'type' =>
'delete_thumb',
'rel' => $rel);
1813 if ($needs_content_cleanup || $this->content_inline_cleanup_needed(
$db)) {
1814 $tasks[] = array(
'type' =>
'content_cleanup');
1817 if ($this->content_inline_placeholder_repair_needed(
$db)) {
1818 $tasks[] = array(
'type' =>
'content_placeholder_repair');
1821 $cache_content_ids = $this->content_media_reference_ids(
$db);
1822 if ($cache_content_ids) {
1823 $tasks[] = array(
'type' =>
'content_cache_flush',
'ids' => $cache_content_ids);
1827 'proc_key' => $token,
1828 'proc_type' =>
'media_maintenance',
1829 'status' => empty($tasks) ?
'finished' :
'running',
1830 'phase' =>
'media_prepare',
1831 'message' => empty($tasks) ?
'Keine Wartung notwendig.' :
'Medienwartung vorbereitet.',
1834 'total' => count($tasks),
1835 'created_thumbs' => 0,
1836 'imported_media' => 0,
1837 'relinked_media' => 0,
1838 'deleted_thumbs' => 0,
1839 'deactivated_media' => 0,
1840 'cleaned_inline_refs' => 0,
1841 'cleaned_content_pages' => 0,
1842 'repaired_inline_refs' => 0,
1843 'flushed_content_pages' => 0,
1845 'percent' => empty($tasks) ? 100 : 0,
1846 'step_percent' => empty($tasks) ? 100 : 0,
1847 'updated_at' => date(
'H:i:s'),
1851 private function update_media_process_percent(array &$state) {
1852 $total = max(1, (
int)($state[
'total'] ?? 0));
1853 $pos = max(0, min($total, (
int)($state[
'pos'] ?? 0)));
1854 $percent = (int)floor(($pos / $total) * 100);
1855 if (($state[
'status'] ??
'') ===
'finished') $percent = 100;
1856 $state[
'percent'] = max(0, min(100, $percent));
1857 $state[
'step_percent'] = $state[
'percent'];
1860 private function media_process_message(array $state, $current =
'') {
1862 if ((
int)($state[
'created_thumbs'] ?? 0) > 0) $parts[] =
'Thumbs +' . (int)$state[
'created_thumbs'];
1863 if ((
int)($state[
'imported_media'] ?? 0) > 0) $parts[] =
'Import +' . (int)$state[
'imported_media'];
1864 if ((
int)($state[
'relinked_media'] ?? 0) > 0) $parts[] =
'Zuordnung repariert ' . (int)$state[
'relinked_media'];
1865 if ((
int)($state[
'deleted_thumbs'] ?? 0) > 0) $parts[] =
'Thumbs weg ' . (int)$state[
'deleted_thumbs'];
1866 if ((
int)($state[
'deactivated_media'] ?? 0) > 0) $parts[] =
'Medien aus ' . (int)$state[
'deactivated_media'];
1867 if ((
int)($state[
'cleaned_inline_refs'] ?? 0) > 0) $parts[] =
'Content bereinigt ' . (int)$state[
'cleaned_inline_refs'];
1868 if ((
int)($state[
'repaired_inline_refs'] ?? 0) > 0) $parts[] =
'Inline repariert ' . (int)$state[
'repaired_inline_refs'];
1869 if ((
int)($state[
'flushed_content_pages'] ?? 0) > 0) $parts[] =
'Cache aktualisiert ' . (int)$state[
'flushed_content_pages'];
1870 if ((
int)($state[
'errors'] ?? 0) > 0) $parts[] =
'Fehler ' . (int)$state[
'errors'];
1871 $msg = empty($parts) ?
'Keine Aenderungen.' : implode(
' | ', $parts);
1872 if ($current !==
'') $msg .=
' | ' . $current;
1876 private function content_inline_cleanup_needed(
$db) {
1877 $rows =
$db->select(dbxContentLng::ddContent(),
"content LIKE '%dbx_mid=%' OR content LIKE '%data-cms-media-id=%'",
'id,content',
'id');
1878 if (!is_array($rows))
return false;
1879 foreach ($rows as $row) {
1880 if (!is_array($row))
continue;
1881 foreach ($this->inline_media_ids($row[
'content'] ??
'') as $id) {
1882 $media =
$db->select1($this->dd_media, $id);
1883 if (!is_array($media) || (
int)($media[
'active'] ?? 0) !== 1 || !$this->media_file_exists($media))
return true;
1889 private function content_media_reference_ids(
$db) {
1891 $rows =
$db->select(dbxContentLng::ddContent(),
"content LIKE '%dbx_mid=%' OR content LIKE '%data-cms-media-id=%'",
'id,content',
'id');
1892 if (!is_array($rows))
return $ids;
1893 foreach ($rows as $row) {
1894 if (!is_array($row))
continue;
1895 $content_id = (int)($row[
'id'] ?? 0);
1896 if ($content_id <= 0)
continue;
1897 if (!$this->inline_media_ids($row[
'content'] ??
''))
continue;
1898 $ids[$content_id] = $content_id;
1900 return array_values($ids);
1903 private function content_inline_placeholder_repair_needed(
$db) {
1904 foreach (dbxContentLngSync::accessibleLngs() as $lng) {
1905 $rows =
$db->select(dbxContentLng::ddContent((
string)$lng),
"content LIKE '%dbx-cms-inline-media-missing%'",
'id,content',
'id');
1906 if (!is_array($rows))
continue;
1907 foreach ($rows as $row) {
1908 if (!is_array($row))
continue;
1909 if (preg_match(
'/\bdbx-cms-inline-media-missing\b/i', (
string)($row[
'content'] ??
'')))
return true;
1915 private function repair_content_inline_placeholders(
$db) {
1918 $prev_lng = dbxContentLng::current();
1920 foreach (dbxContentLngSync::accessibleLngs() as $lng) {
1921 $lng = (string)$lng;
1922 dbx()->set_system_var(
'dbx_lng', $lng);
1923 $dd = dbxContentLng::ddContent($lng);
1924 $rows =
$db->select($dd,
"content LIKE '%dbx-cms-inline-media-missing%'",
'id,folder,content',
'id');
1925 if (!is_array($rows))
continue;
1927 foreach ($rows as $row) {
1928 if (!is_array($row))
continue;
1929 $content_id = (int)($row[
'id'] ?? 0);
1930 if ($content_id <= 0)
continue;
1931 $html = (string)($row[
'content'] ??
'');
1932 if (stripos($html,
'dbx-cms-inline-media-missing') ===
false)
continue;
1935 $clean = preg_replace_callback(
1936 '/<(p|figure|div)\b([^>]*(?:\bdbx-cms-inline-media-missing-wrap\b|\bdbx-cms-inline-media\b|\bdata-cms-media-id=)[^>]*)>([\s\S]*?\bdbx-cms-inline-media-missing\b[\s\S]*?)<\/\1>/i',
1937 function($m) use (
$db, &$changed) {
1938 $block = (string)($m[0] ??
'');
1939 if (!preg_match(
'/\bdata-cms-media-id=["\']?([0-9]+)/i', $block, $id_match))
return $block;
1940 $id = (int)$id_match[1];
1941 if ($id <= 0)
return $block;
1942 $media =
$db->select1($this->dd_media, $id);
1943 if (!is_array($media) || (
int)($media[
'active'] ?? 0) !== 1 || !$this->media_file_exists($media))
return $block;
1944 $replacement = $this->inline_media_embed_html($media);
1945 if ($replacement ===
'')
return $block;
1947 return $replacement;
1951 if ($clean ===
null || $clean === $html || $changed <= 0)
continue;
1952 $ok =
$db->update($dd, array(
'content' => $clean), $content_id, 0, 0, 0, 0);
1953 if ($ok < 0)
continue;
1954 $this->sync_inline_media_usage(
$db, $content_id, $clean, (
int)($row[
'folder'] ?? 0));
1955 $this->flush_saved_page_cache(
$db, $content_id);
1961 dbx()->set_system_var(
'dbx_lng', $prev_lng);
1963 return array(
'pages' => $pages,
'refs' => $refs);
1966 private function remove_inline_media_ids_from_html($html, array $ids, &$removed = 0) {
1967 $html = (string)$html;
1969 $ids = array_values(array_unique(array_filter(array_map(
'intval', $ids))));
1970 if (!$ids || trim($html) ===
'')
return $html;
1972 if (class_exists(
'\DOMDocument')) {
1973 $doc = new \DOMDocument(
'1.0',
'UTF-8');
1974 $prev = libxml_use_internal_errors(
true);
1975 $wrapped =
'<div id="dbx-cms-clean-root">' . $html .
'</div>';
1976 $ok = $doc->loadHTML(
'<?xml encoding="utf-8" ?>' . $wrapped, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
1977 libxml_clear_errors();
1978 libxml_use_internal_errors($prev);
1980 $xpath = new \DOMXPath($doc);
1981 $root_nodes = $xpath->query(
"//*[@id='dbx-cms-clean-root']");
1982 $root = $root_nodes && $root_nodes->length ? $root_nodes->item(0) :
null;
1983 foreach ($ids as $id) {
1984 $query =
"//*[@data-cms-media-id='" . $id .
"' or contains(@src,'dbx_mid=" . $id .
"') or contains(@href,'dbx_mid=" . $id .
"')]";
1985 $nodes = $xpath->query($query);
1988 foreach ($nodes as $node) {
1990 for ($p = $node; $p && $p !==
$root; $p = $p->parentNode) {
1991 if ($p->nodeType !== XML_ELEMENT_NODE)
continue;
1992 $tag = strtolower($p->nodeName);
1993 $class =
' ' . (string)$p->getAttribute(
'class') .
' ';
1994 if ($tag ===
'figure' || strpos($class,
' dbx-cms-inline-media ') !==
false) {
1998 if ($target && $target->parentNode) $targets[spl_object_hash($target)] = $target;
2001 foreach ($targets as $target) {
2002 if ($target->parentNode) {
2003 $target->parentNode->removeChild($target);
2010 foreach (iterator_to_array(
$root->childNodes) as $child) {
2011 $out .= $doc->saveHTML($child);
2018 foreach ($ids as $id) {
2020 $id_re = preg_quote((
string)$id,
'~');
2021 $html = preg_replace(
'~<(p|figure|div)\b[^>]*(?:data-cms-media-id=["\']?' . $id_re .
'\b|class=["\'][^"\']*dbx-cms-inline-media[^"\']*["\'][^>]*>.*?(?:dbx_mid=' . $id_re .
'\b|data-cms-media-id=["\']?' . $id_re .
'\b))[\s\S]*?</\1>~i',
'', $html);
2022 $html = preg_replace(
'~<(img|video|iframe|source)\b[^>]*(?:dbx_mid=' . $id_re .
'\b|data-cms-media-id=["\']?' . $id_re .
'\b)[^>]*>(?:</\1>)?~i',
'', $html);
2023 if ($html !== $before) $removed++;
2028 private function clean_content_inline_media(
$db) {
2029 $rows =
$db->select(dbxContentLng::ddContent(),
"content LIKE '%dbx_mid=%' OR content LIKE '%data-cms-media-id=%'",
'id,folder,content',
'id');
2030 if (!is_array($rows))
return array(
'pages' => 0,
'refs' => 0);
2033 foreach ($rows as $row) {
2034 if (!is_array($row))
continue;
2035 $content_id = (int)($row[
'id'] ?? 0);
2036 if ($content_id <= 0)
continue;
2038 foreach ($this->inline_media_ids($row[
'content'] ??
'') as $id) {
2039 $media =
$db->select1($this->dd_media, $id);
2040 if (!is_array($media) || (
int)($media[
'active'] ?? 0) !== 1 || !$this->media_file_exists($media)) $bad[] = $id;
2042 if (!$bad)
continue;
2044 $clean = $this->remove_inline_media_ids_from_html((
string)($row[
'content'] ??
''), $bad, $removed);
2045 if ($clean !== (
string)($row[
'content'] ??
'')) {
2046 $db->update(dbxContentLng::ddContent(), array(
'content' => $clean), $content_id);
2047 $this->sync_inline_media_usage(
$db, $content_id, $clean, (
int)($row[
'folder'] ?? 0));
2048 $this->flush_saved_page_cache(
$db, $content_id);
2050 $refs += max($removed, count($bad));
2053 return array(
'pages' => $pages,
'refs' => $refs);
2056 private function import_media_file(
$db, $rel, $slot, &$relinked =
null) {
2058 $rel = ltrim(str_replace(
'\\',
'/', (
string)$rel),
'/');
2059 $slot = $this->valid_media_slot($slot);
2060 $file = $this->file_from_media_rel($rel);
2061 if ($file ===
'' || !is_file($file) || !is_readable($file))
return 0;
2063 $name = basename($file);
2064 $meta = $this->media_file_meta($file, $name);
2065 $media_folder = $this->media_folder_from_path($rel, $meta[
'media_type']);
2066 $existing =
$db->select($this->dd_media,
'',
'*',
'id');
2067 if (!is_array($existing)) $existing = array();
2068 $relocated = $this->find_relocated_media_row($existing, $rel, $name, $meta, $media_folder, $this->active_media_usage_map(
$db));
2069 if (is_array($relocated)) {
2070 $id = (int)($relocated[
'id'] ?? 0);
2072 $thumb = (empty($relocated[
'thumb_file_path']) || !$this->media_thumb_exists($relocated))
2073 ? $this->create_media_thumbnail($file, $media_folder, $name, $meta[
'mime'])
2077 'file_path' => $rel,
2078 'file_name' => $name,
2079 'media_folder' => $media_folder,
2080 'mime' => $meta[
'mime'],
2081 'size' => $meta[
'size'],
2082 'width' => $meta[
'width'],
2083 'height' => $meta[
'height'],
2084 'media_type' => $meta[
'media_type'],
2085 'storage_type' =>
'local',
2087 if ($thumb) $update = array_merge($update, $thumb);
2088 $db->update($this->dd_media, $update, $id);
2089 $this->flush_media_by_media_id(
$db, $id);
2095 $title = pathinfo($name, PATHINFO_FILENAME);
2102 'sorter' => $this->next_media_sorter(
$db, 0, $slot),
2106 'file_name' => $name,
2107 'file_path' => $rel,
2108 'mime' => $meta[
'mime'],
2109 'size' => $meta[
'size'],
2110 'width' => $meta[
'width'],
2111 'height' => $meta[
'height'],
2112 'media_type' => $meta[
'media_type'],
2113 'media_folder' => $media_folder,
2114 'storage_type' =>
'local',
2116 $thumb = $this->create_media_thumbnail($file, $media_folder, $name, $meta[
'mime']);
2117 if ($thumb) $insert = array_merge($insert, $thumb);
2118 return (
$db->insert($this->dd_media, $insert) === 1) ?
$db->get_insert_id() : 0;
2121 private function run_media_process_task(
$db, array &$state, array $task) {
2122 $type = (string)($task[
'type'] ??
'');
2124 if ($type ===
'delete_thumb') {
2125 $rel = ltrim(str_replace(
'\\',
'/', (
string)($task[
'rel'] ??
'')),
'/');
2126 $file = $this->file_from_media_rel($rel);
2127 if ($file !==
'' && is_file($file) && @unlink($file)) {
2128 $state[
'deleted_thumbs'] = (int)($state[
'deleted_thumbs'] ?? 0) + 1;
2130 $state[
'phase'] =
'media_cleanup';
2131 return basename($rel);
2134 if ($type ===
'thumb') {
2135 $id = (int)($task[
'id'] ?? 0);
2136 $row = $id > 0 ?
$db->select1($this->dd_media, $id) : array();
2137 if (!is_array($row))
return '#' . $id;
2138 $rel = ltrim(str_replace(
'\\',
'/', (
string)($row[
'file_path'] ??
'')),
'/');
2139 $file = $this->file_from_media_rel($rel);
2140 if ($file ===
'' || !is_file($file) || !is_readable($file)) {
2141 $state[
'errors'] = (int)($state[
'errors'] ?? 0) + 1;
2142 return basename($rel);
2144 $name = basename($file);
2145 $meta = $this->media_file_meta($file, $name);
2146 $media_folder = $this->media_folder_from_path($rel, $meta[
'media_type']);
2147 $thumb = $this->create_media_thumbnail($file, $media_folder, $name, $meta[
'mime']);
2149 $db->update($this->dd_media, $thumb, $id);
2150 $state[
'created_thumbs'] = (int)($state[
'created_thumbs'] ?? 0) + 1;
2152 $state[
'errors'] = (int)($state[
'errors'] ?? 0) + 1;
2154 $state[
'phase'] =
'media_thumbs';
2155 return basename($rel);
2158 if ($type ===
'import') {
2159 $rel = ltrim(str_replace(
'\\',
'/', (
string)($task[
'rel'] ??
'')),
'/');
2160 $slot = $this->valid_media_slot($task[
'slot'] ?? $this->slot_from_media_rel($rel));
2162 $id = $this->import_media_file(
$db, $rel, $slot, $relinked);
2165 $state[
'relinked_media'] = (int)($state[
'relinked_media'] ?? 0) + 1;
2167 $state[
'imported_media'] = (int)($state[
'imported_media'] ?? 0) + 1;
2169 $row =
$db->select1($this->dd_media, $id);
2170 if (is_array($row) && !empty($row[
'thumb_file_path'])) {
2171 $state[
'created_thumbs'] = (int)($state[
'created_thumbs'] ?? 0) + 1;
2174 $state[
'errors'] = (int)($state[
'errors'] ?? 0) + 1;
2176 $state[
'phase'] =
'media_import';
2177 return basename($rel);
2180 if ($type ===
'content_cleanup') {
2181 $done = $this->clean_content_inline_media(
$db);
2182 $state[
'cleaned_inline_refs'] = (int)($state[
'cleaned_inline_refs'] ?? 0) + (int)($done[
'refs'] ?? 0);
2183 $state[
'cleaned_content_pages'] = (int)($state[
'cleaned_content_pages'] ?? 0) + (int)($done[
'pages'] ?? 0);
2184 $state[
'phase'] =
'content_cleanup';
2188 if ($type ===
'content_placeholder_repair') {
2189 $done = $this->repair_content_inline_placeholders(
$db);
2190 $state[
'repaired_inline_refs'] = (int)($state[
'repaired_inline_refs'] ?? 0) + (int)($done[
'refs'] ?? 0);
2191 $state[
'cleaned_content_pages'] = (int)($state[
'cleaned_content_pages'] ?? 0) + (int)($done[
'pages'] ?? 0);
2192 $state[
'phase'] =
'content_repair';
2196 if ($type ===
'content_cache_flush') {
2197 $ids = is_array($task[
'ids'] ??
null) ? $task[
'ids'] : array();
2199 foreach ($ids as $content_id) {
2200 $content_id = (int)$content_id;
2201 if ($content_id <= 0)
continue;
2202 $this->flush_saved_page_cache(
$db, $content_id);
2205 $state[
'flushed_content_pages'] = (int)($state[
'flushed_content_pages'] ?? 0) +
$count;
2206 $state[
'phase'] =
'content_cache';
2210 if ($type ===
'record') {
2211 $id = (int)($task[
'id'] ?? 0);
2212 $row = $id > 0 ?
$db->select1($this->dd_media, $id) : array();
2213 if (!is_array($row))
return '#' . $id;
2215 $rel = ltrim(str_replace(
'\\',
'/', (
string)($row[
'file_path'] ??
'')),
'/');
2216 $file = $this->file_from_media_rel($rel);
2217 if ($file ===
'' || !is_file($file) || !is_readable($file)) {
2218 $thumb = $this->source_thumb_file($row);
2219 if ($thumb !==
'' && @unlink($thumb)) {
2220 $state[
'deleted_thumbs'] = (int)($state[
'deleted_thumbs'] ?? 0) + 1;
2222 if ((
int)($row[
'active'] ?? 0) !== 0 || !empty($row[
'thumb_file_path'])) {
2223 $db->update($this->dd_media, array(
2225 'thumb_file_path' =>
'',
2227 'thumb_height' => 0,
2229 $state[
'deactivated_media'] = (int)($state[
'deactivated_media'] ?? 0) + 1;
2231 $state[
'phase'] =
'media_cleanup';
2232 return basename($rel);
2235 $slot = $this->slot_from_media_rel($rel, (
string)($row[
'slot'] ??
'gallery'));
2236 $name = basename($file);
2237 $meta = $this->media_file_meta($file, $name);
2239 if ((
int)($row[
'active'] ?? 0) !== 1
2240 || (
string)($row[
'slot'] ??
'') !== $slot
2241 || (
string)($row[
'mime'] ??
'') !== $meta[
'mime']
2242 || (
int)($row[
'size'] ?? 0) !== $meta[
'size']
2243 || (
string)($row[
'media_type'] ??
'') !== $meta[
'media_type']) {
2248 'mime' => $meta[
'mime'],
2249 'size' => $meta[
'size'],
2250 'width' => $meta[
'width'],
2251 'height' => $meta[
'height'],
2252 'media_type' => $meta[
'media_type'],
2256 if (empty($row[
'thumb_file_path']) || !$this->media_thumb_exists($row)) {
2257 $thumb = $this->create_media_thumbnail($file, $slot, $name, $meta[
'mime']);
2259 $update = array_merge($update, $thumb);
2260 $state[
'created_thumbs'] = (int)($state[
'created_thumbs'] ?? 0) + 1;
2264 if (!empty($update))
$db->update($this->dd_media, $update, $id);
2265 $state[
'phase'] =
'media_thumbs';
2266 return basename($rel);
2272 private function tick_media_process(
$db, array &$state) {
2273 if (($state[
'status'] ??
'') !==
'running')
return;
2274 $started = microtime(
true);
2278 $total = (int)($state[
'total'] ?? 0);
2280 while ((
int)($state[
'pos'] ?? 0) < $total && $processed < $limit && (microtime(
true) - $started) < 3.2) {
2281 $idx = (int)$state[
'pos'];
2282 $task = is_array($state[
'tasks'][$idx] ??
null) ? $state[
'tasks'][$idx] : array();
2283 $current = $this->run_media_process_task(
$db, $state, $task);
2284 $state[
'pos'] = $idx + 1;
2288 if ((
int)($state[
'pos'] ?? 0) >= $total) {
2289 $state[
'status'] =
'finished';
2290 $state[
'phase'] =
'media_done';
2291 $state[
'tasks'] = array();
2295 $this->update_media_process_percent($state);
2296 $state[
'message'] = $this->media_process_message($state, $current);
2297 if (($state[
'status'] ??
'') ===
'finished') $state[
'message'] =
'Fertig. ' . $state[
'message'];
2298 $state[
'updated_at'] = date(
'H:i:s');
2301 private function render_media_process(array $state, $next_url) {
2302 $status = (string)($state[
'status'] ??
'running');
2303 $percent = max(0, min(100, (
int)($state[
'percent'] ?? 0)));
2304 $step_percent = max(0, min(100, (
int)($state[
'step_percent'] ?? $percent)));
2305 $status_labels = array(
2306 'running' =>
'Laeuft',
2307 'paused' =>
'Angehalten',
2308 'canceled' =>
'Abgebrochen',
2309 'finished' =>
'Fertig',
2310 'error' =>
'Fehler',
2312 $status_class = $status ===
'finished' ?
'bg-success' : ($status ===
'error' || $status ===
'canceled' ?
'bg-danger' : ($status ===
'paused' ?
'bg-warning text-dark' :
'bg-primary'));
2313 $status_icon = $status ===
'finished' ?
'bi bi-check-lg' : ($status ===
'error' || $status ===
'canceled' ?
'bi bi-exclamation-triangle' : ($status ===
'paused' ?
'bi bi-pause-fill' :
'bi bi-play-fill'));
2314 $token = (string)($state[
'proc_key'] ??
'');
2315 $restart_url = $this->append_url_params($next_url, array(
'reset' => 1,
'proc_cmd' =>
'restart'));
2316 $cancel_url = $this->append_url_params($next_url, array(
'proc_cmd' =>
'cancel'));
2317 $resume_url = $this->append_url_params($next_url, array(
'proc_cmd' =>
'resume'));
2318 $pause_url = $this->append_url_params($next_url, array(
'proc_cmd' =>
'pause'));
2319 $autostart = ($status ===
'running' && $next_url !==
'') ? 1 : 0;
2320 $target_id =
'dbx_cms_media_process_' . substr(md5($token ?:
'media'), 0, 14);
2322 return '<div id="' . dbx()->esc($target_id) .
'"'
2323 .
' class="container-fluid dbx-process dbx-cms-media-process"'
2324 .
' data-dbx="lib=process|id=' . dbx()->esc($target_id) .
'|url=' . dbx()->esc($next_url) .
'|interval=900|autostart=' . $autostart .
'"'
2325 .
' data-process-status="' . dbx()->esc($status) .
'"'
2326 .
' data-process-percent="' . $percent .
'"'
2327 .
' data-process-step-percent="' . $step_percent .
'"'
2328 .
' data-process-next-url="' . dbx()->esc($next_url) .
'"'
2329 .
' data-process-pause-url="' . dbx()->esc($pause_url) .
'"'
2330 .
' data-process-resume-url="' . dbx()->esc($resume_url) .
'"'
2331 .
' data-process-cancel-url="' . dbx()->esc($cancel_url) .
'"'
2332 .
' data-process-restart-url="' . dbx()->esc($restart_url) .
'"'
2333 .
' data-process-autostart="' . $autostart .
'"'
2334 .
' data-process-interval="900">'
2335 .
'<div class="dbx-process-header">'
2336 .
'<h3 class="dbx-process-title">Medienwartung</h3>'
2337 .
'<span class="dbx-process-status badge ' . dbx()->esc($status_class) .
'"><i class="' . dbx()->esc($status_icon) .
'"></i> ' . dbx()->esc($status_labels[$status] ?? $status) .
'</span>'
2339 .
'<div class="dbx-process-grid">'
2340 .
'<div class="dbx-process-progress"><div class="dbx-process-progress-head"><span class="dbx-process-progress-label">Gesamt</span><span class="dbx-process-progress-value" data-process-percent="overall">' . $percent .
'%</span></div><div class="dbx-process-bar" role="progressbar" aria-valuenow="' . $percent .
'" aria-valuemin="0" aria-valuemax="100"><div class="dbx-process-bar-fill" data-process-bar="overall" style="width:' . $percent .
'%;"></div></div></div>'
2341 .
'<div class="dbx-process-progress"><div class="dbx-process-progress-head"><span class="dbx-process-progress-label">Thumbnails und verwaiste Dateien</span><span class="dbx-process-progress-value" data-process-percent="step">' . $step_percent .
'%</span></div><div class="dbx-process-bar" role="progressbar" aria-valuenow="' . $step_percent .
'" aria-valuemin="0" aria-valuemax="100"><div class="dbx-process-bar-fill" data-process-bar="step" style="width:' . $step_percent .
'%;"></div></div></div>'
2342 .
'<div class="dbx-process-message" data-process-message>' . dbx()->esc((
string)($state[
'message'] ??
'')) .
'</div>'
2343 .
'<div class="dbx-process-meta"><span>Eintraege: ' . (int)($state[
'pos'] ?? 0) .
' / ' . (int)($state[
'total'] ?? 0) .
'</span><span>Aktualisiert: ' . dbx()->esc((
string)($state[
'updated_at'] ??
'')) .
'</span></div>'
2345 .
'<div class="dbx-process-actions">'
2346 .
'<button type="button" class="btn btn-warning btn-sm" data-process-action="pause" data-process-visible="running" title="Anhalten"><i class="bi bi-pause-fill"></i></button>'
2347 .
'<button type="button" class="btn btn-primary btn-sm" data-process-action="resume" data-process-visible="paused" title="Weiter"><i class="bi bi-play-fill"></i></button>'
2348 .
'<button type="button" class="btn btn-secondary btn-sm" data-process-action="restart" data-process-visible="paused,canceled,error,finished" title="Neu starten"><i class="bi bi-arrow-clockwise"></i></button>'
2349 .
'<button type="button" class="btn btn-danger btn-sm" data-process-action="cancel" data-process-visible="running,paused" title="Abbrechen"><i class="bi bi-x-lg"></i></button>'
2354 private function append_url_params($url, $params = array()) {
2355 foreach ($params as $key => $value) {
2356 $url .= (strpos($url,
'?') ===
false ?
'?' :
'&') . rawurlencode((
string)$key) .
'=' . rawurlencode((
string)$value);
2361 private function media_process() {
2362 $db = dbx()->get_system_obj(
'dbxDB');
2363 $this->ensure_cms_schema(
$db);
2365 $token = preg_replace(
'/[^A-Za-z0-9_-]+/',
'', (
string)($_GET[
'proc_key'] ??
''));
2366 if ($token ===
'') $token = substr(md5(session_id() . microtime(
true)), 0, 16);
2367 $reset = (int)($_GET[
'reset'] ?? 0);
2368 $cmd = strtolower(preg_replace(
'/[^a-z_]+/',
'', (
string)($_GET[
'proc_cmd'] ??
'')));
2370 if ($reset || $cmd ===
'restart') {
2371 $state = $this->build_media_process_state(
$db, $token);
2373 $state = $this->get_media_process_state($token);
2374 if (empty($state)) $state = $this->build_media_process_state(
$db, $token);
2377 if ($cmd ===
'pause' && ($state[
'status'] ??
'') ===
'running') {
2378 $state[
'status'] =
'paused';
2379 $state[
'message'] =
'Medienwartung angehalten.';
2380 } elseif ($cmd ===
'resume' && ($state[
'status'] ??
'') ===
'paused') {
2381 $state[
'status'] =
'running';
2382 $state[
'message'] =
'Medienwartung fortgesetzt.';
2383 } elseif ($cmd ===
'cancel') {
2384 $state[
'status'] =
'canceled';
2385 $state[
'message'] =
'Medienwartung abgebrochen.';
2388 $this->tick_media_process(
$db, $state);
2389 $this->set_media_process_state($token, $state);
2391 $next = $this->base_url(
'cms_media_process', array(
'proc_key' => $token));
2392 return $this->render_media_process($state, $next);
2395 private function first_upload_file() {
2396 if (empty($_FILES) || !is_array($_FILES))
return array();
2398 foreach ($_FILES as $file) {
2399 if (!is_array($file))
continue;
2401 if (isset($file[
'tmp_name']) && !is_array($file[
'tmp_name'])) {
2405 if (!isset($file[
'tmp_name']) || !is_array($file[
'tmp_name']))
continue;
2407 $idx = array_key_first($file[
'tmp_name']);
2408 if ($idx ===
null)
continue;
2411 'name' => is_array($file[
'name'] ??
null) ? ($file[
'name'][$idx] ??
'') : ($file[
'name'] ??
''),
2412 'type' => is_array($file[
'type'] ??
null) ? ($file[
'type'][$idx] ??
'') : ($file[
'type'] ??
''),
2413 'tmp_name' => $file[
'tmp_name'][$idx] ??
'',
2414 'error' => is_array($file[
'error'] ??
null) ? ($file[
'error'][$idx] ?? UPLOAD_ERR_NO_FILE) : ($file[
'error'] ?? UPLOAD_ERR_NO_FILE),
2415 'size' => is_array($file[
'size'] ??
null) ? ($file[
'size'][$idx] ?? 0) : ($file[
'size'] ?? 0),
2422 private function load_gd_image($file, $mime) {
2423 if (!extension_loaded(
'gd'))
return false;
2424 $mime = strtolower((
string)$mime);
2425 if ((strpos($mime,
'jpeg') !==
false || preg_match(
'/\.jpe?g$/i', $file)) && function_exists(
'imagecreatefromjpeg'))
return @imagecreatefromjpeg($file);
2426 if ((strpos($mime,
'png') !==
false || preg_match(
'/\.png$/i', $file)) && function_exists(
'imagecreatefrompng'))
return @imagecreatefrompng($file);
2427 if (strpos($mime,
'webp') !==
false || preg_match(
'/\.webp$/i', $file))
return function_exists(
'imagecreatefromwebp') ? @imagecreatefromwebp($file) : false;
2428 if ((strpos($mime,
'gif') !==
false || preg_match(
'/\.gif$/i', $file)) && function_exists(
'imagecreatefromgif'))
return @imagecreatefromgif($file);
2432 private function save_gd_image($img, $file, $mime) {
2433 if (!extension_loaded(
'gd'))
return false;
2434 $mime = strtolower((
string)$mime);
2435 if (strpos($mime,
'png') !==
false || preg_match(
'/\.png$/i', $file)) {
2436 if (!function_exists(
'imagepng'))
return false;
2437 imagealphablending($img,
false);
2438 imagesavealpha($img,
true);
2439 return @imagepng($img, $file, 6);
2441 if ((strpos($mime,
'webp') !==
false || preg_match(
'/\.webp$/i', $file)) && function_exists(
'imagewebp')) {
2442 return @imagewebp($img, $file, 86);
2444 if (!function_exists(
'imagejpeg'))
return false;
2445 return @imagejpeg($img, $file, 88);
2448 private function convert_media_image_to_webp($file, $name, $mime =
'') {
2449 if (!extension_loaded(
'gd') || !function_exists(
'imagewebp'))
return false;
2450 if ($file ===
'' || !is_file($file) || !is_readable($file))
return false;
2451 if (preg_match(
'/\.(webp|gif|svg)$/i', (
string)$name))
return false;
2453 $mime_l = strtolower((
string)$mime);
2454 $ext = strtolower(pathinfo((
string)$name, PATHINFO_EXTENSION));
2455 $is_supported = in_array($ext, array(
'jpg',
'jpeg',
'png'),
true)
2456 || strpos($mime_l,
'jpeg') !==
false
2457 || strpos($mime_l,
'png') !==
false;
2458 if (!$is_supported)
return false;
2460 $image = $this->load_gd_image($file, $mime);
2461 if (!$image)
return false;
2463 imagepalettetotruecolor($image);
2464 imagealphablending($image,
true);
2465 imagesavealpha($image,
true);
2467 $webp_name = preg_replace(
'/\.[^.]+$/',
'', basename((
string)$name)) .
'.webp';
2468 $webp_file = rtrim(dirname($file),
'/\\') . DIRECTORY_SEPARATOR . $webp_name;
2469 if (is_file($webp_file)) {
2470 $webp_name = $this->unique_media_name($webp_name);
2471 $webp_file = rtrim(dirname($file),
'/\\') . DIRECTORY_SEPARATOR . $webp_name;
2474 $ok = @imagewebp($image, $webp_file, 86);
2475 imagedestroy($image);
2476 if (!$ok || !is_file($webp_file))
return false;
2480 'file' => $webp_file,
2481 'name' => $webp_name,
2482 'mime' =>
'image/webp',
2486 private function resize_image_to_max($file, $mime, $max_long_side = 2560) {
2487 $max_long_side = max(800, min(6000, (
int)$max_long_side));
2488 if ($file ===
'' || preg_match(
'/\.(svg|gif)$/i', (
string)$file))
return false;
2490 $size = @getimagesize($file);
2491 if (!is_array($size))
return false;
2493 $src_w = (int)($size[0] ?? 0);
2494 $src_h = (int)($size[1] ?? 0);
2495 if ($src_w <= 0 || $src_h <= 0 || max($src_w, $src_h) <= $max_long_side)
return false;
2497 $op = array(
'action' =>
'resize');
2498 if ($src_w >= $src_h) {
2499 $op[
'width'] = $max_long_side;
2503 $op[
'height'] = $max_long_side;
2506 return $this->edit_image_file($file, $mime, $op);
2509 private function create_media_thumbnail($src, $slot, $name, $mime =
'') {
2510 $type = $this->media_type(array(
'mime' => $mime,
'file_name' => $name));
2511 if ($type ===
'video')
return $this->create_video_thumbnail($src, $slot, $name);
2512 if ($type !==
'image' || preg_match(
'/\.svg$/i', (
string)$name) || !function_exists(
'imagecreatetruecolor'))
return array();
2514 $image = $this->load_gd_image($src, $mime);
2515 if (!$image)
return array();
2517 $src_w = imagesx($image);
2518 $src_h = imagesy($image);
2519 if ($src_w <= 0 || $src_h <= 0) {
2520 imagedestroy($image);
2526 $scale = min($max_w / $src_w, $max_h / $src_h, 1);
2527 $dst_w = max(1, (
int)round($src_w * $scale));
2528 $dst_h = max(1, (
int)round($src_h * $scale));
2529 $thumb = imagecreatetruecolor($dst_w, $dst_h);
2530 imagealphablending($thumb,
false);
2531 imagesavealpha($thumb,
true);
2532 imagefill($thumb, 0, 0, imagecolorallocatealpha($thumb, 255, 255, 255, 127));
2533 imagecopyresampled($thumb, $image, 0, 0, 0, 0, $dst_w, $dst_h, $src_w, $src_h);
2535 $dir = $this->cms_media_thumb_dir($slot);
2536 if (!is_dir(
$dir)) @mkdir(
$dir, 0777,
true);
2537 $thumb_ext = function_exists(
'imagewebp') ?
'webp' :
'jpg';
2538 $thumb_name = preg_replace(
'/\.[^.]+$/',
'', basename((
string)$name)) .
'.' . $thumb_ext;
2539 $thumb_name = $this->unique_media_name($thumb_name,
'thumb');
2540 $dst = rtrim(
$dir,
'/\\') . DIRECTORY_SEPARATOR . $thumb_name;
2541 $ok = $thumb_ext ===
'webp' ? @imagewebp($thumb, $dst, 82) : @imagejpeg($thumb, $dst, 82);
2542 imagedestroy($thumb);
2543 imagedestroy($image);
2545 if (!$ok)
return array();
2547 'thumb_file_path' => $this->media_thumb_rel_dir($slot) . $thumb_name,
2548 'thumb_width' => $dst_w,
2549 'thumb_height' => $dst_h,
2553 private function create_video_thumbnail($src, $slot, $name) {
2554 $ffmpeg = dbx()->find_executable_path(
'ffmpeg');
2556 $dir = $this->cms_media_thumb_dir($slot);
2557 if (!is_dir(
$dir)) @mkdir(
$dir, 0777,
true);
2558 $thumb_name = $this->unique_media_name(preg_replace(
'/\.[^.]+$/',
'', basename((
string)$name)) .
'.jpg',
'video-thumb');
2559 $dst = rtrim(
$dir,
'/\\') . DIRECTORY_SEPARATOR . $thumb_name;
2561 if ($ffmpeg !==
'') {
2562 $cmd =
'"' . $ffmpeg .
'" -y -ss 00:00:01 -i "' . $src .
'" -frames:v 1 -vf "scale=640:-1" "' . $dst .
'" 2>NUL';
2566 if (!is_file($dst) || !filesize($dst)) {
2567 $this->create_video_placeholder_thumbnail($dst, $name);
2570 if (!is_file($dst) || !filesize($dst))
return array();
2571 $size = @getimagesize($dst);
2573 'thumb_file_path' => $this->media_thumb_rel_dir($slot) . $thumb_name,
2574 'thumb_width' => is_array($size) ? (
int)$size[0] : 0,
2575 'thumb_height' => is_array($size) ? (
int)$size[1] : 0,
2579 private function create_video_placeholder_thumbnail($dst, $name) {
2580 if (!function_exists(
'imagecreatetruecolor'))
return false;
2583 $img = imagecreatetruecolor($w, $h);
2584 $bg = imagecolorallocate($img, 30, 43, 60);
2585 $panel = imagecolorallocate($img, 52, 72, 96);
2586 $white = imagecolorallocate($img, 255, 255, 255);
2587 $muted = imagecolorallocate($img, 188, 202, 220);
2588 imagefill($img, 0, 0, $bg);
2589 imagefilledrectangle($img, 20, 20, $w - 20, $h - 20, $panel);
2590 $cx = (int)($w / 2);
2591 $cy = (int)($h / 2) - 12;
2592 imagefilledpolygon($img, array($cx - 38, $cy - 48, $cx - 38, $cy + 48, $cx + 52, $cy), 3, $white);
2593 $label = substr(basename((
string)$name), 0, 54);
2594 imagestring($img, 3, 28, $h - 42, $label, $muted);
2595 $ok = @imagejpeg($img, $dst, 82);
2600 private function source_thumb_file($row) {
2601 $rel = ltrim(str_replace(
'\\',
'/', (
string)($row[
'thumb_file_path'] ??
'')),
'/');
2602 if ($rel ===
'' || strpos($rel,
'..') !==
false)
return '';
2606 $real = realpath($file);
2607 if (!
$base || !$real || strpos($real,
$base) !== 0 || !is_file($real) || !is_readable($real))
return '';
2611 private function render_cms() {
2612 $this->clear_cms_tpl_cache();
2613 $oTPL = dbx()->get_system_obj(
'dbxTPL');
2614 $db = dbx()->get_system_obj(
'dbxDB');
2615 $this->ensure_cms_schema(
$db);
2616 $tree = $this->cms_tree();
2617 $page_count = is_array($tree[
'items'] ??
null) ? count($tree[
'items']) : 0;
2618 $folder_count = is_array($tree[
'folders'] ??
null) ? count($tree[
'folders']) : 0;
2619 $active_count =
$db->count(dbxContentLng::ddContent(),
'activ = 1');
2620 if ($active_count < 0) $active_count = 0;
2622 $cms_cid = $this->resolve_cms_page_id();
2623 $cms_fid = (int)dbx()->get_modul_var(
'fid', 0,
'int');
2624 if ($cms_fid <= 0) {
2625 $cms_fid = (int)dbx()->get_modul_var(
'dbx_fid', 0,
'int');
2629 'i' => dbx()->next_id(),
2630 'cms_cid' => (
string)$cms_cid,
2631 'cms_fid' => (
string)$cms_fid,
2632 'title' =>
'Content CMS',
2633 'subtitle' =>
'Seiten, Ordner, Rechte und Medien zentral bearbeiten.',
2634 'count_pages' => (
string)$page_count,
2635 'count_folders' => (
string)$folder_count,
2636 'count_active' => (
string)$active_count,
2637 'tree_url' => dbx()->esc($this->base_url(
'cms_tree')),
2638 'page_url' => dbx()->esc($this->base_url(
'cms_page')),
2639 'save_url' => dbx()->esc($this->base_url(
'cms_save')),
2640 'delete_page_url' => dbx()->esc($this->base_url(
'cms_delete_page')),
2641 'new_page_url' => dbx()->esc($this->base_url(
'cms_new_page')),
2642 'duplicate_page_url' => dbx()->esc($this->base_url(
'cms_duplicate_page')),
2643 'new_folder_url' => dbx()->esc($this->base_url(
'cms_new_folder')),
2644 'media_url' => dbx()->esc($this->base_url(
'cms_media')),
2645 'media_process_url' => dbx()->esc($this->base_url(
'cms_media_process')),
2646 'media_folders_url' => dbx()->esc($this->base_url(
'cms_media_folders')),
2647 'media_folder_create_url' => dbx()->esc($this->base_url(
'cms_media_folder_create')),
2648 'media_folder_delete_url' => dbx()->esc($this->base_url(
'cms_media_folder_delete')),
2649 'media_folder_rename_url' => dbx()->esc($this->base_url(
'cms_media_folder_rename')),
2650 'media_move_url' => dbx()->esc($this->base_url(
'cms_media_move')),
2651 'media_unused_url' => dbx()->esc($this->base_url(
'cms_media_unused')),
2652 'upload_url' => dbx()->esc($this->base_url(
'cms_upload')),
2653 'external_video_url' => dbx()->esc($this->base_url(
'cms_external_video')),
2654 'upload_max_bytes' => (
string)$this->upload_max_bytes(),
2655 'remove_media_url' => dbx()->esc($this->base_url(
'cms_remove_media')),
2656 'delete_media_url' => dbx()->esc($this->base_url(
'cms_delete_media')),
2657 'edit_media_url' => dbx()->esc($this->base_url(
'cms_edit_media')),
2658 'set_media_slot_url' => dbx()->esc($this->base_url(
'cms_set_media_slot')),
2659 'assign_media_url' => dbx()->esc($this->base_url(
'cms_assign_media')),
2660 'sort_media_url' => dbx()->esc($this->base_url(
'cms_sort_media')),
2661 'mod_catalog_url' => dbx()->esc($this->base_url(
'cms_mod_catalog')),
2662 'mod_modules_url' => dbx()->esc($this->base_url(
'cms_mod_modules')),
2663 'folder_save_url' => dbx()->esc($this->base_url(
'cms_save_folder')),
2664 'folder_delete_url' => dbx()->esc($this->base_url(
'cms_delete_folder')),
2665 'move_node_url' => dbx()->esc($this->base_url(
'cms_move_node')),
2666 'lng_coverage_url' => dbx()->esc($this->base_url(
'cms_lng_coverage')),
2667 'lng_preview_url' => dbx()->esc($this->base_url(
'cms_lng_preview')),
2668 'lng_provision_url' => dbx()->esc($this->base_url(
'cms_lng_provision')),
2669 'lng_provision_tree_url' => dbx()->esc($this->base_url(
'cms_lng_provision_tree')),
2670 'lng_reset_sync_url' => dbx()->esc($this->base_url(
'cms_lng_reset_sync')),
2671 'lng_delete_preview_url' => dbx()->esc($this->base_url(
'cms_lng_delete_preview')),
2672 'current_lng' => dbxContentLng::current(),
2673 'master_lng' => dbxContentLngSync::masterLng(),
2674 'lng_bar' => dbxContentLngSync::renderLngBar(),
2675 'template_options' => $this->template_options(),
2676 'rights_options' => $this->rights_options(),
2677 'media_template_options' => $this->media_template_options(),
2678 'hero_template_options' => $this->hero_template_options(),
2679 'hero_variant_options' => $this->hero_variant_options(),
2680 'gallery_template_options' => $this->gallery_template_options(),
2681 'gallery_overflow_options' => $this->gallery_overflow_options(),
2682 'gallery_click_options' => $this->gallery_click_options(),
2683 'page_form' => $this->render_page_form(),
2684 'folder_form' => $this->render_folder_form(),
2685 'settings_form' => $this->render_settings_form(),
2686 'dbx_search' => dbx()->search_html(dbx()->search_defaults(array(
2687 'title' =>
'Tree durchsuchen',
2688 'extra_attrs' =>
'data-cms-search',
2693 $data = array_merge($data, dbx()->get_include_obj(
'dbxAdminHelp',
'dbxAdmin')->vars(
'content'));
2694 $data[
'bar_class'] =
'dbx-module-bar dbx-cms-head';
2695 $data[
'bar_title_class'] =
'dbx-module-bar-titleblock';
2696 $data[
'bar_actions_class'] =
'dbx-module-bar-actions flex-wrap';
2697 if (trim((
string)($data[
'bar_subtitle'] ??
'')) ===
'' && trim((
string)($data[
'subtitle'] ??
'')) !==
'') {
2698 $data[
'bar_subtitle'] = (string)$data[
'subtitle'];
2701 return $oTPL->get_tpl(
'dbxContent_admin|cms-admin', $data);
2705 $modul = preg_replace(
'/[^A-Za-z0-9_]+/',
'', (
string)$modul);
2706 if ($modul ===
'')
return array();
2709 $main =
$base . $modul .
'.class.php';
2710 if (is_file($main)) $files[] = $main;
2711 $inc =
$base .
'include' . DIRECTORY_SEPARATOR;
2713 foreach (glob($inc .
'*.class.php') ?: array() as $path) {
2714 if (is_file($path)) $files[] = $path;
2725 $src = @file_get_contents($file);
2726 if (!is_string($src) || $src ===
'')
continue;
2727 if (preg_match(
"/get_modul_var\s*\(\s*['\"]dbx_run2['\"]/", $src)) {
2730 if (preg_match_all(
"/case\s+['\"]([^'\"]+)['\"]\s*:/", $src, $matches)) {
2731 foreach ($matches[1] as $case) {
2732 $case = trim((
string)$case);
2733 if ($case ===
'' || $case ===
'default')
continue;
2734 $run1[$case] =
true;
2737 if (preg_match_all(
'/\$(?:run|action|work)\s*===?\s*[\'"]([^\'"]+)[\'"]/', $src, $ifs)) {
2738 foreach ($ifs[1] as $case) {
2739 $case = trim((
string)$case);
2740 if ($case ===
'')
continue;
2741 $run1[$case] =
true;
2744 if (preg_match_all(
"/get_modul_var\s*\(\s*['\"]dbx_run1['\"][^)]*['\"]([a-zA-Z0-9_]+)['\"]/", $src, $defaults)) {
2745 foreach ($defaults[1] as $case) {
2746 $case = trim((
string)$case);
2747 if ($case ===
'' || $case ===
'parameter')
continue;
2748 $run1[$case] =
true;
2751 if (preg_match_all(
"/get_modul_var\s*\(\s*['\"]dbx_run2['\"][^)]*['\"]([^'\"]+)['\"]/", $src, $defaults)) {
2752 foreach ($defaults[1] as $val) {
2753 $val = trim((
string)$val);
2754 if ($val !==
'') $run2[$val] =
true;
2758 $run1_list = array_keys($run1);
2759 usort($run1_list,
function($a, $b) {
2760 return strlen($b) <=> strlen($a);
2763 'run1' => array_values(array_keys($run1)),
2764 'run2' => array_values(array_keys($run2)),
2765 'uses_run2' => $uses_run2,
2766 'run1_sorted' => $run1_list,
2770 private function mod_modules_json() {
2771 $base = dbx()->os_path(dbx()->get_base_dir() .
'dbx/modules/dbxAdmin/include/');
2772 require_once
$base .
'dbxModuleImages.class.php';
2773 $images = new \dbx\dbxAdmin\dbxModuleImages();
2778 foreach (glob(
$dir .
'*', GLOB_ONLYDIR) ?: array() as $path) {
2779 $name = basename(str_replace(
'\\',
'/', $path));
2780 if (!preg_match(
'/^[A-Za-z][A-Za-z0-9_]*$/', $name))
continue;
2781 $class_file =
dbx_os_path_file($path . DIRECTORY_SEPARATOR . $name .
'.class.php');
2782 if (!is_file($class_file))
continue;
2784 $count = $images->imageCount($name);
2792 'uses_run2' => !empty($scan[
'uses_run2']) ? 1 : 0,
2793 'run1_actions' => array_slice($scan[
'run1'], 0, 24),
2797 usort($items,
function($a, $b) {
2798 return strcmp((
string)($a[
'label'] ??
''), (
string)($b[
'label'] ??
''));
2800 $this->cms_json_response(array(
'ok' => 1,
'items' => $items));
2803 private function mod_catalog_json() {
2804 $base = dbx()->os_path(dbx()->get_base_dir() .
'dbx/modules/dbxAdmin/include/');
2805 require_once
$base .
'dbxModuleImages.class.php';
2806 $images = new \dbx\dbxAdmin\dbxModuleImages();
2808 $modul = preg_replace(
'/[^A-Za-z0-9_]+/',
'', (
string)dbx()->get_modul_var(
'modul',
'',
'parameter'));
2809 if ($modul ===
'') {
2810 $this->cms_json_response(array(
'ok' => 0,
'msg' =>
'Modul fehlt.',
'items' => array()));
2814 if (!is_file($class_file)) {
2815 $this->cms_json_response(array(
'ok' => 0,
'msg' =>
'Modul nicht gefunden.',
'items' => array()));
2819 $out = $images->catalogForModul($modul);
2822 $this->cms_json_response(array(
2825 'uses_run2' => !empty($scan[
'uses_run2']) ? 1 : 0,
2826 'run1_actions' => $scan[
'run1'],
2827 'items' => is_array($out) ? $out : array(),
2831 private function tree_json() {
2832 $this->cms_json_response(array(
'ok' => 1) + $this->cms_tree());
2835 private function lng_coverage_json() {
2836 $type = trim((
string) dbx()->get_modul_var(
'type',
'page'));
2837 $type = $type ===
'folder' ?
'folder' :
'page';
2838 $id = (int) dbx()->get_modul_var(
'id', 0,
'int');
2839 $lngUid = trim((
string) dbx()->get_modul_var(
'lng_uid',
''));
2840 $db = dbx()->get_system_obj(
'dbxDB');
2841 $this->ensure_cms_schema(
$db);
2843 if ($lngUid ===
'' && $id > 0) {
2845 $lngUid = dbxContentLngSync::ensureRecordUid(
$db, $dd, $id, $type ===
'folder' ?
'f' :
'p');
2848 $coverage = dbxContentLngSync::coverageForUid(
$db, $type, $lngUid);
2849 $this->cms_json_response(array(
'ok' => 1,
'coverage' => $coverage));
2852 private function lng_preview_json() {
2853 if (!dbxContentLngSync::isMasterLng()) {
2854 $this->cms_json_response(array(
'ok' => 0,
'msg' =>
'Vorschau nur in der Master-Sprache moeglich.'));
2857 $payload = $this->request_json();
2858 $type = (($payload[
'type'] ??
'page') ===
'folder') ?
'folder' :
'page';
2859 $id = (int) ($payload[
'id'] ?? 0);
2860 $lngs = is_array($payload[
'lngs'] ??
null) ? $payload[
'lngs'] : array();
2861 $db = dbx()->get_system_obj(
'dbxDB');
2862 $this->ensure_cms_schema(
$db);
2864 dbxContentTranslate::clearWarnings();
2865 $preview = dbxContentLngSync::previewProvision(
$db, $type, $id, $lngs);
2866 $this->cms_json_response(array(
2868 'preview' => $preview,
2869 'provider' => dbxContentTranslate::provider(),
2870 'translate_warnings' => dbxContentTranslate::warnings(),
2874 private function lng_provision_json() {
2875 if (!dbxContentLngSync::isMasterLng()) {
2876 $this->cms_json_response(array(
'ok' => 0,
'msg' =>
'Uebertragung nur in der Master-Sprache moeglich.'));
2879 $payload = $this->request_json();
2880 $type = (($payload[
'type'] ??
'page') ===
'folder') ?
'folder' :
'page';
2881 $id = (int) ($payload[
'id'] ?? 0);
2882 $items = is_array($payload[
'items'] ??
null) ? $payload[
'items'] : array();
2883 $db = dbx()->get_system_obj(
'dbxDB');
2884 $this->ensure_cms_schema(
$db);
2886 dbxContentTranslate::clearWarnings();
2887 $result = dbxContentLngSync::provisionFromPreview(
$db, $type, $id, $items);
2888 if ((
int) (
$result[
'ok'] ?? 0) === 1) {
2889 $result[
'media_copied'] = $this->apply_lng_provision_media(
$db, $type, $id,
$result);
2890 $this->flush_menu_cache();
2892 $result[
'translate_warnings'] = dbxContentTranslate::warnings();
2893 $this->cms_json_response(array(
'ok' => (
int) (
$result[
'ok'] ?? 0),
'result' =>
$result));
2896 private function lng_provision_tree_json() {
2897 if (!dbxContentLngSync::isMasterLng()) {
2898 $this->cms_json_response(array(
'ok' => 0,
'msg' =>
'Unterbaum-Uebertragung nur in der Master-Sprache moeglich.'));
2901 $payload = $this->request_json();
2902 $id = (int) ($payload[
'id'] ?? 0);
2903 $lngs = is_array($payload[
'lngs'] ??
null) ? $payload[
'lngs'] : array();
2905 $this->cms_json_response(array(
'ok' => 0,
'msg' =>
'Kein Ordner gewaehlt.'));
2908 $db = dbx()->get_system_obj(
'dbxDB');
2909 $this->ensure_cms_schema(
$db);
2910 dbxContentTranslate::clearWarnings();
2911 $result = dbxContentLngSync::provisionFolderTree(
$db, $id, $lngs);
2912 if ((
int) (
$result[
'ok'] ?? 0) === 1) {
2914 foreach (is_array(
$result[
'pages'] ??
null) ?
$result[
'pages'] : array() as $pageItem) {
2915 if (!is_array($pageItem)) {
2918 $masterPageId = (int) ($pageItem[
'master_id'] ?? 0);
2919 $prov = is_array($pageItem[
'result'] ??
null) ? $pageItem[
'result'] : array();
2920 if ($masterPageId > 0) {
2921 $mediaCopied += $this->apply_lng_provision_media(
$db,
'page', $masterPageId, $prov);
2924 $result[
'media_copied'] = $mediaCopied;
2925 $this->flush_menu_cache();
2927 $result[
'translate_warnings'] = dbxContentTranslate::warnings();
2928 $this->cms_json_response(array(
'ok' => (
int) (
$result[
'ok'] ?? 0),
'result' =>
$result));
2931 private function lng_reset_sync_json() {
2932 if (!dbxContentLngSync::isMasterLng()) {
2933 $this->cms_json_response(array(
'ok' => 0,
'msg' =>
'Auto-Sync nur in der Master-Sprache setzbar.'));
2936 $payload = $this->request_json();
2937 $type = (($payload[
'type'] ??
'page') ===
'folder') ?
'folder' :
'page';
2938 $id = (int) ($payload[
'id'] ?? 0);
2939 $lngs = is_array($payload[
'lngs'] ??
null) ? $payload[
'lngs'] : array();
2941 $this->cms_json_response(array(
'ok' => 0,
'msg' =>
'Kein Datensatz gewaehlt.'));
2944 $db = dbx()->get_system_obj(
'dbxDB');
2945 $this->ensure_cms_schema(
$db);
2946 $result = dbxContentLngSync::resetSyncToAuto(
$db, $type, $id, $lngs);
2947 $this->cms_json_response(array(
'ok' => count(
$result[
'updated'] ?? array()) ? 1 : 0,
'result' =>
$result));
2950 private function lng_delete_preview_json() {
2951 if (!dbxContentLngSync::isMasterLng()) {
2952 $this->cms_json_response(array(
'ok' => 0,
'msg' =>
'Mehrsprachige Loesch-Vorschau nur in der Master-Sprache moeglich.'));
2955 $payload = $this->request_json();
2956 $type = (($payload[
'type'] ??
'page') ===
'folder') ?
'folder' :
'page';
2957 $id = (int) ($payload[
'id'] ?? 0);
2959 $this->cms_json_response(array(
'ok' => 0,
'msg' =>
'Kein Datensatz gewaehlt.'));
2962 $db = dbx()->get_system_obj(
'dbxDB');
2963 $this->ensure_cms_schema(
$db);
2964 $preview = dbxContentLngSync::previewDelete(
$db, $type, $id);
2965 $this->cms_json_response(array(
'ok' => 1,
'preview' => $preview));
2968 private function normalize_delete_lngs(array $payload): array {
2970 $raw = $payload[
'delete_lngs'] ??
null;
2971 if (!is_array($raw) || !count($raw)) {
2972 return array($current);
2974 if (!dbxContentLngSync::isMasterLng()) {
2975 return array($current);
2978 $allowed = dbxContentLngSync::accessibleLngs();
2980 foreach ($raw as $lng) {
2981 $lng = strtolower(trim((
string) $lng));
2982 if ($lng !==
'' && in_array($lng, $allowed,
true) && !in_array($lng, $out,
true)) {
2987 return count($out) ? $out : array($current);
2990 private function delete_page_in_lngs(
$db,
int $id, array $deleteLngs): array {
2995 foreach ($targets as $target) {
2996 if (!is_array($target)) {
2999 $lng = (string) ($target[
'lng'] ??
'');
3000 $targetId = (int) ($target[
'id'] ?? 0);
3001 if ($targetId <= 0) {
3005 $targetDd = dbxContentLng::ddContent($lng);
3006 $ok =
$db->delete($targetDd,
'id = ' . $targetId, 1, 0);
3008 $db->update($this->dd_media, array(
'active' => 1,
'content_id' => 0,
'folder_id' => 0),
'content_id = ' . $targetId, 0, 1, 1, 0);
3009 $db->update($this->dd_media_usage, array(
'active' => 0),
'content_id = ' . $targetId .
' AND active = 1', 0, 1, 1, 0);
3010 $this->flush_deleted_page_cache($targetId, $lng);
3011 $deleted[] = array(
'lng' => $lng,
'id' => $targetId);
3013 $errors[] =
'Loeschen in ' . strtoupper($lng) .
' fehlgeschlagen.';
3018 'ok' => count($deleted) ? 1 : 0,
3019 'deleted' => $deleted,
3020 'errors' => $errors,
3026 return array(
'ok' => 0,
'deleted' => array(),
'errors' => array(
'Keine Seite gewaehlt.'));
3029 $db =
dbx()->get_system_obj(
'dbxDB');
3030 $this->ensure_cms_schema(
$db);
3031 $result = $this->delete_page_in_lngs(
$db, $id, array(dbxContentLng::current()));
3033 if ((
int)(
$result[
'ok'] ?? 0) === 1) {
3034 $this->flush_menu_cache();
3042 return array(
'ok' => 0,
'deleted' => array(),
'errors' => array(
'Kein Ordner gewaehlt.'));
3045 $db =
dbx()->get_system_obj(
'dbxDB');
3046 $this->ensure_cms_schema(
$db);
3047 $result = $this->delete_folder_in_lngs(
$db, $id, array(dbxContentLng::current()));
3049 if ((
int)(
$result[
'ok'] ?? 0) === 1) {
3050 $this->flush_menu_cache();
3058 return array(
'ok' => 0,
'errors' => array(
'Keine Medien-ID erhalten.'));
3061 $db =
dbx()->get_system_obj(
'dbxDB');
3062 $this->ensure_cms_schema(
$db);
3063 $used =
$db->select(
3064 $this->dd_media_usage,
3065 'media_id = ' . $id .
' AND active = 1',
3074 if (is_array($used) && isset($used[0])) {
3075 return array(
'ok' => 0,
'errors' => array(
'Medium wird noch verwendet.'));
3078 foreach (dbxContentLngSync::accessibleLngs() as $lng) {
3079 $contentDd = dbxContentLng::ddContent((
string)$lng);
3080 $folderDd = dbxContentLng::ddFolder((
string)$lng);
3081 if (
$db->count($contentDd,
'hero_image_id = ' . $id) > 0 ||
$db->count($folderDd,
'hero_image_id = ' . $id) > 0) {
3082 return array(
'ok' => 0,
'errors' => array(
'Medium wird noch verwendet.'));
3084 $pages =
$db->select($contentDd,
'',
'content',
'id',
'ASC',
'', 0, 0, 0);
3085 foreach (is_array($pages) ? $pages : array() as $page) {
3086 if (preg_match(
'/data-cms-media-id=["\']?' . $id .
'(?:["\'\s>]|$)/i', (
string)($page[
'content'] ??
''))) {
3087 return array(
'ok' => 0,
'errors' => array(
'Medium wird noch verwendet.'));
3092 $row =
$db->select1($this->dd_media, $id);
3093 if (!is_array($row) || (
int)($row[
'active'] ?? 0) !== 1) {
3094 return array(
'ok' => 0,
'errors' => array(
'Medium nicht gefunden.'));
3097 if (strtolower((
string)($row[
'storage_type'] ??
'local')) ===
'local') {
3098 $file = $this->source_media_file($row);
3103 $file = $this->source_media_file($row);
3104 if ($file !==
'' && preg_match(
'/\.json$/i', $file)) {
3108 $thumb = $this->source_thumb_file($row);
3109 if ($thumb !==
'') {
3113 $ok =
$db->update($this->dd_media, array(
'active' => 0), $id);
3115 'ok' => $ok >= 0 ? 1 : 0,
3116 'errors' => $ok >= 0 ? array() : array(
'Medium konnte nicht geloescht werden.'),
3120 private function delete_folder_in_lngs(
$db,
int $id, array $deleteLngs): array {
3121 $targets = dbxContentLngSync::resolveDeleteIds(
$db,
'folder', $id, $deleteLngs);
3125 foreach ($targets as $target) {
3126 if (!is_array($target)) {
3129 $lng = (string) ($target[
'lng'] ??
'');
3130 $targetId = (int) ($target[
'id'] ?? 0);
3131 if ($targetId <= 0) {
3135 $check = dbxContentLngSync::folderDeletable(
$db, $lng, $targetId);
3136 if ((
int) (
$check[
'deletable'] ?? 0) !== 1) {
3137 $reason = trim((
string) (
$check[
'reason'] ??
''));
3138 $errors[] = strtoupper($lng) . ($reason !==
'' ?
': ' . $reason :
': Ordner kann nicht geloescht werden.');
3142 $targetDd = dbxContentLng::ddFolder($lng);
3143 $ok =
$db->delete($targetDd,
'id = ' . $targetId, 1, 0);
3145 $this->flush_deleted_folder_cache(
$db, $targetId, $lng);
3146 $deleted[] = array(
'lng' => $lng,
'id' => $targetId);
3148 $errors[] =
'Loeschen in ' . strtoupper($lng) .
' fehlgeschlagen.';
3153 'ok' => count($deleted) ? 1 : 0,
3154 'deleted' => $deleted,
3155 'errors' => $errors,
3159 private function page_json() {
3160 $id = (int)
dbx()->get_modul_var(
'id', 0,
'int');
3162 $id = $this->resolve_cms_page_id();
3164 $db =
dbx()->get_system_obj(
'dbxDB');
3165 $this->ensure_cms_schema(
$db);
3166 $row = $id > 0 ?
$db->select1(dbxContentLng::ddContent(), $id) :
$db->empty_record(dbxContentLng::ddContent())[0];
3167 if (is_array($row) && isset($row[
'content'])) {
3168 $row[
'content'] = $this->normalize_content_media_urls($row[
'content']);
3170 if (is_array($row)) {
3171 $row = $this->normalize_gallery_row($row);
3173 if ($id > 0 && is_array($row)) {
3174 $this->sync_inline_media_usage(
$db, $id, (
string)($row[
'content'] ??
''), (
int)($row[
'folder'] ?? 0));
3176 $media = $id > 0 ? $this->media_usage_rows_for_context(
$db, $id, 0) : array();
3177 $this->cms_json_response(array(
3181 'hero_preview_media' => $this->hero_preview_media(
$db, $row),
3182 'hero_parent_preview_media' => $this->inherited_hero_preview_media(
$db, (
int)($row[
'folder'] ?? 0)),
3183 'seo_preview_media' => $this->seo_preview_media(
$db, $row),
3187 private function hero_preview_media(
$db, $row) {
3188 if (!is_array($row))
return array();
3189 $hero_id = (int)($row[
'hero_image_id'] ?? 0);
3190 if ($hero_id <= 0)
return array();
3191 $hero =
$db->select1($this->dd_media, $hero_id);
3192 if (!is_array($hero) || (
int)($hero[
'active'] ?? 0) !== 1)
return array();
3193 if (!$this->media_file_exists($hero))
return array();
3194 return $this->normalize_media_row($hero);
3197 private function inherited_hero_preview_media(
$db, $folder_id) {
3198 $folder_id = (int)$folder_id;
3200 while ($folder_id > 0 && !isset($seen[$folder_id])) {
3201 $seen[$folder_id] = 1;
3202 $folder =
$db->select1(dbxContentLng::ddFolder(), $folder_id,
'*', 0);
3203 if (!is_array($folder))
return array();
3204 $hero_template = trim((
string)($folder[
'hero_template'] ??
'parent'));
3205 if ($this->is_no_hero_template($hero_template))
return array();
3206 $hero_value = trim((
string)($folder[
'hero_image_id'] ??
'parent'));
3207 $hero_id = (int)$hero_value;
3209 $hero =
$db->select1($this->dd_media, $hero_id);
3210 if (!is_array($hero) || (
int)($hero[
'active'] ?? 0) !== 1 || !$this->media_file_exists($hero))
return array();
3211 $hero = $this->normalize_media_row($hero);
3212 $hero[
'parent_folder_id'] = $folder_id;
3213 $hero[
'parent_folder_name'] = (string)($folder[
'name'] ??
'');
3216 if ($hero_value ===
'0' || strtolower($hero_value) ===
'none')
return array();
3217 $folder_id = (int)($folder[
'parent_id'] ?? 0);
3222 private function seo_preview_media(
$db, $row) {
3223 if (!is_array($row))
return array();
3224 $seo_id = (int)($row[
'seo_image_id'] ?? 0);
3225 if ($seo_id <= 0)
return array();
3226 $media =
$db->select1($this->dd_media, $seo_id);
3227 if (!is_array($media) || (
int)($media[
'active'] ?? 0) !== 1)
return array();
3228 if (!$this->media_file_exists($media))
return array();
3229 return $this->normalize_media_row($media);
3232 private function save_json() {
3233 $payload = $this->request_json();
3234 $id = (int)($payload[
'id'] ?? 0);
3235 $db =
dbx()->get_system_obj(
'dbxDB');
3236 $this->ensure_cms_schema(
$db);
3238 $title = $this->clean_text($payload[
'title'] ??
'', 254);
3239 $folder = (int)($payload[
'folder'] ?? 0);
3241 'activ' => $this->bool_int($payload[
'activ'] ?? 1, 1),
3242 'folder' => $folder,
3244 'permalink' => $this->page_permalink(
$db, $folder, $title, $payload[
'permalink'] ??
''),
3245 'description' => $this->clean_text($payload[
'description'] ??
'', 254),
3246 'template' => $this->clean_text($payload[
'template'] ??
'parent', 254),
3247 'hero_template' => $this->clean_text($payload[
'hero_template'] ??
'parent', 80),
3248 'hero_image_id' => $this->clean_text($payload[
'hero_image_id'] ??
'parent', 32),
3249 'hero_margin_top' => $this->clean_text($payload[
'hero_margin_top'] ??
'parent', 32),
3250 'hero_height' => $this->clean_text($payload[
'hero_height'] ??
'parent', 32),
3251 'hero_variant' => $this->clean_text($payload[
'hero_variant'] ??
'parent', 32),
3252 'hero_sticky' => $this->clean_text($payload[
'hero_sticky'] ??
'parent', 32),
3253 'hero_scroll_layer' => $this->clean_text($payload[
'hero_scroll_layer'] ??
'parent', 32),
3254 'gallery_template' =>
'image-gallery',
3255 'gallery_visible_count' =>
'3',
3256 'gallery_image_size' => $this->clean_text($payload[
'gallery_image_size'] ??
'original', 32),
3257 'gallery_lightbox_width' => $this->clean_text($payload[
'gallery_lightbox_width'] ??
'100vw', 32),
3258 'gallery_overflow' => $this->clean_text($payload[
'gallery_overflow'] ??
'grid', 32),
3259 'gallery_click_behavior' => $this->clean_text($payload[
'gallery_click_behavior'] ??
'lightbox', 32),
3260 'content' => $this->normalize_and_sanitize_content($payload[
'content'] ??
''),
3262 $data = $this->normalize_hero_payload($data);
3265 $existingSeo =
$db->select1(dbxContentLng::ddContent(), $id,
'keywords,meta_robots,seo_title,seo_image_id', 0);
3266 $data[
'keywords'] = is_array($existingSeo) ? (string)($existingSeo[
'keywords'] ??
'') :
'';
3267 $data[
'meta_robots'] = is_array($existingSeo) ? (string)($existingSeo[
'meta_robots'] ??
'index,follow') :
'index,follow';
3268 $data[
'seo_title'] = is_array($existingSeo) ? (string)($existingSeo[
'seo_title'] ??
'') :
'';
3269 $data[
'seo_image_id'] = is_array($existingSeo) ? max(0, (
int)($existingSeo[
'seo_image_id'] ?? 0)) : 0;
3271 $data[
'keywords'] =
'';
3272 $data[
'meta_robots'] =
'index,follow';
3273 $data[
'seo_title'] =
'';
3274 $data[
'seo_image_id'] = 0;
3278 $ok =
$db->update(dbxContentLng::ddContent(), $data, $id);
3281 $ok =
$db->insert(dbxContentLng::ddContent(), $data);
3282 $saved_id = ($ok === 1) ?
$db->get_insert_id() : 0;
3286 $this->sync_saved_hero_media_usage(
$db, $saved_id, 0, $data[
'hero_image_id']);
3287 dbxContentLngSync::afterPageSave(
$db, $saved_id, $id <= 0);
3288 $syncResult = array(
'updated' => array(),
'skipped' => array(),
'errors' => array());
3289 if ($id > 0 && dbxContentLngSync::isMasterLng()) {
3290 dbxContentTranslate::clearWarnings();
3291 $syncResult = dbxContentLngSync::syncSlavesFromMaster(
$db,
'page', $saved_id);
3292 $syncResult[
'media_copied'] = $this->apply_lng_sync_media(
$db, $saved_id, $syncResult);
3293 $syncResult[
'translate_warnings'] = dbxContentTranslate::warnings();
3294 $this->flush_lng_sync_cache(
$db, $syncResult);
3296 $inline_provided = array_key_exists(
'inline_media_ids', $payload) && is_array($payload[
'inline_media_ids']);
3297 $inline_sync = $this->sync_inline_media_usage(
3302 $inline_provided ? $payload[
'inline_media_ids'] :
null,
3305 $this->flush_saved_page_cache(
$db, $saved_id);
3306 $media = $this->media_usage_rows_for_context(
$db, $saved_id, 0);
3307 $saved_row =
$db->select1(dbxContentLng::ddContent(), $saved_id);
3308 if (is_array($saved_row)) {
3309 if (isset($saved_row[
'content'])) {
3310 $saved_row[
'content'] = $this->normalize_content_media_urls($saved_row[
'content']);
3312 $saved_row = $this->normalize_gallery_row($saved_row);
3314 $this->cms_json_response(array(
3318 'row' => $saved_row,
3320 'inline_media_sync' => $inline_sync,
3321 'hero_preview_media' => $this->hero_preview_media(
$db, $saved_row),
3322 'hero_parent_preview_media' => $this->inherited_hero_preview_media(
$db, (
int)($saved_row[
'folder'] ?? 0)),
3323 'seo_preview_media' => $this->seo_preview_media(
$db, $saved_row),
3324 'lng_forked' => dbxContentLngSync::isMasterLng() ? 0 : 1,
3325 'lng_synced' => count($syncResult[
'updated'] ?? array()),
3326 'lng_media_copied' => (
int) ($syncResult[
'media_copied'] ?? 0),
3327 'translate_warnings' => is_array($syncResult[
'translate_warnings'] ??
null) ? $syncResult[
'translate_warnings'] : array(),
3328 'open_lng_provision' => $this->lng_provision_open_flag(
$db,
'page', $saved_id),
3332 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Seite konnte nicht gespeichert werden.',
'db' => $ok));
3335 private function new_page_json() {
3336 $folder = (int)
dbx()->get_modul_var(
'folder', 0,
'int');
3337 $db =
dbx()->get_system_obj(
'dbxDB');
3338 $this->ensure_cms_schema(
$db);
3339 $title =
'Neue Seite ' . date(
'H:i');
3340 $sorter = $this->next_content_sorter(
$db, $folder);
3342 if (
$db->insert(dbxContentLng::ddContent(), array(
3344 'folder' => $folder,
3346 'permalink' => dbxContent_permalink::build(
$db, dbxContentLng::ddFolder(), $folder, $title),
3347 'template' =>
'parent',
3348 'hero_template' =>
'parent',
3349 'hero_image_id' =>
'parent',
3350 'hero_margin_top' =>
'parent',
3351 'hero_height' =>
'parent',
3352 'hero_variant' =>
'parent',
3353 'hero_sticky' =>
'parent',
3354 'hero_scroll_layer' =>
'parent',
3355 'gallery_template' =>
'image-gallery',
3356 'gallery_visible_count' =>
'3',
3357 'gallery_image_size' =>
'original',
3358 'gallery_lightbox_width' =>
'100vw',
3359 'gallery_overflow' =>
'grid',
3360 'gallery_click_behavior' =>
'lightbox',
3361 'description' =>
'',
3363 'meta_robots' =>
'index,follow',
3365 'seo_image_id' => 0,
3366 'sorter' => $sorter,
3367 'content' =>
'<p>Neue Seite bearbeiten...</p>',
3368 ), 0, 1, 0, 1) === 1) {
3369 $id =
$db->get_insert_id();
3372 dbxContentLngSync::afterPageSave(
$db, $id,
true);
3373 $this->flush_menu_cache();
3374 $this->cms_json_response(array(
3378 'row' =>
$db->select1(dbxContentLng::ddContent(), $id),
3379 'open_lng_provision' => $this->lng_provision_open_flag(
$db,
'page', $id),
3382 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Seite konnte nicht angelegt werden.',
'db' => $id));
3385 private function duplicate_page_json() {
3386 $payload = $this->request_json();
3387 $sourceId = (int)($payload[
'id'] ??
dbx()->get_modul_var(
'id', 0,
'int'));
3388 if ($sourceId <= 0) {
3389 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Bitte zuerst eine Seite auswaehlen.'));
3392 $db =
dbx()->get_system_obj(
'dbxDB');
3393 $this->ensure_cms_schema(
$db);
3394 $contentDd = dbxContentLng::ddContent();
3395 if (
$db->begin($contentDd) !== 1) {
3396 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Seite konnte nicht fuer die Duplizierung gesperrt werden.'));
3399 $source =
$db->select1($contentDd, $sourceId,
'*', 0);
3400 if (!is_array($source) || (
int)($source[
'id'] ?? 0) !== $sourceId) {
3401 $db->rollback($contentDd);
3402 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Die ausgewaehlte Seite wurde nicht gefunden.'));
3407 'id',
'create_date',
'create_uid',
'update_date',
'update_uid',
'owner',
3408 'hits',
'xvote',
'vote',
'vote1',
'vote2',
'vote3',
'vote4',
'vote5',
'lastuservote'
3413 $folderId = (int)($source[
'folder'] ?? 0);
3414 $title = (string)($source[
'title'] ??
'Unbenannte Seite');
3415 $copy[
'sorter'] = $this->next_content_sorter(
$db, $folderId);
3416 $copy[
'permalink'] = $this->duplicate_page_permalink(
3420 (
string)($source[
'permalink'] ??
'')
3422 $copy[
'lng_uid'] =
'';
3423 $copy[
'lng_sync'] = dbxContentLngSync::isMasterLng() ?
'auto' :
'manual';
3424 $copy[
'lng_rev'] = 1;
3425 $copy[
'lng_synced_rev'] = 0;
3427 $inserted =
$db->insert($contentDd, $copy, 0, 1, 0, 1);
3428 $newId = $inserted === 1 ? (int)
$db->get_insert_id() : 0;
3430 $db->rollback($contentDd);
3431 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Seite konnte nicht dupliziert werden.',
'db' => $inserted));
3434 dbxContentLngSync::afterPageSave(
$db, $newId,
true);
3435 if (
$db->commit($contentDd) !== 1) {
3436 $db->rollback($contentDd);
3437 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Die duplizierte Seite konnte nicht abgeschlossen werden.'));
3440 $mediaCopied = $this->copy_page_media_usage(
$db, $sourceId, $newId, $folderId,
true);
3441 $inlineSync = $this->sync_inline_media_usage(
3444 (
string)($source[
'content'] ??
''),
3447 $this->flush_saved_page_cache(
$db, $newId);
3449 $row =
$db->select1($contentDd, $newId);
3450 if (is_array($row) && isset($row[
'content'])) {
3451 $row[
'content'] = $this->normalize_content_media_urls($row[
'content']);
3452 $row = $this->normalize_gallery_row($row);
3455 $this->cms_json_response(array(
3459 'source_id' => $sourceId,
3461 'permalink' => (
string)($row[
'permalink'] ?? $copy[
'permalink']),
3462 'media_copied' => $mediaCopied,
3463 'inline_media_sync' => $inlineSync,
3464 'open_lng_provision' => $this->lng_provision_open_flag(
$db,
'page', $newId),
3465 'msg' =>
'Seite wurde dupliziert.',
3469 private function next_content_sorter(
$db, $folder_id) {
3470 $folder_id = (int)$folder_id;
3471 $rows =
$db->select(dbxContentLng::ddContent(),
'folder = ' . $folder_id,
'*',
'sorter,id',
'DESC',
'', 1, 0, 0);
3473 if (is_array($rows) && isset($rows[0]) && is_array($rows[0])) {
3474 $max = (int)($rows[0][
'sorter'] ?? 0);
3476 return sprintf(
'%04d', $max + 10);
3479 private function new_folder_json() {
3480 $parent = (int)
dbx()->get_modul_var(
'parent', 0,
'int');
3481 $db =
dbx()->get_system_obj(
'dbxDB');
3482 $this->ensure_cms_schema(
$db);
3483 $sorter = $this->next_folder_sorter(
$db, $parent);
3485 if (
$db->insert(dbxContentLng::ddFolder(), array(
3486 'name' =>
'Neuer Ordner ' . date(
'H:i'),
3487 'parent_id' => $parent,
3488 'sorter' => $sorter,
3489 'group_read' => $parent > 0 ?
'parent' :
'*',
3490 'template' => $parent > 0 ?
'parent' :
'c-content',
3491 'hero_template' => $parent > 0 ?
'parent' :
'image-hero',
3492 'hero_image_id' =>
'parent',
3493 'hero_margin_top' =>
'parent',
3494 'hero_height' =>
'parent',
3495 'hero_variant' =>
'parent',
3496 'hero_sticky' =>
'parent',
3497 'hero_scroll_layer' =>
'parent',
3499 $id =
$db->get_insert_id();
3502 dbxContentLngSync::afterFolderSave(
$db, $id,
true);
3503 $this->flush_menu_cache();
3504 $this->cms_json_response(array(
3508 'row' =>
$db->select1(dbxContentLng::ddFolder(), $id),
3509 'open_lng_provision' => $this->lng_provision_open_flag(
$db,
'folder', $id),
3512 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Ordner konnte nicht angelegt werden.'));
3515 private function save_folder_json() {
3516 $payload = $this->request_json();
3517 $id = (int)($payload[
'id'] ?? 0);
3518 $db =
dbx()->get_system_obj(
'dbxDB');
3519 $this->ensure_cms_schema(
$db);
3521 $name = $this->clean_text($payload[
'name'] ??
'', 120);
3522 $parent_id = (int)($payload[
'parent_id'] ?? 0);
3524 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Bitte eine Bezeichnung eintragen.'));
3526 if ($parent_id < 0) $parent_id = 0;
3527 if ($id > 0 && $parent_id === $id) {
3528 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Ordner kann nicht sein eigener Parent sein.'));
3530 if ($id > 0 && $this->folder_is_descendant(
$db, $parent_id, $id)) {
3531 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Ordner kann nicht unter einen eigenen Unterordner gelegt werden.'));
3534 $rights = $this->clean_text($payload[
'group_read'] ??
'', 512);
3535 if ($rights ===
'') $rights = $parent_id > 0 ?
'parent' :
'*';
3537 $old = $id > 0 ?
$db->select1(dbxContentLng::ddFolder(), $id,
'*', 0) : array();
3540 'parent_id' => $parent_id,
3541 'group_read' => $rights,
3542 'template' => $this->clean_text($payload[
'template'] ??
'parent', 254),
3543 'hero_template' => $this->clean_text($payload[
'hero_template'] ??
'parent', 80),
3544 'hero_image_id' => $this->clean_text($payload[
'hero_image_id'] ??
'parent', 32),
3545 'hero_margin_top' => $this->clean_text($payload[
'hero_margin_top'] ??
'parent', 32),
3546 'hero_height' => $this->clean_text($payload[
'hero_height'] ??
'parent', 32),
3547 'hero_variant' => $this->clean_text($payload[
'hero_variant'] ??
'parent', 32),
3548 'hero_sticky' => $this->clean_text($payload[
'hero_sticky'] ??
'parent', 32),
3549 'hero_scroll_layer' => $this->clean_text($payload[
'hero_scroll_layer'] ??
'parent', 32),
3551 $data = $this->normalize_hero_payload($data);
3552 if ($id <= 0 || (is_array($old) && (
int)($old[
'parent_id'] ?? 0) !== $parent_id)) {
3553 $data[
'sorter'] = $this->next_folder_sorter(
$db, $parent_id);
3557 $ok =
$db->update(dbxContentLng::ddFolder(), $data, $id);
3560 $ok =
$db->insert(dbxContentLng::ddFolder(), $data);
3561 $saved_id = ($ok === 1) ?
$db->get_insert_id() : 0;
3564 if ($ok === 1 && $saved_id > 0) {
3565 $this->sync_saved_hero_media_usage(
$db, 0, $saved_id, $data[
'hero_image_id']);
3566 dbxContentLngSync::afterFolderSave(
$db, $saved_id, $id <= 0);
3567 $syncResult = array(
'updated' => array(),
'skipped' => array(),
'errors' => array());
3568 if ($id > 0 && dbxContentLngSync::isMasterLng()) {
3569 dbxContentTranslate::clearWarnings();
3570 $syncResult = dbxContentLngSync::syncSlavesFromMaster(
$db,
'folder', $saved_id);
3571 $syncResult[
'translate_warnings'] = dbxContentTranslate::warnings();
3572 $this->flush_lng_sync_cache(
$db, $syncResult);
3574 $this->flush_folder_cache(
$db, $saved_id);
3575 if (is_array($old) && (
int)($old[
'parent_id'] ?? 0) !== $parent_id) {
3576 $this->flush_folder_cache(
$db, (
int)($old[
'parent_id'] ?? 0));
3578 $this->cms_json_response(array(
3582 'row' =>
$db->select1(dbxContentLng::ddFolder(), $saved_id),
3583 'lng_forked' => dbxContentLngSync::isMasterLng() ? 0 : 1,
3584 'lng_synced' => count($syncResult[
'updated'] ?? array()),
3585 'translate_warnings' => is_array($syncResult[
'translate_warnings'] ??
null) ? $syncResult[
'translate_warnings'] : array(),
3586 'open_lng_provision' => $this->lng_provision_open_flag(
$db,
'folder', $saved_id),
3589 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Ordner konnte nicht gespeichert werden.'));
3592 private function delete_folder_json() {
3593 $payload = $this->request_json();
3594 $id = (int)($payload[
'id'] ?? 0);
3596 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Kein Ordner gewaehlt.'));
3599 $db =
dbx()->get_system_obj(
'dbxDB');
3600 $deleteLngs = $this->normalize_delete_lngs($payload);
3601 $result = $this->delete_folder_in_lngs(
$db, $id, $deleteLngs);
3603 if ((
int)(
$result[
'ok'] ?? 0) === 1) {
3604 $this->cms_json_response(array(
3608 'deleted' =>
$result[
'deleted'] ?? array(),
3609 'warnings' =>
$result[
'errors'] ?? array(),
3613 $msg = count(
$result[
'errors'] ?? array()) ? implode(
' ',
$result[
'errors']) :
'Ordner konnte nicht geloescht werden.';
3614 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' => $msg));
3617 private function delete_page_json() {
3618 $payload = $this->request_json();
3619 $id = (int)($payload[
'id'] ??
dbx()->get_modul_var(
'id', 0,
'int'));
3621 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Keine Seite gewaehlt.'));
3624 $db =
dbx()->get_system_obj(
'dbxDB');
3625 $deleteLngs = $this->normalize_delete_lngs($payload);
3626 $result = $this->delete_page_in_lngs(
$db, $id, $deleteLngs);
3628 if ((
int)(
$result[
'ok'] ?? 0) === 1) {
3629 $this->flush_menu_cache();
3630 $this->cms_json_response(array(
3634 'deleted' =>
$result[
'deleted'] ?? array(),
3635 'warnings' =>
$result[
'errors'] ?? array(),
3639 $msg = count(
$result[
'errors'] ?? array()) ? implode(
' ',
$result[
'errors']) :
'Seite konnte nicht geloescht werden.';
3640 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' => $msg));
3643 private function move_node_json() {
3644 $payload = $this->request_json();
3645 $type = $this->clean_text($payload[
'type'] ??
'', 16);
3646 $id = (int)($payload[
'id'] ?? 0);
3647 $target = (int)($payload[
'target_folder'] ?? 0);
3648 $before_id = (int)($payload[
'before_id'] ?? 0);
3649 $after_id = (int)($payload[
'after_id'] ?? 0);
3651 if ($id <= 0 || $target < 0 || !in_array($type, array(
'folder',
'page'),
true)) {
3652 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Ungueltige Tree-Verschiebung.'));
3655 if ($type ===
'folder' && $id === $target) {
3656 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Ordner kann nicht in sich selbst verschoben werden.'));
3659 $db =
dbx()->get_system_obj(
'dbxDB');
3660 if ($type ===
'folder') {
3661 if ($this->folder_is_descendant(
$db, $target, $id)) {
3662 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Ordner kann nicht in einen eigenen Unterordner verschoben werden.'));
3664 $data = array(
'parent_id' => $target);
3665 if ($before_id > 0 || $after_id > 0) {
3666 $data[
'sorter'] = $this->next_folder_sorter(
$db, $target);
3668 $current =
$db->select1(dbxContentLng::ddFolder(), $id,
'*', 0);
3669 if (is_array($current) && (
int)($current[
'parent_id'] ?? 0) !== $target) {
3670 $data[
'sorter'] = $this->next_folder_sorter(
$db, $target);
3673 $ok =
$db->update(dbxContentLng::ddFolder(), $data, $id);
3674 if ($ok >= 0 && ($before_id > 0 || $after_id > 0)) {
3675 $this->reorder_folders(
$db, $target, $id, $before_id, $after_id);
3678 $data = array(
'folder' => $target);
3679 if ($before_id <= 0 && $after_id <= 0) {
3680 $current =
$db->select1(dbxContentLng::ddContent(), $id,
'*', 0);
3681 if (is_array($current) && (
int)($current[
'folder'] ?? 0) !== $target) {
3682 $data[
'sorter'] = $this->next_content_sorter(
$db, $target);
3685 $ok =
$db->update(dbxContentLng::ddContent(), $data, $id);
3686 if ($ok >= 0 && ($before_id > 0 || $after_id > 0)) {
3687 $this->reorder_content_pages(
$db, $target, $id, $before_id, $after_id);
3692 if ($type ===
'page') {
3693 $this->flush_saved_page_cache(
$db, $id);
3695 $this->flush_folder_cache(
$db, $id);
3696 $this->flush_folder_cache(
$db, $target);
3698 $this->cms_json_response(array(
'ok' => 1,
'success' =>
true,
'id' => $id,
'type' => $type,
'target_folder' => $target));
3701 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Tree-Eintrag konnte nicht verschoben werden.'));
3704 private function folder_is_descendant(
$db, $folder_id, $ancestor_id) {
3705 $folder_id = (int)$folder_id;
3706 $ancestor_id = (int)$ancestor_id;
3708 while ($folder_id > 0 && $guard < 100) {
3709 if ($folder_id === $ancestor_id)
return true;
3710 $row =
$db->select1(dbxContentLng::ddFolder(), $folder_id);
3711 if (!is_array($row))
return false;
3712 $folder_id = (int)($row[
'parent_id'] ?? 0);
3718 private function next_folder_sorter(
$db, $parent_id) {
3719 $parent_id = (int)$parent_id;
3720 $rows =
$db->select(dbxContentLng::ddFolder(),
'parent_id = ' . $parent_id,
'*',
'sorter,id',
'DESC',
'', 1, 0, 0);
3722 if (is_array($rows) && isset($rows[0]) && is_array($rows[0])) {
3723 $max = (int)($rows[0][
'sorter'] ?? 0);
3725 return sprintf(
'%04d', $max + 10);
3728 private function reorder_folders(
$db, $parent_id, $moved_id, $before_id = 0, $after_id = 0) {
3729 $parent_id = (int)$parent_id;
3730 $moved_id = (int)$moved_id;
3731 $rows =
$db->select(dbxContentLng::ddFolder(),
'parent_id = ' . $parent_id,
'*',
'sorter,name,id',
'ASC');
3732 if (!is_array($rows))
return;
3736 foreach ($rows as $row) {
3737 if (!is_array($row))
continue;
3738 $rid = (int)($row[
'id'] ?? 0);
3739 if ($rid === $moved_id) {
3745 if (!$moved)
return;
3749 foreach ($ordered as $row) {
3750 $rid = (int)($row[
'id'] ?? 0);
3751 if ($before_id > 0 && $rid === $before_id) {
3756 if ($after_id > 0 && $rid === $after_id) {
3761 if (!$inserted) $out[] = $moved;
3764 foreach ($out as $row) {
3765 $rid = (int)($row[
'id'] ?? 0);
3766 if ($rid <= 0)
continue;
3767 $db->update(dbxContentLng::ddFolder(), array(
'sorter' => sprintf(
'%04d', $sort)), $rid);
3772 private function reorder_content_pages(
$db, $folder_id, $moved_id, $before_id = 0, $after_id = 0) {
3773 $folder_id = (int)$folder_id;
3774 $moved_id = (int)$moved_id;
3775 $rows =
$db->select(dbxContentLng::ddContent(),
'folder = ' . $folder_id,
'*',
'sorter,title,id',
'ASC');
3776 if (!is_array($rows))
return;
3780 foreach ($rows as $row) {
3781 if (!is_array($row))
continue;
3782 $rid = (int)($row[
'id'] ?? 0);
3783 if ($rid === $moved_id) {
3789 if (!$moved)
return;
3793 foreach ($ordered as $row) {
3794 $rid = (int)($row[
'id'] ?? 0);
3795 if ($before_id > 0 && $rid === $before_id) {
3800 if ($after_id > 0 && $rid === $after_id) {
3805 if (!$inserted) $out[] = $moved;
3809 foreach ($out as $row) {
3810 $rid = (int)($row[
'id'] ?? 0);
3811 if ($rid <= 0)
continue;
3812 $sorter = sprintf(
'%04d', $sort);
3813 if ((
string)($row[
'sorter'] ??
'') !== $sorter) {
3814 $db->update(dbxContentLng::ddContent(), array(
'sorter' => $sorter), $rid);
3819 foreach ($changed as $rid) {
3820 dbxContentPageCache::invalidateContent((
int)$rid);
3822 if (count($changed)) {
3823 dbxContentPageCache::invalidateAllMenus();
3827 private function media_usage_page_map(
$db, $usage_rows) {
3829 if (!is_array($usage_rows))
return $map;
3831 $page_cache = array();
3832 $folder_cache = array();
3834 foreach ($usage_rows as $usage) {
3835 if (!is_array($usage))
continue;
3836 $media_id = (int)($usage[
'media_id'] ?? 0);
3837 $content_id = (int)($usage[
'content_id'] ?? 0);
3838 if ($media_id <= 0 || $content_id <= 0)
continue;
3840 if (!array_key_exists($content_id, $page_cache)) {
3841 $page_cache[$content_id] =
$db->select1(dbxContentLng::ddContent(), $content_id,
'id,folder,title', 0);
3843 $page = is_array($page_cache[$content_id]) ? $page_cache[$content_id] : array();
3844 $folder_id = (int)($page[
'folder'] ?? ($usage[
'folder_id'] ?? 0));
3846 if ($folder_id > 0 && !array_key_exists($folder_id, $folder_cache)) {
3847 $folder_cache[$folder_id] =
$db->select1(dbxContentLng::ddFolder(), $folder_id,
'*', 0);
3849 $folder = ($folder_id > 0 && is_array($folder_cache[$folder_id] ??
null)) ? $folder_cache[$folder_id] : array();
3851 if (!isset($map[$media_id])) $map[$media_id] = array();
3852 if (!isset($map[$media_id][$content_id])) {
3853 $map[$media_id][$content_id] = array(
3854 'id' => $content_id,
3855 'content_id' => $content_id,
3856 'title' => (
string)($page[
'title'] ??
''),
3857 'folder_id' => $folder_id,
3858 'folder_title' => (
string)($folder[
'name'] ?? $folder[
'title'] ??
''),
3863 $slot = trim((
string)($usage[
'slot'] ??
''));
3864 if ($slot !==
'' && !in_array($slot, $map[$media_id][$content_id][
'slots'],
true)) {
3865 $map[$media_id][$content_id][
'slots'][] = $slot;
3869 foreach ($map as $media_id => $items) {
3870 ksort($items, SORT_NUMERIC);
3871 $map[$media_id] = array_values($items);
3877 private function media_json() {
3878 $content_id = (int)
dbx()->get_modul_var(
'content_id', 0,
'int');
3879 $folder_id = (int)
dbx()->get_modul_var(
'folder_id', 0,
'int');
3880 $images = (int)
dbx()->get_modul_var(
'images', 0,
'int');
3881 $sync = (int)
dbx()->get_modul_var(
'sync', 0,
'int');
3882 $media_type = strtolower(trim((
string)
dbx()->get_modul_var(
'media_type',
'',
'varchar')));
3883 $raw_media_folder = trim((
string)
dbx()->get_modul_var(
'media_folder',
'',
'varchar'));
3884 $media_folder = $raw_media_folder !==
'' && strtolower($raw_media_folder) !==
'all'
3885 ? $this->clean_media_folder($raw_media_folder, $media_type ?: $this->media_type_from_folder($raw_media_folder))
3887 $slot = trim((
string)
dbx()->get_modul_var(
'slot',
'',
'varchar'));
3888 $query = trim((
string)
dbx()->get_modul_var(
'query',
'',
'varchar'));
3889 $usage_only = (int)
dbx()->get_modul_var(
'usage', 0,
'int');
3890 $db =
dbx()->get_system_obj(
'dbxDB');
3891 $this->ensure_cms_schema(
$db);
3892 if ($sync) $this->sync_cms_media_files(
$db);
3893 $where =
'active = 1' . $this->cms_media_exclude_sql();
3895 $where .=
" AND (mime LIKE 'image/%' OR file_name LIKE '%.jpg' OR file_name LIKE '%.jpeg' OR file_name LIKE '%.png' OR file_name LIKE '%.gif' OR file_name LIKE '%.webp' OR file_name LIKE '%.svg')";
3897 if (in_array($media_type, array(
'image',
'video',
'external_video',
'file'),
true)) {
3898 $where .=
" AND media_type = '" . str_replace(
"'",
"''", $media_type) .
"'";
3900 if ($media_folder !==
'') {
3901 $where .=
" AND media_folder = '" . str_replace(
"'",
"''", $media_folder) .
"'";
3903 if ($query !==
'') {
3904 $q = str_replace(
"'",
"''", $query);
3905 $where .=
" AND (title LIKE '%$q%' OR alt LIKE '%$q%' OR caption LIKE '%$q%' OR tags LIKE '%$q%' OR file_name LIKE '%$q%')";
3907 $rows =
$db->select($this->dd_media, $where,
'*',
'media_folder,title,id');
3908 if (!is_array($rows)) $rows = array();
3909 $rows = $this->filter_existing_media(
$db, $rows);
3910 $usage_rows =
$db->select($this->dd_media_usage,
'active = 1',
'*',
'media_id,id',
'ASC',
'', 0, 0, 0);
3911 $usage_count = array();
3912 $usage_pages = $this->media_usage_page_map(
$db, $usage_rows);
3913 $current_usage = array();
3914 $current_usage_row = array();
3915 if (is_array($usage_rows)) {
3916 foreach ($usage_rows as $usage) {
3917 if (!is_array($usage))
continue;
3918 $mid = (int)($usage[
'media_id'] ?? 0);
3919 if ($mid <= 0)
continue;
3920 $usage_count[$mid] = ($usage_count[$mid] ?? 0) + 1;
3921 if (($content_id <= 0 || (
int)($usage[
'content_id'] ?? 0) === $content_id)
3922 && ($folder_id <= 0 || (
int)($usage[
'folder_id'] ?? 0) === $folder_id)
3923 && ($slot ===
'' || (
string)($usage[
'slot'] ??
'') === $slot)) {
3924 $current_usage[$mid] = (int)($usage[
'id'] ?? 0);
3925 $current_usage_row[$mid] = $usage;
3929 $rows = array_map(
function($row) use ($usage_count, $usage_pages, $current_usage, $current_usage_row) {
3930 $row = $this->normalize_media_row($row);
3931 $id = (int)($row[
'id'] ?? 0);
3932 $row[
'used_count'] = (int)($usage_count[$id] ?? 0);
3933 $row[
'usage_pages'] = $usage_pages[$id] ?? array();
3934 $row[
'current_usage_id'] = (int)($current_usage[$id] ?? 0);
3935 if (!empty($current_usage_row[$id])) {
3936 $usage = $current_usage_row[$id];
3937 $row[
'usage_id'] = (int)($usage[
'id'] ?? 0);
3938 $row[
'slot'] = (string)($usage[
'slot'] ?? $row[
'slot'] ??
'gallery');
3939 $row[
'sorter'] = (string)($usage[
'sorter'] ?? $row[
'sorter'] ??
'');
3940 if (!empty($usage[
'template'])) $row[
'template'] = $usage[
'template'];
3941 if (!empty($usage[
'caption'])) $row[
'caption'] = $usage[
'caption'];
3945 if ($usage_only && ($content_id > 0 || $folder_id > 0 || $slot !==
'')) {
3946 $rows = array_values(array_filter($rows,
function($row) {
3947 return (
int)($row[
'current_usage_id'] ?? 0) > 0;
3950 $this->cms_json_response(array(
'ok' => 1,
'rows' => $rows));
3953 private function media_usage_rows_for_context(
$db, $content_id = 0, $folder_id = 0, $slot =
'') {
3954 $content_id = (int)$content_id;
3955 $folder_id = (int)$folder_id;
3956 $where =
'active = 1';
3957 if ($content_id > 0) $where .=
' AND content_id = ' . $content_id;
3958 if ($folder_id > 0) $where .=
' AND folder_id = ' . $folder_id;
3959 if ($slot !==
'') $where .=
" AND slot = '" . str_replace(
"'",
"''", (
string)$slot) .
"'";
3961 $usage_rows =
$db->select($this->dd_media_usage, $where,
'*',
'slot,sorter,id',
'ASC',
'', 0, 0, 0);
3962 if (!is_array($usage_rows))
return array();
3965 foreach ($usage_rows as $usage) {
3966 if (!is_array($usage))
continue;
3967 $media_id = (int)($usage[
'media_id'] ?? 0);
3968 if ($media_id <= 0)
continue;
3969 $row =
$db->select1($this->dd_media, $media_id);
3970 if (!is_array($row) || (
int)($row[
'active'] ?? 0) !== 1)
continue;
3971 if (!$this->media_file_exists($row))
continue;
3972 $row = $this->normalize_media_row($row);
3973 $row[
'usage_id'] = (int)($usage[
'id'] ?? 0);
3974 $row[
'current_usage_id'] = (int)($usage[
'id'] ?? 0);
3975 $row[
'slot'] = (string)($usage[
'slot'] ?? $row[
'slot'] ??
'gallery');
3976 $row[
'sorter'] = (string)($usage[
'sorter'] ?? $row[
'sorter'] ??
'');
3977 if (!empty($usage[
'template'])) $row[
'template'] = $usage[
'template'];
3978 if (!empty($usage[
'caption'])) $row[
'caption'] = $usage[
'caption'];
3985 private function inline_media_block_has_content($inner) {
3986 $inner = trim((
string)$inner);
3987 if ($inner ===
'')
return false;
3988 if (preg_match(
'/<(img|video|iframe|source)\b/i', $inner))
return true;
3989 if (preg_match(
'/\bdbx-cms-inline-video-thumb\b/i', $inner))
return true;
3990 if (preg_match(
'/\bdbx-cms-inline-video-empty\b/i', $inner))
return true;
3991 if (preg_match(
'/\bdbx-cms-inline-media-missing\b/i', $inner))
return true;
3995 private function strip_empty_inline_media_wrappers($html) {
3996 $html = (string)$html;
3997 $pattern =
'/<(p|figure|div)\b([^>]*(?:\bdbx-cms-inline-media\b|\bdata-cms-media-id=)[^>]*)>([\s\S]*?)<\/\1>/i';
3999 $next = preg_replace_callback(
4002 if ($this->inline_media_block_has_content($m[3] ??
''))
return $m[0];
4007 if ($next ===
null || $next === $html)
break;
4013 private function inline_media_ids($html) {
4014 $html = preg_replace(
'/<!--[\s\S]*?-->/',
'', (
string)$html);
4017 if (preg_match_all(
'/<(p|figure|div)\b([^>]*(?:\bdbx-cms-inline-media\b|\bdata-cms-media-id=)[^>]*)>([\s\S]*?)<\/\1>/i', $html, $blocks, PREG_SET_ORDER)) {
4018 foreach ($blocks as $block) {
4019 $inner = (string)($block[3] ??
'');
4020 if (!$this->inline_media_block_has_content($inner))
continue;
4021 if (preg_match_all(
'/<(?:img|video|source|iframe)\b[^>]*\bsrc=["\'][^"\']*dbx_mid=([0-9]+)/i', $inner, $inner_ids)) {
4022 foreach ($inner_ids[1] as $id) {
4024 if ($id > 0) $ids[$id] = $id;
4028 if (preg_match(
'/\bdata-cms-media-id=["\']?([0-9]+)/i', (
string)($block[0] ??
''), $id_match)) {
4029 $id = (int)$id_match[1];
4030 if ($id > 0) $ids[$id] = $id;
4034 if (preg_match_all(
'/<(?:img|video|source|iframe)\b[^>]*\bdata-cms-media-id=["\']?([0-9]+)/i', $html, $m)) {
4035 foreach ($m[1] as $id) {
4037 if ($id > 0) $ids[$id] = $id;
4040 if (preg_match_all(
'/<(?:img|video|source|iframe)\b[^>]*\bsrc=["\'][^"\']*dbx_mid=([0-9]+)/i', $html, $m)) {
4041 foreach ($m[1] as $id) {
4043 if ($id > 0) $ids[$id] = $id;
4046 return array_values($ids);
4049 private function resolve_inline_media_ids($html, $client_ids =
null, $client_ids_provided =
false) {
4050 $html_ids = array();
4051 foreach ($this->inline_media_ids($html) as $id) {
4053 if ($id > 0) $html_ids[$id] = $id;
4055 return array_values($html_ids);
4058 private function sync_inline_media_usage(
$db, $content_id, $html, $folder_id = 0, $client_ids =
null, $client_ids_provided =
false) {
4059 $content_id = (int)$content_id;
4060 $folder_id = (int)$folder_id;
4061 $result = array(
'added' => 0,
'removed' => 0,
'kept' => 0);
4062 if ($content_id <= 0)
return $result;
4065 foreach ($this->resolve_inline_media_ids($html, $client_ids, $client_ids_provided) as $id) {
4067 if ($id > 0) $wanted[$id] = $id;
4070 $rows =
$db->select($this->dd_media_usage,
'content_id = ' . $content_id .
" AND active = 1 AND slot = 'inline'",
'*',
'id');
4071 if (is_array($rows)) {
4072 foreach ($rows as $row) {
4073 if (!is_array($row))
continue;
4074 $usage_id = (int)($row[
'id'] ?? 0);
4075 $media_id = (int)($row[
'media_id'] ?? 0);
4076 if ($usage_id > 0 && $media_id > 0 && !isset($wanted[$media_id])) {
4077 $db->update($this->dd_media_usage, array(
'active' => 0), $usage_id, 0, 1, 1, 0);
4083 foreach ($wanted as $id) {
4084 $row =
$db->select1($this->dd_media, $id);
4085 if (!is_array($row) || (
int)($row[
'active'] ?? 0) !== 1)
continue;
4087 $usages =
$db->select(
4088 $this->dd_media_usage,
4089 'media_id = ' . $id .
' AND content_id = ' . $content_id .
" AND slot = 'inline'",
4090 '*',
'active DESC, id DESC',
'', 0, 0, 0
4093 $active_usage =
null;
4094 $inactive_usage =
null;
4095 if (is_array($usages)) {
4096 foreach ($usages as $usage) {
4097 if (!is_array($usage))
continue;
4098 if ((
int)($usage[
'active'] ?? 0) === 1) {
4099 if (!$active_usage) {
4100 $active_usage = $usage;
4102 $dup_id = (int)($usage[
'id'] ?? 0);
4104 $db->update($this->dd_media_usage, array(
'active' => 0), $dup_id, 0, 1, 1, 0);
4110 if (!$inactive_usage) $inactive_usage = $usage;
4114 if ($active_usage) {
4115 $usage_id = (int)($active_usage[
'id'] ?? 0);
4116 if ($usage_id > 0 && (
int)($active_usage[
'folder_id'] ?? 0) !== $folder_id) {
4117 $db->update($this->dd_media_usage, array(
'folder_id' => $folder_id), $usage_id, 0, 1, 1, 0);
4123 if ($inactive_usage) {
4124 $usage_id = (int)($inactive_usage[
'id'] ?? 0);
4125 if ($usage_id > 0) {
4126 $db->update($this->dd_media_usage, array(
'active' => 1,
'folder_id' => $folder_id), $usage_id, 0, 1, 1, 0);
4132 if ($this->create_media_usage(
$db, $id, $content_id, $folder_id,
'inline',
'image-inline',
'',
'') > 0) {
4140 private function next_media_sorter(
$db, $content_id, $slot) {
4141 $content_id = (int)$content_id;
4142 $slot = str_replace(
"'",
"''", (
string)$slot);
4143 $rows =
$db->select($this->dd_media,
"content_id = " . $content_id .
" AND slot = '" . $slot .
"' AND active = 1",
'*',
'sorter,id',
'DESC',
'', 1, 0, 0);
4145 if (is_array($rows) && isset($rows[0]) && is_array($rows[0])) {
4146 $max = (int)($rows[0][
'sorter'] ?? 0);
4148 return sprintf(
'%04d', $max + 10);
4151 private function deactivate_existing_hero_media(
$db, $content_id, $folder_id, $except_id = 0) {
4152 $content_id = (int)$content_id;
4153 $folder_id = (int)$folder_id;
4154 $except_id = (int)$except_id;
4155 $except = $except_id > 0 ?
' AND media_id <> ' . $except_id :
'';
4156 if ($content_id > 0) {
4157 $db->update($this->dd_media_usage, array(
'active' => 0),
"content_id = " . $content_id .
" AND slot = 'hero' AND active = 1" . $except, 0, 1, 1, 0);
4158 } elseif ($folder_id > 0) {
4159 $db->update($this->dd_media_usage, array(
'active' => 0),
"folder_id = " . $folder_id .
" AND slot = 'hero' AND active = 1" . $except, 0, 1, 1, 0);
4163 private function sync_saved_hero_media_usage(
$db, $content_id, $folder_id, $hero_image_id) {
4164 $content_id = (int)$content_id;
4165 $folder_id = (int)$folder_id;
4166 $hero_id = (int)$hero_image_id;
4167 if ($content_id <= 0 && $folder_id <= 0)
return;
4169 $this->deactivate_existing_hero_media(
$db, $content_id, $folder_id, $hero_id);
4172 $this->deactivate_existing_hero_media(
$db, $content_id, $folder_id, 0);
4175 private function is_no_hero_template($value) {
4176 $value = strtolower(trim((
string)$value));
4177 return in_array($value, array(
'none',
'no-hero',
'0',
'off'),
true);
4180 private function normalize_hero_payload(array $data) {
4181 if ($this->is_no_hero_template($data[
'hero_template'] ??
'')) {
4182 $data[
'hero_template'] =
'none';
4183 $data[
'hero_image_id'] =
'0';
4188 private function source_media_file($row) {
4189 $rel = ltrim(str_replace(
'\\',
'/', (
string)($row[
'file_path'] ??
'')),
'/');
4190 if ($rel ===
'' || strpos($rel,
'..') !==
false)
return '';
4194 if (!
$base)
return '';
4195 $file =
$root . $rel;
4197 $real = realpath($file);
4198 return $real && strpos($real,
$base) === 0 && is_file($real) && is_readable($real) ? $real :
'';
4201 private function copy_media_to_slot(
$db, array $row, $slot) {
4203 $slot = $this->valid_media_slot($slot);
4204 $src = $this->source_media_file($row);
4205 if ($src ===
'')
return 0;
4207 $dir = $this->cms_media_dir($slot);
4208 if (!is_dir(
$dir)) @mkdir(
$dir, 0777,
true);
4209 if (!is_dir(
$dir))
return 0;
4211 $name = $this->unique_media_name((
string)($row[
'file_name'] ?? basename($src)), $slot);
4212 $dst = rtrim(
$dir,
'/\\') . DIRECTORY_SEPARATOR . $name;
4213 if (!copy($src, $dst))
return 0;
4215 $rel = $this->media_rel_dir($slot) . $name;
4216 $mime = (string)($row[
'mime'] ??
'');
4219 $img = @getimagesize($dst);
4220 if (is_array($img)) {
4221 $width = (int)($img[0] ?? 0);
4222 $height = (int)($img[1] ?? 0);
4227 'content_id' => (
int)($row[
'content_id'] ?? 0),
4228 'folder_id' => (
int)($row[
'folder_id'] ?? 0),
4231 'sorter' => $this->next_media_sorter(
$db, (
int)($row[
'content_id'] ?? 0), $slot),
4232 'template' => (
string)($row[
'template'] ??
''),
4233 'title' => (
string)($row[
'title'] ?? pathinfo($name, PATHINFO_FILENAME)),
4234 'alt' => (
string)($row[
'alt'] ?? $row[
'title'] ??
''),
4235 'caption' => (
string)($row[
'caption'] ??
''),
4236 'file_name' => $name,
4237 'file_path' => $rel,
4239 'size' => (
int)@filesize($dst),
4241 'height' => $height,
4242 'tags' => (
string)($row[
'tags'] ??
''),
4243 'media_type' => $this->media_type(array(
'mime' => $mime,
'file_name' => $name)),
4245 $thumb = $this->create_media_thumbnail($dst, $slot, $name, $mime);
4246 if ($thumb) $insert = array_merge($insert, $thumb);
4247 return (
$db->insert($this->dd_media, $insert) === 1) ?
$db->get_insert_id() : 0;
4250 private function set_media_slot_json() {
4251 $payload = $this->request_json();
4252 $usage_id = (int)($payload[
'usage_id'] ?? 0);
4253 $id = (int)($payload[
'media_id'] ?? $payload[
'id'] ?? 0);
4254 $slot = $this->valid_media_slot($payload[
'slot'] ??
'gallery');
4255 if ($usage_id <= 0 && $id <= 0) $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Keine Medien- oder Usage-ID erhalten.'));
4257 $db =
dbx()->get_system_obj(
'dbxDB');
4258 $this->ensure_cms_schema(
$db);
4259 if ($usage_id <= 0) {
4260 $rows =
$db->select($this->dd_media_usage, $this->usage_where($id),
'*',
'id',
'DESC',
'', 1, 0, 0);
4261 if (is_array($rows) && isset($rows[0])) $usage_id = (int)($rows[0][
'id'] ?? 0);
4263 if ($usage_id <= 0) {
4264 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Keine vorhandene Zuordnung gefunden.'));
4267 $usage =
$db->select1($this->dd_media_usage, $usage_id);
4268 if (!is_array($usage) || (
int)($usage[
'active'] ?? 0) !== 1) {
4269 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Zuordnung nicht gefunden.'));
4271 if ($slot ===
'hero') $this->deactivate_existing_hero_media(
$db, (
int)($usage[
'content_id'] ?? 0), (
int)($usage[
'folder_id'] ?? 0), (
int)($usage[
'media_id'] ?? 0));
4272 $ok =
$db->update($this->dd_media_usage, array(
'slot' => $slot), $usage_id);
4273 $usage = $this->normalize_usage_row(
$db->select1($this->dd_media_usage, $usage_id));
4274 $row = $this->normalize_media_row(
$db->select1($this->dd_media, (
int)($usage[
'media_id'] ?? 0)));
4276 $this->flush_media_cache(
$db, (
int)($usage[
'content_id'] ?? 0), (
int)($usage[
'folder_id'] ?? 0));
4278 $this->cms_json_response(array(
'ok' => ($ok >= 0 ? 1 : 0),
'success' => ($ok >= 0),
'row' => $row,
'usage' => $usage,
'msg' =>
'Zuordnung aktualisiert.'));
4281 private function assign_media_json() {
4282 $payload = $this->request_json();
4283 $id = (int)($payload[
'media_id'] ?? $payload[
'id'] ?? 0);
4284 $content_id = (int)($payload[
'content_id'] ?? 0);
4285 $folder_id = (int)($payload[
'folder_id'] ?? 0);
4286 $slot = $this->valid_media_slot($payload[
'slot'] ??
'gallery');
4287 if ($id <= 0 || ($content_id <= 0 && !($folder_id > 0 && $slot ===
'hero'))) {
4288 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Keine Zuordnung erhalten.'));
4291 $db =
dbx()->get_system_obj(
'dbxDB');
4292 $this->ensure_cms_schema(
$db);
4293 $row =
$db->select1($this->dd_media, $id);
4294 if (!is_array($row) || (
int)($row[
'active'] ?? 0) !== 1) {
4295 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Medium nicht gefunden.'));
4298 $usage_id = $this->create_media_usage(
4304 (
string)($payload[
'template'] ?? $row[
'template'] ??
''),
4305 (
string)($payload[
'caption'] ??
''),
4306 is_array($payload[
'settings'] ??
null) ? json_encode($payload[
'settings'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : (
string)($payload[
'settings'] ??
'')
4308 if ($usage_id <= 0) $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Medium konnte nicht zugeordnet werden.'));
4309 $usage = $this->normalize_usage_row(
$db->select1($this->dd_media_usage, $usage_id));
4310 $this->flush_media_cache(
$db, $content_id, $folder_id);
4311 $this->cms_json_response(array(
'ok' => 1,
'success' =>
true,
'id' => $id,
'usage_id' => $usage_id,
'row' => $this->normalize_media_row($row),
'usage' => $usage,
'msg' =>
'Zuordnung angelegt.'));
4314 private function sort_media_json() {
4315 $payload = $this->request_json();
4316 $content_id = (int)($payload[
'content_id'] ?? 0);
4317 $folder_id = (int)($payload[
'folder_id'] ?? 0);
4318 $slot = $this->valid_media_slot($payload[
'slot'] ??
'gallery');
4319 $ids = $payload[
'ids'] ?? array();
4320 if (($content_id <= 0 && $folder_id <= 0) || !is_array($ids)) {
4321 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Keine Sortierung erhalten.'));
4324 $db =
dbx()->get_system_obj(
'dbxDB');
4325 $this->ensure_cms_schema(
$db);
4327 foreach ($ids as $id) {
4329 if ($id <= 0)
continue;
4330 $where =
"active = 1 AND slot = '" . str_replace(
"'",
"''", $slot) .
"'";
4331 if ($content_id > 0) $where .=
' AND content_id = ' . $content_id;
4332 if ($folder_id > 0) $where .=
' AND folder_id = ' . $folder_id;
4333 $where .=
' AND (id = ' . $id .
' OR media_id = ' . $id .
')';
4334 $rows =
$db->select($this->dd_media_usage, $where,
'*',
'id',
'ASC',
'', 1, 0, 0);
4335 if (!is_array($rows) || !isset($rows[0]))
continue;
4336 $db->update($this->dd_media_usage, array(
'sorter' => sprintf(
'%04d', $sort)), (
int)$rows[0][
'id']);
4339 $this->flush_media_cache(
$db, $content_id, $folder_id);
4340 $this->cms_json_response(array(
'ok' => 1,
'success' =>
true));
4343 private function filter_existing_media(
$db, array $rows) {
4345 foreach ($rows as $row) {
4346 if (!is_array($row))
continue;
4347 if ($this->media_file_exists($row)) {
4351 $id = (int)($row[
'id'] ?? 0);
4353 $db->update($this->dd_media, array(
'active' => 0), $id);
4359 private function upload_json() {
4360 $content_length = (int)(
$_SERVER[
'CONTENT_LENGTH'] ?? 0);
4361 $max_upload = $this->upload_max_bytes();
4362 if ($content_length > 0 && $max_upload > 0 && $content_length > $max_upload) {
4363 $this->cms_json_response(array(
4366 'msg' =>
'Upload zu gross. Erlaubt sind maximal ' . round($max_upload / 1024 / 1024) .
' MB.'
4370 $file = $this->first_upload_file();
4371 if (empty($file) || !is_array($file)) {
4372 $this->cms_json_response(array(
'ok' => 0,
'msg' =>
'Keine Datei erhalten. Bitte Upload-Limit und Dateigroesse pruefen.'));
4375 if (($file[
'error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
4376 if (($file[
'error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
4377 $this->cms_json_response(array(
'ok' => 0,
'msg' =>
'Bitte zuerst eine Datei auswaehlen.'));
4379 $this->cms_json_response(array(
'ok' => 0,
'msg' =>
'Upload fehlgeschlagen.'));
4382 $name = basename((
string)$file[
'name']);
4383 $name = preg_replace(
'/[^A-Za-z0-9_.-]+/',
'-', $name);
4384 $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
4385 if (!in_array($ext, array(
'jpg',
'jpeg',
'png',
'gif',
'webp',
'svg',
'pdf',
'mp4',
'webm',
'ogv',
'ogg',
'mov',
'm4v'),
true)) {
4386 $this->cms_json_response(array(
'ok' => 0,
'msg' =>
'Dateityp nicht erlaubt.'));
4389 $db =
dbx()->get_system_obj(
'dbxDB');
4390 $this->ensure_cms_schema(
$db);
4391 $content_id = (int)($_POST[
'content_id'] ?? 0);
4392 $folder_id = (int)($_POST[
'folder_id'] ?? 0);
4393 $slot = $this->valid_media_slot($_POST[
'slot'] ??
'gallery');
4394 $probe_type = $this->media_type(array(
'file_name' => $name,
'mime' => (
string)($file[
'type'] ??
'')));
4395 $media_folder = $this->clean_media_folder($_POST[
'media_folder'] ??
'', $probe_type);
4396 $media_rel_dir = $this->media_folder_rel_dir($media_folder, $probe_type);
4397 $dir = $this->cms_media_dir($media_folder);
4398 if (!is_dir(
$dir)) {
4399 mkdir(
$dir, 0777,
true);
4402 $dstName = $this->unique_media_name($name);
4403 $dst =
$dir . $dstName;
4405 if (!move_uploaded_file($file[
'tmp_name'], $dst)) {
4406 $this->cms_json_response(array(
'ok' => 0,
'msg' =>
'Datei konnte nicht gespeichert werden.'));
4411 $detected_mime = function_exists(
'mime_content_type') ? (string)@mime_content_type($dst) :
'';
4412 $mime = $detected_mime !==
'' ? $detected_mime : (string)($file[
'type'] ??
'');
4413 $img = @getimagesize($dst);
4414 if (is_array($img)) {
4415 $width = (int)($img[0] ?? 0);
4416 $height = (int)($img[1] ?? 0);
4418 $max_image_size = max(800, min(6000, (
int)($_POST[
'max_image_size'] ?? 2560)));
4419 if ($width > 0 && $height > 0 && $this->media_type(array(
'mime' => $mime,
'file_name' => $dstName)) ===
'image') {
4420 if ($this->resize_image_to_max($dst, $mime, $max_image_size)) {
4421 clearstatcache(
true, $dst);
4422 $img = @getimagesize($dst);
4423 if (is_array($img)) {
4424 $width = (int)($img[0] ?? 0);
4425 $height = (int)($img[1] ?? 0);
4429 $webp = $this->convert_media_image_to_webp($dst, $dstName, $mime);
4430 if (is_array($webp) && !empty($webp[
'file']) && !empty($webp[
'name'])) {
4431 $dst = (string)$webp[
'file'];
4432 $dstName = (string)$webp[
'name'];
4433 $mime = (string)($webp[
'mime'] ??
'image/webp');
4434 clearstatcache(
true, $dst);
4435 $img = @getimagesize($dst);
4436 if (is_array($img)) {
4437 $width = (int)($img[0] ?? 0);
4438 $height = (int)($img[1] ?? 0);
4443 $media_tpl = $this->clean_text($_POST[
'template'] ??
'', 80);
4444 $file_size = (int)@filesize($dst);
4445 $title = pathinfo($name, PATHINFO_FILENAME);
4446 $media_type = $this->media_type(array(
'mime' => $mime,
'file_name' => $dstName));
4447 $media_folder = $this->clean_media_folder($media_folder, $media_type);
4448 if ($media_tpl ===
'') {
4449 $media_tpl = $media_type ===
'video' ? ($slot ===
'hero' ?
'video-hero' :
'video-gallery') :
'';
4452 $existing =
$db->select(
4454 "active = 1 AND storage_type = 'local' AND media_folder = '" . str_replace(
"'",
"''", $media_folder) .
"' AND title = '" . str_replace(
"'",
"''", $title) .
"' AND size = " . $file_size,
4459 if (is_array($existing) && isset($existing[0]) && $this->media_file_exists($existing[0])) {
4461 $row = $this->normalize_media_row($existing[0]);
4463 if ($content_id > 0 || $folder_id > 0) {
4464 $usage_id = $this->create_media_usage(
$db, (
int)($row[
'id'] ?? 0), $content_id, $folder_id, $slot, $media_tpl,
'',
'');
4465 if ($usage_id > 0) $usage = $this->normalize_usage_row(
$db->select1($this->dd_media_usage, $usage_id));
4467 $this->flush_media_cache(
$db, $content_id, $folder_id);
4468 $this->cms_json_response(array(
4471 'duplicate' =>
true,
4474 'files' => array($row[
'file_name'] ??
''),
4487 'template' => $media_tpl,
4490 'file_name' => $dstName,
4491 'file_path' => $media_rel_dir . $dstName,
4493 'size' => $file_size,
4495 'height' => $height,
4496 'media_type' => $media_type,
4497 'storage_type' =>
'local',
4498 'media_folder' => $media_folder,
4500 $thumb = $this->create_media_thumbnail($dst, $media_folder, $dstName, $mime);
4501 if ($thumb) $insert = array_merge($insert, $thumb);
4502 $id = (
$db->insert($this->dd_media, $insert) === 1) ?
$db->get_insert_id() : 0;
4506 if ($content_id > 0 || $folder_id > 0) {
4507 $usage_id = $this->create_media_usage(
$db, $id, $content_id, $folder_id, $slot, $media_tpl,
'',
'');
4508 if ($usage_id > 0) $usage = $this->normalize_usage_row(
$db->select1($this->dd_media_usage, $usage_id));
4510 $row = $this->normalize_media_row(
$db->select1($this->dd_media, $id));
4512 if (!empty($row[
'url'])) {
4513 $baseurl = preg_replace(
'~/[^/]*$~',
'/', $row[
'url']);
4515 $this->flush_media_cache(
$db, $content_id, $folder_id);
4516 $this->cms_json_response(array(
4521 'files' => array($row[
'file_name'] ?? $dstName),
4523 'baseurl' => $baseurl,
4527 $this->cms_json_response(array(
'ok' => 0,
'msg' =>
'Mediendatensatz konnte nicht gespeichert werden.'));
4530 private function remove_media_json() {
4531 $id = (int)
dbx()->get_modul_var(
'id', 0,
'int');
4532 $usage_id = (int)
dbx()->get_modul_var(
'usage_id', 0,
'int');
4534 if ($id <= 0 || $usage_id <= 0) {
4535 $payload = $this->request_json();
4536 if ($usage_id <= 0) $usage_id = (int)($payload[
'usage_id'] ?? 0);
4537 if ($id <= 0) $id = (int)($payload[
'media_id'] ?? $payload[
'id'] ?? 0);
4539 if ($usage_id <= 0 && $id <= 0) $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Keine Medien- oder Usage-ID erhalten.'));
4541 $db =
dbx()->get_system_obj(
'dbxDB');
4542 $this->ensure_cms_schema(
$db);
4543 $content_id = (int)($payload[
'content_id'] ??
dbx()->get_modul_var(
'content_id', 0,
'int'));
4544 $folder_id = (int)($payload[
'folder_id'] ??
dbx()->get_modul_var(
'folder_id', 0,
'int'));
4545 if ($usage_id > 0) {
4546 $usage_row =
$db->select1($this->dd_media_usage, $usage_id);
4547 if (is_array($usage_row)) {
4548 if ($content_id <= 0) $content_id = (int)($usage_row[
'content_id'] ?? 0);
4549 if ($folder_id <= 0) $folder_id = (int)($usage_row[
'folder_id'] ?? 0);
4550 if ($id <= 0) $id = (int)($usage_row[
'media_id'] ?? 0);
4552 $ok =
$db->update($this->dd_media_usage, array(
'active' => 0), $usage_id);
4554 $slot = trim((
string)($payload[
'slot'] ??
dbx()->get_modul_var(
'slot',
'',
'varchar')));
4555 if ($content_id <= 0 && $folder_id <= 0) {
4556 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Keine Seiten- oder Ordner-Zuordnung erhalten.'));
4558 $where =
'media_id = ' . $id .
' AND active = 1';
4559 if ($content_id > 0) $where .=
' AND content_id = ' . $content_id;
4560 if ($folder_id > 0) $where .=
' AND folder_id = ' . $folder_id;
4561 if ($slot !==
'') $where .=
" AND slot = '" . str_replace(
"'",
"''", $slot) .
"'";
4562 $ok =
$db->update($this->dd_media_usage, array(
'active' => 0), $where, 0, 1, 1, 0);
4564 $media = ($content_id > 0 || $folder_id > 0) ? $this->media_usage_rows_for_context(
$db, $content_id, $content_id > 0 ? 0 : $folder_id) : array();
4566 $this->flush_media_cache(
$db, $content_id, $folder_id);
4568 $this->cms_json_response(array(
'ok' => ($ok >= 0 ? 1 : 0),
'success' => ($ok >= 0),
'id' => $id,
'usage_id' => $usage_id,
'media' => $media,
'rows' => $media,
'msg' =>
'Zuordnung entfernt.'));
4571 private function delete_media_json() {
4572 $id = (int)
dbx()->get_modul_var(
'id', 0,
'int');
4573 $force = (int)
dbx()->get_modul_var(
'force', 0,
'int');
4575 $payload = $this->request_json();
4576 $id = (int)($payload[
'media_id'] ?? $payload[
'id'] ?? 0);
4577 $force = (int)($payload[
'force'] ?? 0);
4579 if ($id <= 0) $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Keine Medien-ID erhalten.'));
4581 $db =
dbx()->get_system_obj(
'dbxDB');
4582 $this->ensure_cms_schema(
$db);
4583 $used =
$db->select($this->dd_media_usage, $this->usage_where($id),
'*',
'id',
'ASC',
'', 1, 0, 0);
4584 if (is_array($used) && isset($used[0]) && !$force) {
4585 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Medium wird noch verwendet.'));
4587 $row =
$db->select1($this->dd_media, $id);
4588 if (is_array($row)) {
4589 $this->flush_media_by_media_id(
$db, $id);
4590 if ($force)
$db->update($this->dd_media_usage, array(
'active' => 0),
'media_id = ' . $id .
' AND active = 1', 0, 1, 1, 0);
4591 if (strtolower((
string)($row[
'storage_type'] ??
'local')) ===
'local') {
4592 $file = $this->source_media_file($row);
4593 if ($file !==
'') @unlink($file);
4595 $file = $this->source_media_file($row);
4596 if ($file !==
'' && preg_match(
'/\.json$/i', $file)) @unlink($file);
4598 $thumb = $this->source_thumb_file($row);
4599 if ($thumb !==
'') @unlink($thumb);
4601 $ok =
$db->update($this->dd_media, array(
'active' => 0), $id);
4602 $this->cms_json_response(array(
'ok' => ($ok >= 0 ? 1 : 0),
'success' => ($ok >= 0),
'id' => $id,
'msg' =>
'Medium geloescht.'));
4605 private function youtube_video_id($url) {
4606 $url = trim((
string)$url);
4607 if ($url ===
'')
return '';
4608 if (preg_match(
'~^[A-Za-z0-9_-]{11}$~', $url))
return $url;
4609 $parts = @parse_url($url);
4610 if (!is_array($parts))
return '';
4611 $host = strtolower((
string)($parts[
'host'] ??
''));
4612 $path = trim((
string)($parts[
'path'] ??
''),
'/');
4613 if (strpos($host,
'youtu.be') !==
false && preg_match(
'~^([A-Za-z0-9_-]{11})~', $path, $m))
return $m[1];
4614 if (strpos($host,
'youtube.com') !==
false) {
4615 parse_str((
string)($parts[
'query'] ??
''), $query);
4616 if (!empty($query[
'v']) && preg_match(
'~^[A-Za-z0-9_-]{11}$~', (
string)$query[
'v']))
return (
string)$query[
'v'];
4617 if (preg_match(
'~^(embed|shorts)/([A-Za-z0-9_-]{11})~', $path, $m))
return $m[2];
4622 private function external_video_json() {
4623 $payload = $this->request_json();
4624 $provider = strtolower(trim((
string)($payload[
'provider'] ??
'youtube')));
4625 $external_url = trim((
string)($payload[
'external_url'] ?? $payload[
'url'] ??
''));
4626 if ($provider !==
'youtube') $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Provider nicht unterstuetzt.'));
4627 $video_id = $this->youtube_video_id($external_url);
4628 if ($video_id ===
'') $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'YouTube-URL nicht erkannt.'));
4630 $db =
dbx()->get_system_obj(
'dbxDB');
4631 $this->ensure_cms_schema(
$db);
4632 $content_id = (int)($payload[
'content_id'] ?? 0);
4633 $folder_id = (int)($payload[
'folder_id'] ?? 0);
4634 $slot = $this->valid_media_slot($payload[
'slot'] ??
'gallery');
4635 $media_folder = $this->clean_media_folder($payload[
'media_folder'] ??
'youtube',
'external_video');
4636 $title = $this->clean_text($payload[
'title'] ?? (
'YouTube ' . $video_id), 160);
4637 $embed_url =
'https://www.youtube.com/embed/' . $video_id;
4638 $thumb_url =
'https://img.youtube.com/vi/' . $video_id .
'/hqdefault.jpg';
4640 $dir = $this->cms_media_dir($media_folder);
4641 if (!is_dir(
$dir)) @mkdir(
$dir, 0777,
true);
4642 $rel = $this->media_folder_rel_dir($media_folder,
'external_video') . $video_id .
'.json';
4643 $file = $this->file_from_media_rel($rel);
4645 @file_put_contents($file, json_encode(array(
4646 'provider' =>
'youtube',
4647 'provider_id' => $video_id,
4648 'external_url' => $external_url,
4649 'embed_url' => $embed_url,
4651 'thumb_url' => $thumb_url,
4652 ), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
4654 $existing =
$db->select($this->dd_media,
"active = 1 AND provider = 'youtube' AND provider_id = '" . str_replace(
"'",
"''", $video_id) .
"'",
'*',
'id',
'DESC',
'', 1, 0, 0);
4655 if (is_array($existing) && isset($existing[0])) {
4656 $row = $existing[0];
4658 'media_folder' => $media_folder,
4659 'file_name' => $video_id .
'.json',
4660 'file_path' => $rel,
4661 'mime' =>
'application/json',
4662 'media_type' =>
'external_video',
4663 'storage_type' =>
'external',
4664 'external_url' => $external_url,
4665 'embed_url' => $embed_url,
4667 'size' => $file !==
'' && is_file($file) ? (
int)@filesize($file) : 0,
4669 $db->update($this->dd_media, $update, (
int)($row[
'id'] ?? 0));
4670 $row = array_merge($row, $update);
4673 if (
$db->insert($this->dd_media, array(
4680 'template' =>
'video-gallery',
4683 'file_name' => $video_id .
'.json',
4684 'file_path' => $rel,
4685 'mime' =>
'application/json',
4686 'size' => $file !==
'' && is_file($file) ? (
int)@filesize($file) : 0,
4689 'media_type' =>
'external_video',
4690 'storage_type' =>
'external',
4691 'media_folder' => $media_folder,
4692 'provider' =>
'youtube',
4693 'provider_id' => $video_id,
4694 'external_url' => $external_url,
4695 'embed_url' => $embed_url,
4697 $id =
$db->get_insert_id();
4699 $row =
$db->select1($this->dd_media, $id);
4703 if (is_array($row) && ((
int)($row[
'id'] ?? 0) > 0) && ($content_id > 0 || $folder_id > 0)) {
4704 $usage_id = $this->create_media_usage(
$db, (
int)$row[
'id'], $content_id, $folder_id, $slot,
'video-gallery',
'',
'');
4705 if ($usage_id > 0) $usage = $this->normalize_usage_row(
$db->select1($this->dd_media_usage, $usage_id));
4707 $this->flush_media_cache(
$db, $content_id, $folder_id);
4708 $this->cms_json_response(array(
'ok' => 1,
'success' =>
true,
'row' => $this->normalize_media_row($row),
'usage' => $usage,
'msg' =>
'Externes Video gespeichert.'));
4711 private function media_dir_has_content(
$dir) {
4712 if (!is_dir(
$dir) || !is_readable(
$dir))
return false;
4713 $it = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(
$dir, \FilesystemIterator::SKIP_DOTS));
4714 foreach ($it as $item) {
4715 if ($item->isFile())
return true;
4720 private function is_reserved_media_folder($folder) {
4721 $folder = $this->canonical_media_folder(strtolower(trim(str_replace(
'\\',
'/', (
string)$folder),
'/')));
4722 if ($folder ===
'module' || strpos($folder,
'module/') === 0)
return true;
4723 if (preg_match(
'~^img/external(/|$)~', $folder))
return true;
4724 if (preg_match(
'~^img/video(/|$)~', $folder))
return true;
4725 return in_array($folder, $this->media_slots(),
true);
4728 private function media_folder_dir_is_listable($folder, $path,
$base =
'') {
4729 return is_dir($path) && is_readable($path);
4732 private function media_folder_is_listable($folder) {
4733 $folder = $this->canonical_media_folder($folder);
4734 if ($folder ===
'' || $folder ===
'module')
return false;
4735 if ($this->is_reserved_media_folder($folder))
return false;
4736 $dir = $this->cms_media_dir($folder);
4737 return is_dir(
$dir) && is_readable(
$dir);
4740 private function collect_custom_media_root_folders(array &$folders) {
4742 if (function_exists(
'dbx_os_path_file')) $mediaRoot =
dbx_os_path_file($mediaRoot);
4743 if (!is_dir($mediaRoot) || !is_readable($mediaRoot))
return;
4744 $skip = array(
'.',
'..',
'_thumbs',
'img',
'video',
'videos',
'youtube',
'external',
'file',
'module');
4745 $skip = array_merge($skip, $this->media_slots());
4746 $root_norm = str_replace(
'\\',
'/', rtrim($mediaRoot,
'/\\')) .
'/';
4747 $it = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($mediaRoot, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST);
4748 foreach ($it as $item) {
4749 if (!$item->isDir())
continue;
4750 $path = str_replace(
'\\',
'/', $item->getPathname());
4751 if (strpos($path .
'/', $root_norm) !== 0)
continue;
4752 $rel = trim(substr($path, strlen($root_norm)),
'/');
4753 if ($rel ===
'')
continue;
4754 $top = strtok($rel,
'/');
4755 if ($top ===
false || in_array($top, $skip,
true))
continue;
4756 if ($this->is_reserved_media_folder($rel))
continue;
4761 private function media_folders_json() {
4762 $bases = $this->media_standard_bases();
4764 foreach ($bases as
$base => $type) {
4766 if (!is_dir(
$root) || !is_readable(
$root))
continue;
4767 if (
$base ===
'youtube' || $type ===
'video') {
4768 $folders[] = $this->clean_media_folder(
$base, $type);
4770 $root_norm = str_replace(
'\\',
'/', rtrim(
$root,
'/\\')) .
'/';
4771 $it = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(
$root, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST);
4772 foreach ($it as $item) {
4773 if (!$item->isDir())
continue;
4774 $path = str_replace(
'\\',
'/', $item->getPathname());
4775 if (strpos($path .
'/', $root_norm) !== 0)
continue;
4776 $rel = trim(substr($path, strlen($root_norm)),
'/');
4777 if ($rel ===
'')
continue;
4778 $folder = $this->clean_media_folder(
$base .
'/' . $rel, $type);
4779 if ($this->is_reserved_media_folder($folder))
continue;
4780 if (!$this->media_folder_dir_is_listable($folder, $path,
$base))
continue;
4781 $folders[] = $folder;
4784 $this->collect_custom_media_root_folders($folders);
4786 $folders = array_values(array_unique(array_map(array($this,
'canonical_media_folder'), $folders)));
4787 $verified = array();
4788 foreach ($folders as $folder) {
4789 $folder = $this->canonical_media_folder($folder);
4790 if ($folder ===
'' || $this->is_excluded_cms_media_folder($folder))
continue;
4791 $dir = $this->cms_media_dir($folder);
4792 if (!is_dir(
$dir) || !is_readable(
$dir))
continue;
4793 $verified[] = $folder;
4795 $folders = $verified;
4797 $this->cms_json_response(array(
'ok' => 1,
'success' =>
true,
'root' =>
'files/media/',
'folders' => $folders));
4800 private function media_folder_create_json() {
4801 $payload = $this->request_json();
4802 $type = $this->media_type(array(
'media_type' => ($payload[
'media_type'] ??
'image')));
4803 $folder = $this->clean_media_folder($payload[
'media_folder'] ?? $payload[
'folder'] ??
'', $type);
4804 if ($this->is_excluded_cms_media_folder($folder)) {
4805 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Dieser Ordner ist im CMS-Medienbrowser nicht verfuegbar.'));
4807 $dir = $this->cms_media_dir($folder);
4808 if (!is_dir(
$dir) && !@mkdir(
$dir, 0777,
true)) {
4809 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Ordner konnte nicht angelegt werden.'));
4811 $this->cms_json_response(array(
'ok' => 1,
'success' =>
true,
'folder' => $folder,
'msg' =>
'Ordner angelegt.'));
4814 private function media_folder_delete_json() {
4815 $payload = $this->request_json();
4816 $raw_folder = $payload[
'media_folder'] ?? $payload[
'folder'] ??
'';
4817 $type = $this->media_type(array(
'media_type' => ($payload[
'media_type'] ?? $this->media_type_from_folder($raw_folder))));
4818 $folder = $this->clean_media_folder($raw_folder, $type);
4819 if ($this->is_excluded_cms_media_folder($folder)) {
4820 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Dieser Ordner ist im CMS-Medienbrowser nicht verfuegbar.'));
4822 $dir = $this->cms_media_dir($folder);
4823 if (!is_dir(
$dir)) $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Ordner nicht gefunden.'));
4825 $db =
dbx()->get_system_obj(
'dbxDB');
4826 $this->ensure_cms_schema(
$db);
4827 $used =
$db->select($this->dd_media,
"active = 1 AND media_folder = '" . str_replace(
"'",
"''", $folder) .
"'",
'*',
'id',
'ASC',
'', 1, 0, 0);
4828 if (is_array($used) && isset($used[0])) $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Ordner enthaelt noch Medien.'));
4830 $items = @scandir(
$dir);
4831 $items = is_array($items) ? array_diff($items, array(
'.',
'..')) : array();
4832 if (!empty($items)) $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Ordner ist nicht leer.'));
4833 if (!@rmdir(
$dir)) $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Ordner konnte nicht geloescht werden.'));
4834 $this->cms_json_response(array(
'ok' => 1,
'success' =>
true,
'folder' => $folder,
'msg' =>
'Ordner geloescht.'));
4837 private function media_folder_rename_json() {
4838 $payload = $this->request_json();
4839 $from = $this->clean_media_folder($payload[
'from_folder'] ?? $payload[
'media_folder_from'] ?? $payload[
'from'] ??
'',
'image');
4840 $to_raw = trim((
string)($payload[
'to_folder'] ?? $payload[
'media_folder_to'] ?? $payload[
'to'] ??
''));
4841 if ($to_raw !==
'' && strpos($to_raw,
'/') ===
false && $from !==
'') {
4842 $base = strtok($from,
'/');
4843 $to_raw = (
$base !==
false &&
$base !==
'' ?
$base :
'img') .
'/' . $to_raw;
4845 $to = $this->clean_media_folder($to_raw, $this->media_type_from_folder($to_raw !==
'' ? $to_raw : $from));
4846 if ($from ===
'' || $to ===
'' || $from === $to) {
4847 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Ungueltiger Ordner.'));
4849 if ($this->is_excluded_cms_media_folder($from) || $this->is_excluded_cms_media_folder($to)) {
4850 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Dieser Ordner ist im CMS-Medienbrowser nicht verfuegbar.'));
4852 $from_dir = $this->cms_media_dir($from);
4853 $to_dir = $this->cms_media_dir($to);
4854 if (!is_dir($from_dir) || !is_readable($from_dir)) {
4855 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Quellordner nicht gefunden.'));
4857 if (is_dir($to_dir)) {
4858 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Zielordner existiert bereits.'));
4860 if (!@rename($from_dir, $to_dir)) {
4861 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Ordner konnte nicht umbenannt werden.'));
4864 $db =
dbx()->get_system_obj(
'dbxDB');
4865 $this->ensure_cms_schema(
$db);
4866 $from_prefix =
'media/' . $from .
'/';
4867 $to_prefix =
'media/' . $to .
'/';
4868 $thumb_from =
'media/_thumbs/' . $from .
'/';
4869 $thumb_to =
'media/_thumbs/' . $to .
'/';
4870 $thumb_from_dir = $this->file_from_media_rel($thumb_from);
4871 $thumb_to_dir = $this->file_from_media_rel($thumb_to);
4872 if ($thumb_from_dir !==
'' && is_dir($thumb_from_dir) && $thumb_to_dir !==
'' && !is_dir($thumb_to_dir)) {
4873 @rename($thumb_from_dir, $thumb_to_dir);
4876 $rows =
$db->select($this->dd_media,
"active = 1 AND (media_folder = '" . str_replace(
"'",
"''", $from) .
"' OR file_path LIKE '" . str_replace(
"'",
"''", $from_prefix) .
"%')",
'*',
'id');
4877 if (!is_array($rows)) $rows = array();
4879 foreach ($rows as $row) {
4880 if (!is_array($row))
continue;
4881 $id = (int)($row[
'id'] ?? 0);
4882 if ($id <= 0)
continue;
4883 $path = ltrim(str_replace(
'\\',
'/', (
string)($row[
'file_path'] ??
'')),
'/');
4885 if (strpos($path, $from_prefix) === 0) {
4886 $new_path = $to_prefix . substr($path, strlen($from_prefix));
4889 'media_folder' => $to,
4890 'file_path' => $new_path,
4892 $thumb = ltrim(str_replace(
'\\',
'/', (
string)($row[
'thumb_file_path'] ??
'')),
'/');
4893 if ($thumb !==
'' && strpos($thumb, $thumb_from) === 0) {
4894 $update[
'thumb_file_path'] = $thumb_to . substr($thumb, strlen($thumb_from));
4896 $db->update($this->dd_media, $update, $id);
4900 $this->cms_json_response(array(
4903 'from_folder' => $from,
4905 'updated' => $updated,
4906 'msg' =>
'Ordner umbenannt.',
4910 private function media_is_used(
$db,
int $id): bool {
4911 if ($id <= 0) return true;
4912 $used =
$db->select(
4913 $this->dd_media_usage,
4914 'media_id = ' . $id .
' AND active = 1',
4923 if (is_array($used) && isset($used[0]))
return true;
4925 foreach (dbxContentLngSync::accessibleLngs() as $lng) {
4926 $contentDd = dbxContentLng::ddContent((
string)$lng);
4927 $folderDd = dbxContentLng::ddFolder((
string)$lng);
4928 if (
$db->count($contentDd,
'hero_image_id = ' . $id) > 0 ||
$db->count($folderDd,
'hero_image_id = ' . $id) > 0) {
4931 $pages =
$db->select($contentDd,
'',
'content',
'id',
'ASC',
'', 0, 0, 0);
4932 foreach (is_array($pages) ? $pages : array() as $page) {
4933 $content = (string)($page[
'content'] ??
'');
4934 if (preg_match(
'/data-cms-media-id=["\']?' . $id .
'(?:["\'\s>]|$)/i', $content)
4935 || preg_match(
'/(?:dbx_mid|media_id)=' . $id .
'(?:[^0-9]|$)/i', $content)) {
4943 private function move_media_record_to_folder(
$db,
int $id,
string $target_folder): array {
4944 if ($id <= 0) return array(
'ok' => 0,
'msg' =>
'Keine Medien-ID erhalten.');
4945 $row =
$db->select1($this->dd_media, $id);
4946 if (!is_array($row) || (
int)($row[
'active'] ?? 0) !== 1)
return array(
'ok' => 0,
'msg' =>
'Medium nicht gefunden.');
4948 $media_type = $this->media_type($row);
4949 $folder = $this->clean_media_folder($target_folder, $media_type);
4950 if ($this->is_excluded_cms_media_folder($folder)) {
4951 return array(
'ok' => 0,
'msg' =>
'Dieser Zielordner ist im CMS-Medienbrowser nicht verfuegbar.');
4953 $old_file = $this->source_media_file($row);
4954 $name = basename((
string)($row[
'file_name'] ?? $row[
'file_path'] ??
''));
4955 if ($name ===
'')
return array(
'ok' => 0,
'msg' =>
'Dateiname fehlt.');
4956 $dir = $this->cms_media_dir($folder);
4957 if (!is_dir(
$dir)) @mkdir(
$dir, 0777,
true);
4958 if (!is_dir(
$dir))
return array(
'ok' => 0,
'msg' =>
'Zielordner konnte nicht angelegt werden.');
4959 $new_rel = $this->media_folder_rel_dir($folder, $media_type) . $name;
4960 $new_file = $this->file_from_media_rel($new_rel);
4961 if ($old_file !==
'' && $new_file !==
'' && $old_file !== $new_file) {
4962 if (is_file($new_file)) {
4963 $name = $this->unique_media_name($name);
4964 $new_rel = $this->media_folder_rel_dir($folder, $media_type) . $name;
4965 $new_file = $this->file_from_media_rel($new_rel);
4967 if (!@rename($old_file, $new_file))
return array(
'ok' => 0,
'msg' =>
'Medium konnte nicht verschoben werden.');
4969 $old_thumb = $this->source_thumb_file($row);
4970 $mime = (string)($row[
'mime'] ??
'');
4971 $thumb = $this->create_media_thumbnail($new_file !==
'' ? $new_file : $old_file, $folder, $name, $mime);
4972 if ($old_thumb !==
'' && is_file($old_thumb) && $old_thumb !== $this->file_from_media_rel((
string)($thumb[
'thumb_file_path'] ??
''))) {
4973 @unlink($old_thumb);
4975 $update = array(
'active' => 1,
'media_folder' => $folder,
'file_name' => $name,
'file_path' => $new_rel);
4976 if ($thumb) $update = array_merge($update, $thumb);
4977 $ok =
$db->update($this->dd_media, $update, $id, 0, 1, 1, 0);
4978 if ($ok < 0)
return array(
'ok' => 0,
'msg' =>
'Mediendaten konnten nicht aktualisiert werden.');
4979 $row = $this->normalize_media_row(
$db->select1($this->dd_media, $id));
4980 if (!is_array($row) || (
int)($row[
'active'] ?? 0) !== 1 || (
string)($row[
'media_folder'] ??
'') !== $folder) {
4981 return array(
'ok' => 0,
'msg' =>
'Mediendaten wurden nicht konsistent gespeichert.');
4983 return array(
'ok' => 1,
'success' =>
true,
'row' => $row,
'msg' =>
'Medium verschoben.');
4986 private function media_move_json() {
4987 $payload = $this->request_json();
4988 $id = (int)($payload[
'media_id'] ?? $payload[
'id'] ?? 0);
4989 if ($id <= 0) $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Keine Medien-ID erhalten.'));
4991 $db =
dbx()->get_system_obj(
'dbxDB');
4992 $this->ensure_cms_schema(
$db);
4993 $result = $this->move_media_record_to_folder(
$db, $id, (
string)($payload[
'media_folder'] ?? $payload[
'folder'] ??
''));
4994 $this->cms_json_response(
$result);
4997 private function media_unused_json() {
4998 $payload = $this->request_json();
4999 $action = strtolower(trim((
string)($payload[
'action'] ??
'')));
5000 if (!in_array($action, array(
'delete',
'move'),
true)) {
5001 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Ungueltige Aktion.'));
5004 $source_raw = trim((
string)($payload[
'source_folder'] ?? $payload[
'media_folder'] ??
'all'));
5005 $source = strtolower($source_raw) ===
'all' || $source_raw ===
''
5007 : $this->clean_media_folder($source_raw, $this->media_type_from_folder($source_raw,
'image'));
5008 if ($source !==
'all' && $this->is_excluded_cms_media_folder($source)) {
5009 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Dieser Quellordner ist im CMS-Medienbrowser nicht verfuegbar.'));
5013 if ($action ===
'move') {
5014 $target_raw = trim((
string)($payload[
'target_folder'] ?? $payload[
'to_folder'] ??
''));
5015 if ($target_raw ===
'') {
5016 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Bitte einen Zielordner waehlen.'));
5018 $target = $this->clean_media_folder($target_raw,
'image');
5019 if ($target ===
'' || $this->is_excluded_cms_media_folder($target)) {
5020 $this->cms_json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Bitte einen gueltigen Zielordner waehlen.'));
5024 $db =
dbx()->get_system_obj(
'dbxDB');
5025 $this->ensure_cms_schema(
$db);
5026 $this->sync_cms_media_files(
$db);
5028 $where =
"active = 1"
5029 . $this->cms_media_exclude_sql()
5030 .
" AND (mime LIKE 'image/%' OR file_name LIKE '%.jpg' OR file_name LIKE '%.jpeg' OR file_name LIKE '%.png' OR file_name LIKE '%.gif' OR file_name LIKE '%.webp' OR file_name LIKE '%.svg')";
5031 if ($source !==
'all') {
5032 $where .=
" AND media_folder = '" . str_replace(
"'",
"''", $source) .
"'";
5034 if ($action ===
'move') {
5035 $where .=
" AND media_folder <> '" . str_replace(
"'",
"''", $target) .
"'";
5038 $rows =
$db->select($this->dd_media, $where,
'*',
'media_folder,title,id',
'ASC',
'', 0, 0, 0);
5039 if (!is_array($rows)) $rows = array();
5040 $rows = $this->filter_existing_media(
$db, $rows);
5046 $moved_rows = array();
5048 foreach ($rows as $row) {
5049 if (!is_array($row))
continue;
5050 $id = (int)($row[
'id'] ?? 0);
5051 if ($id <= 0)
continue;
5053 if ($this->media_is_used(
$db, $id)) {
5057 if ($action ===
'delete') {
5058 $result = $this->delete_media_record($id);
5059 if ((
int)(
$result[
'ok'] ?? 0) === 1) {
5062 $errors[] =
'#' . $id .
': ' . implode(
' ', (array)(
$result[
'errors'] ?? array(
'Loeschen fehlgeschlagen.')));
5066 $result = $this->move_media_record_to_folder(
$db, $id, $target);
5067 if ((
int)(
$result[
'ok'] ?? 0) === 1) {
5071 $errors[] =
'#' . $id .
': ' . (string)(
$result[
'msg'] ??
'Verschieben fehlgeschlagen.');
5075 $label = $action ===
'delete' ?
'geloescht' :
'verschoben';
5076 $msg = $affected .
' unbenutzte Bilder ' . $label .
'.';
5077 if ($skipped_used > 0) $msg .=
' ' . $skipped_used .
' verwendete Bilder uebersprungen.';
5078 if (!empty($errors)) $msg .=
' ' . count($errors) .
' Fehler.';
5080 $this->cms_json_response(array(
5081 'ok' => empty($errors) ? 1 : ($affected > 0 ? 1 : 0),
5082 'success' => empty($errors) || $affected > 0,
5083 'action' => $action,
5084 'source_folder' => $source,
5085 'target_folder' => $target,
5086 'checked' => $checked,
5087 'affected' => $affected,
5088 'skipped_used' => $skipped_used,
5089 'errors' => $errors,
5090 'rows' => $moved_rows,
5095 private function edit_image_file($file, $mime, array $op) {
5096 if (!extension_loaded(
'gd') || !function_exists(
'imagecreatetruecolor'))
return false;
5097 $img = $this->load_gd_image($file, $mime);
5098 if (!$img)
return false;
5100 $src_w = imagesx($img);
5101 $src_h = imagesy($img);
5102 if ($src_w <= 0 || $src_h <= 0) {
5107 $action = strtolower((
string)($op[
'action'] ??
'resize'));
5108 if ($action ===
'crop') {
5109 $x = max(0, min($src_w - 1, (
int)($op[
'x'] ?? 0)));
5110 $y = max(0, min($src_h - 1, (
int)($op[
'y'] ?? 0)));
5111 $w = max(1, min($src_w - $x, (
int)($op[
'width'] ?? $src_w)));
5112 $h = max(1, min($src_h - $y, (
int)($op[
'height'] ?? $src_h)));
5113 $dst = imagecreatetruecolor($w, $h);
5114 imagefill($dst, 0, 0, imagecolorallocate($dst, 255, 255, 255));
5115 imagecopyresampled($dst, $img, 0, 0, $x, $y, $w, $h, $w, $h);
5117 $w = (int)($op[
'width'] ?? 0);
5118 $h = (int)($op[
'height'] ?? 0);
5119 $ratio = !empty($op[
'ratio']);
5120 if ($w <= 0 && $h <= 0) {
5124 if ($w <= 0) $w = max(1, (
int)round($src_w * ($h / $src_h)));
5125 if ($h <= 0) $h = max(1, (
int)round($src_h * ($w / $src_w)));
5126 if ($ratio && $w > 0 && $h > 0) {
5127 $scale = min($w / $src_w, $h / $src_h);
5128 $w = max(1, (
int)round($src_w * $scale));
5129 $h = max(1, (
int)round($src_h * $scale));
5131 $dst = imagecreatetruecolor($w, $h);
5132 imagefill($dst, 0, 0, imagecolorallocate($dst, 255, 255, 255));
5133 imagecopyresampled($dst, $img, 0, 0, 0, 0, $w, $h, $src_w, $src_h);
5136 $ok = $this->save_gd_image($dst, $file, $mime);
5142 private function edit_media_json() {
5144 $payload = $this->request_json();
5146 if (isset($payload[
'ids']) && is_array($payload[
'ids'])) {
5147 foreach ($payload[
'ids'] as $id) {
5149 if ($id > 0) $ids[$id] = $id;
5152 $id = (int)($payload[
'id'] ?? 0);
5153 if ($id > 0) $ids[$id] = $id;
5155 if (!$ids) $this->cms_json_response(array(
'ok' => 0,
'msg' =>
'Keine Medien ausgewaehlt.'));
5157 $action = strtolower((
string)($payload[
'action'] ??
'resize'));
5158 if (!in_array($action, array(
'resize',
'crop'),
true)) $action =
'resize';
5159 if ($action ===
'crop' && count($ids) > 1) $this->cms_json_response(array(
'ok' => 0,
'msg' =>
'Zuschneiden ist nur fuer ein Bild moeglich.'));
5161 $db =
dbx()->get_system_obj(
'dbxDB');
5162 $this->ensure_cms_schema(
$db);
5164 foreach ($ids as $id) {
5165 $row =
$db->select1($this->dd_media, $id);
5166 if (!is_array($row) || (
int)($row[
'active'] ?? 0) !== 1 || $this->media_type($row) !==
'image')
continue;
5167 if (preg_match(
'/\.svg$/i', (
string)($row[
'file_name'] ??
'')))
continue;
5168 $file = $this->source_media_file($row);
5169 if ($file ===
'')
continue;
5170 $mime = (string)($row[
'mime'] ??
'');
5171 if (!$this->edit_image_file($file, $mime, array(
5172 'action' => $action,
5173 'x' => (
int)($payload[
'x'] ?? 0),
5174 'y' => (
int)($payload[
'y'] ?? 0),
5175 'width' => (
int)($payload[
'width'] ?? 0),
5176 'height' => (
int)($payload[
'height'] ?? 0),
5177 'ratio' => !empty($payload[
'ratio']),
5180 $old_thumb = $this->source_thumb_file($row);
5181 if ($old_thumb !==
'') @unlink($old_thumb);
5182 $size = @getimagesize($file);
5184 'size' => (
int)@filesize($file),
5185 'width' => is_array($size) ? (
int)$size[0] : 0,
5186 'height' => is_array($size) ? (
int)$size[1] : 0,
5187 'media_type' =>
'image',
5188 'thumb_file_path' =>
'',
5190 'thumb_height' => 0,
5192 $media_folder = $this->canonical_media_folder(trim((
string)($row[
'media_folder'] ??
'')) ?: $this->media_folder_from_path((
string)($row[
'file_path'] ??
''), $this->media_type($row)));
5193 $thumb = $this->create_media_thumbnail($file, $media_folder, (
string)($row[
'file_name'] ?? basename($file)), $mime);
5194 if ($thumb) $update = array_merge($update, $thumb);
5195 $db->update($this->dd_media, $update, $id);
5196 $done[] = $this->normalize_media_row(
$db->select1($this->dd_media, $id));
5199 if (!$done) $this->cms_json_response(array(
'ok' => 0,
'msg' =>
'Keine bearbeitbaren Bilder gefunden.'));
5200 foreach ($ids as $id) {
5201 $this->flush_media_by_media_id(
$db, (
int)$id);
5203 $this->cms_json_response(array(
'ok' => 1,
'success' =>
true,
'rows' => $done));
5204 }
catch (\Throwable $e) {
5205 $this->cms_json_response(array(
5208 'msg' =>
'Medienbearbeitung fehlgeschlagen: ' . $e->getMessage()
5213 private function template_options() {
5214 $dir =
dbx()->get_system_var(
'dbx_dir',
'') .
'/modules/dbxContent/tpl/htm/';
5215 if (!is_dir(
$dir))
$dir = dirname(__DIR__, 2) .
'/dbxContent/tpl/htm/';
5216 $files = glob(
$dir .
'c-*.htm');
5218 if (is_array($files)) {
5220 foreach ($files as $file) {
5221 $name = basename($file,
'.htm');
5222 $html .=
'<option value="' .
dbx()->esc($name) .
'">' .
dbx()->esc($name) .
'</option>';
5225 return $html ?:
'<option value="c-content">c-content</option>';
5228 private function rights_options() {
5229 $values = $this->rights_values();
5231 foreach ($values as $value => $label) {
5232 $html .=
'<option value="' .
dbx()->esc($value) .
'">' .
dbx()->esc($label) .
'</option>';
5237 private function rights_values() {
5239 'parent' =>
'parent - Rechte vom Parent',
5242 $db =
dbx()->get_system_obj(
'dbxDB');
5243 $rows =
$db->select(
'dbxUser_groups',
'',
'*',
'name');
5244 if (is_array($rows)) {
5245 foreach ($rows as $row) {
5246 if (!is_array($row))
continue;
5247 $name = trim((
string)($row[
'name'] ??
''));
5248 if ($name !==
'') $values[$name] = $name;
5254 private function media_template_options() {
5256 'image-hero' =>
'Bild Hero',
5257 'image-gallery' =>
'Bild Galerie',
5258 'image-teaser' =>
'Bild Header',
5259 'video-hero' =>
'Video Hero',
5260 'video-gallery' =>
'Video Galerie',
5261 'file-gallery' =>
'Datei Download',
5264 foreach ($values as $value => $label) {
5265 $html .=
'<option value="' .
dbx()->esc($value) .
'">' .
dbx()->esc($label) .
'</option>';
5270 private function options_html(array $values, $selected =
'') {
5272 foreach ($values as $value => $label) {
5273 $sel = ((string)$value === (string)$selected) ?
' selected' :
'';
5274 $html .=
'<option value="' .
dbx()->esc($value) .
'"' . $sel .
'>' .
dbx()->esc($label) .
'</option>';
5279 private function content_template_options($with_parent =
true) {
5280 $html = $with_parent ?
'<option value="parent">parent - vom Parent</option>' :
'';
5281 $html .= $this->template_options();
5285 private function hero_template_values() {
5287 'parent' =>
'parent - vom Parent',
5288 'none' =>
'Ohne Hero',
5289 'image-hero' =>
'Bild Hero',
5290 'video-hero' =>
'Video Hero',
5294 private function hero_template_options() {
5295 return $this->options_html($this->hero_template_values());
5298 private function hero_variant_values() {
5300 'parent' =>
'parent - vom Parent',
5301 'original' =>
'Original',
5302 'yellow' =>
'gelblich',
5303 'green' =>
'gruenlich',
5304 'blue' =>
'blaeulich',
5305 'red' =>
'roetlich',
5308 'blackwhite' =>
'schwarz/weiss',
5309 'monochrome' =>
'monochrom',
5313 private function hero_variant_options() {
5314 return $this->options_html($this->hero_variant_values());
5317 private function hero_sticky_values() {
5319 'parent' =>
'parent - vom Parent',
5320 '0' =>
'Nicht sticky',
5325 private function hero_scroll_layer_values() {
5327 'parent' =>
'parent - vom Parent',
5328 'under' =>
'Scroll laeuft drunter',
5329 'over' =>
'Scroll laeuft drueber',
5333 private function gallery_template_values() {
5335 'image-gallery' =>
'Bildergalerie',
5336 'video-gallery' =>
'Video Galerie',
5337 'carousel3' =>
'Carousel 3',
5338 'cols3' =>
'3 Spalten',
5342 private function gallery_template_options() {
5343 return $this->options_html($this->gallery_template_values());
5346 private function gallery_image_size_values() {
5348 '800x600' =>
'4:3 - Standard (800 x 600)',
5349 '1200x900' =>
'4:3 - gross (1200 x 900)',
5350 '1024x768' =>
'4:3 - klassisch (1024 x 768)',
5351 '1600x1200' =>
'4:3 - hochaufloesend (1600 x 1200)',
5352 '1280x720' =>
'16:9 - breit (1280 x 720)',
5353 '1920x1080' =>
'16:9 - Full HD (1920 x 1080)',
5354 '1200x675' =>
'16:9 - Web (1200 x 675)',
5355 '1080x1080' =>
'1:1 - Quadrat (1080 x 1080)',
5356 '1200x1200' =>
'1:1 - Quadrat gross (1200 x 1200)',
5357 '1080x1350' =>
'4:5 - Portrait (1080 x 1350)',
5358 '900x1200' =>
'3:4 - Portrait (900 x 1200)',
5359 '1600x900' =>
'16:9 - gross (1600 x 900)',
5360 '2560x1440' =>
'16:9 - QHD (2560 x 1440)',
5364 private function gallery_lightbox_width_values() {
5366 '60%' =>
'60% - kompakt',
5367 '70%' =>
'70% - mittel',
5368 '80%' =>
'80% - Standard',
5369 '90%' =>
'90% - breit',
5370 '95%' =>
'95% - sehr breit',
5371 '70vw' =>
'70vw - Viewport mittel',
5372 '80vw' =>
'80vw - Viewport Standard',
5373 '90vw' =>
'90vw - Viewport breit',
5374 '960px' =>
'960px - kleine Desktopbreite',
5375 '1200px' =>
'1200px - Desktop',
5376 '1440px' =>
'1440px - grosser Desktop',
5380 private function gallery_overflow_values() {
5383 'scroll' =>
'scroll',
5384 'laufband' =>
'laufband',
5385 'slider' =>
'slider',
5386 'tutorial' =>
'Tutorial Slideshow',
5390 private function gallery_overflow_options() {
5391 return $this->options_html($this->gallery_overflow_values());
5394 private function gallery_click_values() {
5396 'lightbox' =>
'Lightbox',
5397 'swiper-coverflow' =>
'Swiper Coverflow',
5398 'swiper-cube' =>
'Swiper Cube',
5399 'swiper-cards' =>
'Swiper Cards',
5400 'swiper-3d' =>
'Swiper 3D-Slider',
5401 'viewerjs' =>
'Viewer.js',
5402 'blueimp' =>
'blueimp Gallery',
5403 'photoswipe' =>
'PhotoSwipe',
5404 'deepzoom' =>
'Deep-Zoom-Viewer',
5406 'newtab' =>
'Neues Fenster',
5407 'none' =>
'Kein Klick',
5411 private function gallery_click_options() {
5412 return $this->options_html($this->gallery_click_values());
5415 private function meta_robots_values() {
5417 'index,follow' =>
'index, follow (Standard)',
5418 'noindex,follow' =>
'noindex, follow',
5419 'index,nofollow' =>
'index, nofollow',
5420 'noindex,nofollow' =>
'noindex, nofollow',
5424 private function render_page_form() {
5425 $form =
dbx()->get_system_obj(
'dbxForm');
5426 $form->init(
'cms-page',
'cms-admin-page-form');
5427 $form->_dd = dbxContentLng::ddContent();
5428 $form->_data = array(
5430 'template' =>
'parent',
5431 'hero_template' =>
'parent',
5432 'hero_image_id' =>
'parent',
5433 'hero_margin_top' =>
'parent',
5434 'hero_height' =>
'parent',
5435 'hero_variant' =>
'parent',
5436 'hero_sticky' =>
'parent',
5437 'hero_scroll_layer' =>
'parent',
5438 'gallery_template' =>
'image-gallery',
5439 'gallery_visible_count' =>
'3',
5440 'gallery_image_size' =>
'original',
5441 'gallery_lightbox_width' =>
'100vw',
5442 'gallery_overflow' =>
'grid',
5443 'gallery_click_behavior' =>
'lightbox',
5444 'meta_robots' =>
'index,follow',
5446 'seo_image_id' => 0,
5448 $form->add_fld(
'id',
'dbxContent_admin|cms-field-hidden', data: array(
'cms_field' =>
'id'));
5449 $form->add_fld(
'folder',
'dbxContent_admin|cms-field-hidden', data: array(
'cms_field' =>
'folder'));
5450 $form->add_fld(
'title',
'dbxContent_admin|cms-field-text', label:
'Titel', rules:
'varchar|min=1', data: array(
'cms_field' =>
'title'));
5451 $form->add_fld(
'permalink',
'dbxContent_admin|cms-field-text', label:
'Permalink', rules:
'parameter+/', data: array(
'cms_field' =>
'permalink'));
5452 $form->add_fld(
'activ',
'dbxContent_admin|cms-field-select', label:
'Status', rules:
'int', data: array(
'cms_field' =>
'activ'), options: array(
'1' =>
'Aktiv',
'0' =>
'Inaktiv'));
5453 $form->add_fld(
'template',
'dbxContent_admin|cms-field-select', label:
'Content-Template', rules:
'varchar', data: array(
'cms_field' =>
'template'), options: $this->content_template_values());
5454 $form->add_fld(
'description',
'dbxContent_admin|cms-field-textarea', label:
'Beschreibung', rules:
'varchar', data: array(
'cms_field' =>
'description'), placeholder:
'Kurzbeschreibung der Seite, max. 254 Zeichen.');
5455 $form->add_fld(
'content',
'dbxContent_admin|cms-field-textarea-hidden', label:
'Inhalt', rules:
'text', data: array(
'cms_field' =>
'content'));
5456 return $form->run();
5459 private function content_template_values() {
5460 $values = array(
'parent' =>
'parent - vom Parent');
5461 $dir =
dbx()->get_system_var(
'dbx_dir',
'') .
'/modules/dbxContent/tpl/htm/';
5462 if (!is_dir(
$dir))
$dir = dirname(__DIR__, 2) .
'/dbxContent/tpl/htm/';
5463 $files = glob(
$dir .
'c-*.htm');
5464 if (is_array($files)) {
5466 foreach ($files as $file) {
5467 $name = basename($file,
'.htm');
5468 $values[$name] = $name;
5471 if (count($values) === 1) $values[
'c-content'] =
'c-content';
5475 private function render_folder_form() {
5476 $form =
dbx()->get_system_obj(
'dbxForm');
5477 $form->init(
'cms-folder',
'cms-admin-folder-form');
5478 $form->_dd = dbxContentLng::ddFolder();
5479 $form->_data = array(
5481 'group_read' =>
'parent',
5482 'template' =>
'parent',
5483 'hero_template' =>
'parent',
5484 'hero_image_id' =>
'parent',
5485 'hero_margin_top' =>
'parent',
5486 'hero_height' =>
'parent',
5487 'hero_variant' =>
'parent',
5488 'hero_sticky' =>
'parent',
5489 'hero_scroll_layer' =>
'parent',
5491 $form->add_fld(
'id',
'dbxContent_admin|cms-field-folder-hidden', data: array(
'cms_field' =>
'id'));
5492 $form->add_fld(
'name',
'dbxContent_admin|cms-field-folder-text', label:
'Name', rules:
'varchar|min=1', data: array(
'cms_field' =>
'name'));
5493 $form->add_fld(
'parent_id',
'dbxContent_admin|cms-field-folder-select', label:
'Zuordnung', rules:
'int', data: array(
'cms_field' =>
'parent_id'), options: array(
'0' =>
'Root / erste Ebene'));
5494 $form->add_fld(
'template',
'dbxContent_admin|cms-field-folder-select', label:
'Content-Template', rules:
'varchar', data: array(
'cms_field' =>
'template'), options: $this->content_template_values());
5495 $form->add_fld(
'group_read',
'dbxContent_admin|cms-field-folder-rights', label:
'Leserechte', rules:
'varchar', data: array(
'cms_field' =>
'group_read'), options: $this->rights_values());
5496 return $form->run();
5499 private function render_settings_form() {
5500 $form =
dbx()->get_system_obj(
'dbxForm');
5501 $form->init(
'cms-settings',
'cms-admin-settings-panels');
5502 $form->_dd = dbxContentLng::ddContent();
5503 $form->_data = array(
5504 'hero_template' =>
'parent',
5505 'hero_image_id' =>
'parent',
5506 'hero_margin_top' =>
'parent',
5507 'hero_height' =>
'parent',
5508 'hero_variant' =>
'parent',
5509 'hero_sticky' =>
'parent',
5510 'hero_scroll_layer' =>
'parent',
5511 'gallery_template' =>
'image-gallery',
5512 'gallery_visible_count' =>
'3',
5513 'gallery_image_size' =>
'original',
5514 'gallery_lightbox_width' =>
'100vw',
5515 'gallery_overflow' =>
'grid',
5516 'gallery_click_behavior' =>
'lightbox',
5518 $form->add_fld(
'hero_template',
'dbxContent_admin|cms-field-select', label:
'Template', rules:
'varchar', data: array(
'cms_field' =>
'hero_template'), options: $this->hero_template_values());
5519 $form->add_fld(
'hero_image_id',
'dbxContent_admin|cms-field-hidden', rules:
'varchar', data: array(
'cms_field' =>
'hero_image_id'));
5520 $form->add_fld(
'hero_margin_top',
'dbxContent_admin|cms-field-text', label:
'Abstand oben', rules:
'varchar', data: array(
'cms_field' =>
'hero_margin_top'));
5521 $form->add_fld(
'hero_height',
'dbxContent_admin|cms-field-text', label:
'Hoehe', rules:
'varchar', data: array(
'cms_field' =>
'hero_height'));
5522 $form->add_fld(
'hero_variant',
'dbxContent_admin|cms-field-select', label:
'Variante', rules:
'varchar', data: array(
'cms_field' =>
'hero_variant'), options: $this->hero_variant_values());
5523 $form->add_fld(
'hero_sticky',
'dbxContent_admin|cms-field-select', label:
'Hero sticky', rules:
'varchar', data: array(
'cms_field' =>
'hero_sticky'), options: $this->hero_sticky_values());
5524 $form->add_fld(
'hero_scroll_layer',
'dbxContent_admin|cms-field-select', label:
'Scroll-Ebene', rules:
'varchar', data: array(
'cms_field' =>
'hero_scroll_layer'), options: $this->hero_scroll_layer_values());
5525 $form->add_fld(
'gallery_image_size',
'dbxContent_admin|cms-field-select', label:
'Bildgroesse', rules:
'varchar', data: array(
'cms_field' =>
'gallery_image_size'), options: $this->gallery_image_size_values());
5526 $form->add_fld(
'gallery_lightbox_width',
'dbxContent_admin|cms-field-select', label:
'Bildbreite Lightbox', rules:
'varchar', data: array(
'cms_field' =>
'gallery_lightbox_width'), options: $this->gallery_lightbox_width_values());
5527 $form->add_fld(
'gallery_overflow',
'dbxContent_admin|cms-field-select', label:
'Overflow', rules:
'varchar', data: array(
'cms_field' =>
'gallery_overflow'), options: $this->gallery_overflow_values());
5528 $form->add_fld(
'gallery_click_behavior',
'dbxContent_admin|cms-field-select', label:
'Klick', rules:
'varchar', data: array(
'cms_field' =>
'gallery_click_behavior'), options: $this->gallery_click_values());
5529 return $form->run();
5532 public function run($action =
'cms') {
5533 $this->apply_cms_lng_context();
5536 return $this->tree_json();
5537 case 'cms_lng_coverage':
5538 return $this->lng_coverage_json();
5539 case 'cms_lng_preview':
5540 return $this->lng_preview_json();
5541 case 'cms_lng_provision':
5542 return $this->lng_provision_json();
5543 case 'cms_lng_provision_tree':
5544 return $this->lng_provision_tree_json();
5545 case 'cms_lng_reset_sync':
5546 return $this->lng_reset_sync_json();
5547 case 'cms_lng_delete_preview':
5548 return $this->lng_delete_preview_json();
5550 return $this->page_json();
5552 return $this->save_json();
5553 case 'cms_new_page':
5554 return $this->new_page_json();
5555 case 'cms_duplicate_page':
5556 return $this->duplicate_page_json();
5557 case 'cms_new_folder':
5558 return $this->new_folder_json();
5559 case 'cms_save_folder':
5560 return $this->save_folder_json();
5561 case 'cms_delete_folder':
5562 return $this->delete_folder_json();
5563 case 'cms_delete_page':
5564 return $this->delete_page_json();
5565 case 'cms_move_node':
5566 return $this->move_node_json();
5568 return $this->media_json();
5569 case 'cms_media_process':
5570 return $this->media_process();
5572 return $this->upload_json();
5573 case 'cms_external_video':
5574 return $this->external_video_json();
5575 case 'cms_media_folders':
5576 return $this->media_folders_json();
5577 case 'cms_media_folder_create':
5578 return $this->media_folder_create_json();
5579 case 'cms_media_folder_delete':
5580 return $this->media_folder_delete_json();
5581 case 'cms_media_folder_rename':
5582 return $this->media_folder_rename_json();
5583 case 'cms_media_move':
5584 return $this->media_move_json();
5585 case 'cms_media_unused':
5586 return $this->media_unused_json();
5587 case 'cms_remove_media':
5588 return $this->remove_media_json();
5589 case 'cms_delete_media':
5590 return $this->delete_media_json();
5591 case 'cms_edit_media':
5592 return $this->edit_media_json();
5593 case 'cms_set_media_slot':
5594 return $this->set_media_slot_json();
5595 case 'cms_assign_media':
5596 return $this->assign_media_json();
5597 case 'cms_sort_media':
5598 return $this->sort_media_json();
5599 case 'cms_mod_catalog':
5600 return $this->mod_catalog_json();
5601 case 'cms_mod_modules':
5602 return $this->mod_modules_json();
5605 return $this->render_cms();