39 private $content_folders = array();
40 private $media_usages = array();
42 private function render_section($title, $subtitle, $content, $barActions =
'') {
43 return dbx()->get_system_obj(
'dbxTPL')->get_tpl(
'dbxContent_admin|content-admin-section', array(
45 'subtitle' => $subtitle,
46 'content' => $content,
47 'bar_actions' => $barActions,
51 private function is_ajax_request() {
52 return (
int)
dbx()->get_system_var(
'dbx_ajax', 0,
'int') === 1;
55 private function render_section_or_ajax($title, $subtitle, $content, $barActions =
'') {
56 if ($this->is_ajax_request()) {
60 return $this->render_section($title, $subtitle, $content, $barActions);
63 private function base_url($action, $params = array()) {
64 $url =
'?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=' . rawurlencode((
string)$action);
65 foreach ($params as $key => $value) {
66 $url .=
'&' . rawurlencode((
string)$key) .
'=' . rawurlencode((
string)$value);
71 private function request_json() {
72 $raw = file_get_contents(
'php://input');
73 $data = $raw ? json_decode($raw,
true) : array();
74 return is_array($data) ? $data : array();
77 private function content_grid_folder_editor_values() {
78 if (!$this->content_folders) {
79 $db =
dbx()->get_system_obj(
'dbxDB');
80 $this->load_content_folders_map(
$db);
83 $pairs = array(
'0=/');
84 $folderIds = array_keys($this->content_folders);
85 usort($folderIds,
function ($a, $b) {
86 return strcmp($this->content_folder_path($a), $this->content_folder_path($b));
89 foreach ($folderIds as $folderId) {
90 $path = $this->content_folder_path($folderId);
91 $label = str_replace(array(
'~',
'=',
';'), array(
' ',
' ',
' '), $path);
92 $pairs[] = (int)$folderId .
'=' . $label;
95 return implode(
'~', $pairs);
98 private function content_grid_cols() {
99 $folderValues = $this->content_grid_folder_editor_values();
101 return implode(
',', array(
102 'id[ID]:number:p:width=72;hozAlign=center;headerHozAlign=center',
103 'title[Titel]:text::width=240',
104 'permalink[Permalink]:text::width=200',
105 'folder[Ordner]:text::editor=list;values=' . $folderValues .
';width=220',
106 'activ[Aktiv]:text::editor=list;values=0=Inaktiv~1=Aktiv;width=110',
107 'update_date[Geaendert]:text:p:width=170',
111 private function load_content_folders_map(
$db) {
112 $folderRows =
$db->select(
113 dbxContentLng::ddFolder(),
123 $this->content_folders = array();
124 if (is_array($folderRows)) {
125 foreach ($folderRows as $folderRow) {
126 $folderId = (int)($folderRow[
'id'] ?? 0);
128 $this->content_folders[$folderId] = $folderRow;
134 private function enrich_content_row(array $row) {
135 $row[
'folder_path'] = $this->content_folder_path($row[
'folder'] ?? 0);
136 if (isset($row[
'update_date'])) {
137 $row[
'update_date'] = preg_replace(
'/\.\d+$/',
'', trim((
string)$row[
'update_date']));
139 $id = (int)($row[
'id'] ?? 0);
141 $row[
'profile_link'] = $this->base_url(
'edit', array(
'cid' => $id)) .
'&dbx_window=1';
142 $row[
'show_link'] =
'?dbx_modul=dbxContent&dbx_run1=show&dbx_cid=' . $id .
'&dbx_window=1';
147 private function grid_sort_from_request() {
150 $allowed = array(
'id',
'title',
'permalink',
'activ',
'update_date',
'folder');
151 $sort =
dbx()->get_request_var(
'dbx_sorters',
'',
'*');
153 $sorters = json_decode($sort,
true);
154 if (is_array($sorters) && isset($sorters[0][
'field'])) {
155 $field = (string)$sorters[0][
'field'];
156 if (in_array(
$field, $allowed,
true)) {
158 $rdesc = strtolower((
string)($sorters[0][
'dir'] ??
'asc')) ===
'desc' ?
'DESC' :
'ASC';
162 return array($rsort, $rdesc);
165 private function content_grid_read() {
166 $db =
dbx()->get_system_obj(
'dbxDB');
167 $dd = dbxContentLng::ddContent();
168 $this->load_content_folders_map(
$db);
170 $page = (int)
dbx()->get_request_var(
'page', 0,
'int');
171 $size = (int)
dbx()->get_request_var(
'size', 0,
'int');
173 $search =
dbx()->get_request_var(
'dbx_search',
'',
'sqlsearch|max=128');
174 $where =
$db->build_search_where($dd, $search, array(
'title',
'permalink'), array(
'id'),
'contains');
175 list($rsort, $rdesc) = $this->grid_sort_from_request();
177 $flds = array(
'id',
'title',
'permalink',
'folder',
'activ',
'update_date');
179 if ($page > 0 && $size > 0) {
180 $size = max(1, min(200, $size));
181 $rpos = ($page - 1) * $size;
183 $rows =
$db->select($dd, $where, $flds, $rsort, $rdesc,
'', $size, $rpos);
185 $rows =
$db->select($dd, $where, $flds, $rsort, $rdesc,
'', 0, 0);
186 $count = is_array($rows) ? count($rows) : 0;
188 if (!is_array($rows)) {
193 foreach ($rows as $row) {
194 if (!is_array($row)) {
197 $out[] = $this->enrich_content_row($row);
201 $last_page = ($page > 0 && $size > 0) ? max(1, (
int)ceil(
$count / $size)) : 1;
203 dbx()->json_response(array(
206 'last_page' => $last_page,
209 'server_time' => (
new \DateTime())->format(
'Y-m-d H:i:s.v'),
213 private function content_grid_sync() {
214 $db =
dbx()->get_system_obj(
'dbxDB');
215 $dd = dbxContentLng::ddContent();
216 $this->load_content_folders_map(
$db);
218 $last_update = trim((
string)
dbx()->get_request_var(
'last_update',
''));
219 if ($last_update ===
'') {
220 $last_update = trim((
string)
dbx()->get_modul_var(
'last_update',
''));
222 if ($last_update ===
'') {
223 $last_update =
'1970-01-01 00:00:00';
226 $where =
"update_date > '" . str_replace(
"'",
"''", $last_update) .
"'";
227 $flds = array(
'id',
'title',
'permalink',
'folder',
'activ',
'update_date');
228 $rows =
$db->select($dd, $where, $flds,
'update_date',
'ASC',
'', 0, 0);
229 if (!is_array($rows)) {
234 foreach ($rows as $row) {
235 if (!is_array($row)) {
238 $out[] = $this->enrich_content_row($row);
241 dbx()->json_response(array(
243 'count' => count($out),
245 'server_time' => (
new \DateTime())->format(
'Y-m-d H:i:s.v'),
249 private function content_grid_save() {
250 $db =
dbx()->get_system_obj(
'dbxDB');
251 $cms =
dbx()->get_include_obj(
'dbxContent_cms');
252 if (is_object(
$cms) && method_exists(
$cms,
'ensure_schema')) {
256 $payload = $this->request_json();
257 $rows = is_array($payload[
'rows'] ??
null) ? $payload[
'rows'] : array();
258 $dd = dbxContentLng::ddContent();
259 $allowed = array_flip(array(
'title',
'permalink',
'activ',
'folder'));
262 $this->load_content_folders_map(
$db);
264 foreach ($rows as $row) {
265 if (!is_array($row)) {
268 $id = (int)($row[
'id'] ?? 0);
274 foreach ($allowed as
$field => $_) {
275 if (!array_key_exists(
$field, $row)) {
288 if (array_key_exists(
'folder', $data)) {
289 $folderId = (int)$data[
'folder'];
290 if ($folderId < 0 || ($folderId > 0 && !isset($this->content_folders[$folderId]))) {
295 if (
$db->update($dd, $data, $id) >= 0) {
296 dbxContentLngSync::afterPageSave(
$db, $id,
false);
297 dbxContentPageCache::invalidateContent($id);
298 dbxContentPageCache::invalidateAllMenus();
299 $fresh =
$db->select1($dd, $id,
'id,title,permalink,folder,activ,update_date', 0);
300 if (is_array($fresh)) {
301 $saved[] = $this->enrich_content_row($fresh);
306 $this->load_content_folders_map(
$db);
308 dbx()->json_response(array(
315 private function content_grid_delete() {
316 $payload = $this->request_json();
317 $id = (int)($payload[
'id'] ?? 0);
319 dbx()->json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Keine Seiten-ID.'),
true);
322 $cms =
dbx()->get_include_obj(
'dbxContent_cms');
323 $result = is_object(
$cms) ?
$cms->delete_page_record($id) : array(
'ok' => 0,
'errors' => array(
'CMS nicht verfuegbar.'));
324 $errors = is_array(
$result[
'errors'] ??
null) ?
$result[
'errors'] : array();
326 dbx()->json_response(array(
327 'ok' => (
int)(
$result[
'ok'] ?? 0),
328 'success' => (
int)(
$result[
'ok'] ?? 0) === 1,
329 'msg' => implode(
' ', $errors),
333 private function report_content_grid() {
334 $oReport =
dbx()->get_system_obj(
'dbxReport');
335 $db =
dbx()->get_system_obj(
'dbxDB');
336 $dd = dbxContentLng::ddContent();
337 $all = (int)
$db->count($dd);
338 $active = (int)
$db->count($dd,
'activ = 1');
340 $oReport->init(
'content-admin-content-grid',
'content-admin-content-grid');
341 $oReport->add_rep(
'shell_panel_class',
'dbx-grid dbx-content-list-grid');
342 $oReport->add_rep(
'frame_use_form',
'0');
343 $oReport->add_rep(
'bar_title',
'Content');
344 $oReport->add_rep(
'bar_subtitle',
'Seiten der aktuell gewaehlten Sprache.');
345 $oReport->_mode =
'tabulurator';
346 $oReport->_rrows = 600;
347 $oReport->_grid_id =
'dbx_content_list_grid';
348 $oReport->_grid_cols = $this->content_grid_cols();
349 $oReport->_grid_layout =
'fitDataStretch';
350 $oReport->_grid_read_url = $this->base_url(
'content_grid_read');
351 $oReport->_grid_save_url = $this->base_url(
'content_grid_save');
352 $oReport->_grid_delete_url = $this->base_url(
'content_grid_delete');
353 $oReport->_grid_sync_url = $this->base_url(
'content_grid_sync');
354 $oReport->_grid_synctime =
'1.5';
355 $oReport->add_grid_stats(array(
356 array(
'label' =>
'Seiten',
'value' => (
string)$all),
357 array(
'label' =>
'Aktiv',
'value' => (
string)$active,
'tone' =>
'ok'),
358 array(
'label' =>
'Inaktiv',
'value' => (
string)max(0, $all - $active)),
359 ),
'Content Kennzahlen');
360 $oReport->add_obj(
'bar_actions',
'obj-value',
361 '<a class="btn btn-outline-primary btn-sm" href="' .
dbx()->esc($this->base_url(
'edit')) .
'">' .
362 '<i class="bi bi-pencil-square"></i><span>Content CMS</span></a>'
365 return $oReport->run();
368 private function report_rows($action, array
$fields, array $rows, $msgSuccess =
'', $msgError =
'') {
370 $report->init(
'content-admin-report');
371 $report->_action =
'?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=' . rawurlencode($action);
372 $report->_create_sel_flds = 0;
374 $report->_mode =
'table';
375 $report->_rdata = $rows;
376 $report->_rcount = count($rows);
377 $report->_rrows = count($rows) > 0 ? count($rows) : 1;
380 if ($msgSuccess !==
'') {
381 $report->_msg_success = $msgSuccess;
383 if ($msgError !==
'') {
384 $report->_msg_error = $msgError;
387 if ($action ===
'list_folder') {
388 $report->set_callback_owner($this);
389 $report->set_callback(
'next_record',
'folder_next_record');
390 $report->set_callback(
'row_action_data',
'folder_row_action_data');
391 $report->_create_row_edit = 1;
392 $report->_create_row_delete = 1;
393 $report->_msg_confirm_delete =
'Diesen Content-Ordner wirklich loeschen?';
394 $report->set_tabel_tpl(
'tpl_row_edit',
'modul|content-admin-row-edit');
395 } elseif ($action ===
'list_media') {
396 $report->set_callback_owner($this);
397 $report->set_callback(
'next_record',
'media_next_record');
398 $report->set_callback(
'row_action_data',
'media_row_action_data');
399 $report->_create_row_delete = 1;
400 $report->_msg_confirm_delete =
'Dieses Medium wirklich loeschen?';
401 $report->set_tabel_tpl(
'tpl_row_delete',
'modul|content-admin-row-delete-media');
402 } elseif ($action ===
'templates') {
403 $report->set_callback_owner($this);
404 $report->set_callback(
'row_action_data',
'template_row_action_data');
405 $report->_create_row_edit = 1;
406 $report->_create_row_delete = 1;
407 $report->_fld_id =
'name';
408 $report->_msg_confirm_delete =
'Template-Datei wirklich loeschen?';
409 $report->_rpt_format[
'modified'] =
'php-datetime-usr';
410 $report->_table_buttons =
'left';
411 $report->set_tabel_tpl(
'tpl_row_edit',
'modul|content-admin-row-edit');
412 $report->set_tabel_tpl(
'tpl_row_delete',
'modul|content-admin-row-delete-template');
415 return $report->run();
418 private function content_folder_path($folderId) {
419 $folderId = (int)$folderId;
420 if ($folderId <= 0) {
427 while ($folderId > 0 && isset($this->content_folders[$folderId]) && !isset($seen[$folderId])) {
428 $seen[$folderId] =
true;
429 $folder = $this->content_folders[$folderId];
430 $name = trim((
string)($folder[
'name'] ??
''),
" \t\n\r\0\x0B/");
433 array_unshift($parts, $name);
436 $parentId = (int)($folder[
'parent_id'] ?? 0);
437 if ($parentId === $folderId) {
440 $folderId = $parentId;
443 return '/' . implode(
'/', $parts);
447 if (!is_array($record)) {
451 $record[
'folder_path'] = $this->content_folder_path($record[
'folder'] ?? 0);
452 if (isset($record[
'update_date'])) {
453 $record[
'update_date'] = preg_replace(
'/\.\d+$/',
'', trim((
string)$record[
'update_date']));
459 if (!is_array($data) || !isset($data[
'data']) || !is_array($data[
'data'])) {
463 $type = (string)($data[
'type'] ??
'');
464 $rid = (int)($data[
'data'][
'rid'] ?? 0);
466 if ($type ===
'edit') {
467 $url =
'?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=edit&cid=' . $rid .
'&dbx_window=1';
468 $data[
'data'][
'action'] = $url;
469 $data[
'data'][
'edit_url'] = htmlspecialchars($url, ENT_QUOTES,
'UTF-8');
470 $data[
'data'][
'edit_title'] = htmlspecialchars(
'Content im CMS bearbeiten', ENT_QUOTES,
'UTF-8');
471 $data[
'data'][
'class'] =
'openWin';
472 } elseif ($type ===
'delete') {
473 $data[
'data'][
'action'] =
474 '?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=list_content';
481 if (!is_array($record)) {
485 $id = (int)($record[
'id'] ?? 0);
486 $type = strtolower(trim((
string)($record[
'media_type'] ??
'')));
487 $provider = strtolower(trim((
string)($record[
'provider'] ??
'')));
488 $providerId = trim((
string)($record[
'provider_id'] ??
''));
489 $title = trim((
string)($record[
'title'] ?? $record[
'file_name'] ??
'Medium'));
490 $preview =
'<span class="text-muted"><i class="bi bi-file-earmark"></i></span>';
492 if ($id > 0 && in_array($type, array(
'image',
'video',
'external_video'),
true)) {
493 $mediaUrl =
'index.php?dbx_modul=dbxContent&dbx_run1=media&dbx_mid=' . $id;
494 $url =
'?dbx_modul=dbxContent_admin&dbx_run1=media_view&rid=' . $id .
'&dbx_window=1';
495 $thumbUrl = $mediaUrl .
'&dbx_thumb=1';
496 if ($type ===
'external_video' && $provider ===
'youtube' && preg_match(
'/^[A-Za-z0-9_-]{11}$/', $providerId)) {
497 $thumbUrl =
'https://img.youtube.com/vi/' . rawurlencode($providerId) .
'/hqdefault.jpg';
500 '<a class="openWin dbx-win" href="' . htmlspecialchars($url, ENT_QUOTES,
'UTF-8') .
'" ' .
501 'data-url="' . htmlspecialchars($url, ENT_QUOTES,
'UTF-8') .
'" ' .
502 'data-title="' . htmlspecialchars($title, ENT_QUOTES,
'UTF-8') .
'" ' .
503 'title="' . htmlspecialchars($title, ENT_QUOTES,
'UTF-8') .
'">' .
504 '<img src="' . htmlspecialchars($thumbUrl, ENT_QUOTES,
'UTF-8') .
'" ' .
505 'alt="' . htmlspecialchars($title, ENT_QUOTES,
'UTF-8') .
'" loading="lazy" ' .
506 'class="rounded border bg-light" style="width:96px;height:64px;object-fit:cover">' .
510 $record[
'thumbnail'] = $preview;
511 $usages = $this->media_usages[$id] ?? array();
512 $usageHtml = array();
513 foreach ($usages as $usage) {
514 $lng = strtolower(trim((
string)($usage[
'lng'] ??
'')));
515 $usageId = (int)($usage[
'id'] ?? 0);
516 $type = (string)($usage[
'type'] ??
'page');
517 $label = trim((
string)($usage[
'title'] ??
''));
518 $url =
'?dbx_modul=dbxContent_admin&dbx_run1=cms&dbx_lng=' . rawurlencode($lng) .
519 ($type ===
'folder' ?
'&fid=' :
'&cid=') . $usageId .
'&dbx_window=1';
521 '<a class="openWin dbx-win d-block text-decoration-none mb-1" ' .
522 'href="' . htmlspecialchars($url, ENT_QUOTES,
'UTF-8') .
'" ' .
523 'data-url="' . htmlspecialchars($url, ENT_QUOTES,
'UTF-8') .
'" ' .
524 'data-title="' . htmlspecialchars($label, ENT_QUOTES,
'UTF-8') .
'" ' .
525 'title="' . htmlspecialchars($label, ENT_QUOTES,
'UTF-8') .
'">' .
526 '<span class="badge text-bg-secondary me-1">' . htmlspecialchars(strtoupper($lng), ENT_QUOTES,
'UTF-8') .
'</span>' .
527 htmlspecialchars($label, ENT_QUOTES,
'UTF-8') .
530 $record[
'usage'] = count($usageHtml)
531 ? implode(
'', $usageHtml)
532 :
'<span class="text-muted">Nicht verwendet</span>';
533 $record[
'_usage_count'] = count($usages);
538 if (!is_array($data) || !isset($data[
'data']) || !is_array($data[
'data'])) {
542 if ((
string)($data[
'type'] ??
'') !==
'delete') {
546 $rid = (int)($data[
'data'][
'rid'] ?? 0);
547 $used = count($this->media_usages[$rid] ?? array()) > 0;
548 $data[
'data'][
'delete_url'] =
549 '?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=list_media&dbx_do=row_delete&rid=' . $rid;
550 $data[
'data'][
'delete_class'] = $used ?
'disabled text-muted' :
'dbxAjax dbxConfirm text-danger';
551 $data[
'data'][
'delete_title'] = $used
552 ?
'Medium wird verwendet und kann nicht geloescht werden'
554 $data[
'data'][
'delete_disabled'] = $used ?
'aria-disabled="true" tabindex="-1"' :
'';
555 $data[
'data'][
'confirm'] = $report->_msg_confirm_delete;
560 private function add_media_usage(array &$map,
int $mediaId,
string $lng,
int $id,
string $title,
string $type =
'page'): void {
561 if ($mediaId <= 0 || $id <= 0 || $lng ===
'') {
565 $key = $type .
'|' . $lng .
'|' . $id;
566 $map[$mediaId][$key] = array(
569 'title' => ($type ===
'folder' ?
'Ordner: ' :
'') . ($title !==
'' ? $title : (
'#' . $id)),
574 private function load_media_usages(
$db): array {
576 $usageRows =
$db->select(
'dbxMediaUsage',
'active = 1',
'media_id,content_id,folder_id',
'media_id,id',
'ASC',
'', 0, 0, 0);
577 $usageRows = is_array($usageRows) ? $usageRows : array();
579 foreach (dbxContentLngSync::accessibleLngs() as $lng) {
580 $lng = strtolower(trim((
string)$lng));
585 $pages =
$db->select(
586 dbxContentLng::ddContent($lng),
588 'id,title,hero_image_id,content',
596 $folders =
$db->select(
597 dbxContentLng::ddFolder($lng),
599 'id,name,hero_image_id',
608 $pagesById = array();
609 foreach (is_array($pages) ? $pages : array() as $page) {
610 $pageId = (int)($page[
'id'] ?? 0);
614 $pagesById[$pageId] = $page;
616 $heroId = (int)($page[
'hero_image_id'] ?? 0);
618 $this->add_media_usage($map, $heroId, $lng, $pageId, (
string)($page[
'title'] ??
''),
'page');
620 if (preg_match_all(
'/data-cms-media-id=["\']?(\d+)/i', (
string)($page[
'content'] ??
''), $matches)) {
621 foreach ($matches[1] as $mediaId) {
622 $this->add_media_usage($map, (
int)$mediaId, $lng, $pageId, (
string)($page[
'title'] ??
''),
'page');
627 $foldersById = array();
628 foreach (is_array($folders) ? $folders : array() as $folder) {
629 $folderId = (int)($folder[
'id'] ?? 0);
630 if ($folderId <= 0) {
633 $foldersById[$folderId] = $folder;
634 $heroId = (int)($folder[
'hero_image_id'] ?? 0);
636 $this->add_media_usage($map, $heroId, $lng, $folderId, (
string)($folder[
'name'] ??
''),
'folder');
640 foreach ($usageRows as $usage) {
641 $mediaId = (int)($usage[
'media_id'] ?? 0);
642 $contentId = (int)($usage[
'content_id'] ?? 0);
643 $folderId = (int)($usage[
'folder_id'] ?? 0);
644 if ($contentId > 0 && isset($pagesById[$contentId])) {
645 $this->add_media_usage(
650 (
string)($pagesById[$contentId][
'title'] ??
''),
653 } elseif ($folderId > 0 && isset($foldersById[$folderId])) {
654 $this->add_media_usage(
659 (
string)($foldersById[$folderId][
'name'] ??
''),
666 foreach ($map as $mediaId => $items) {
667 $map[$mediaId] = array_values($items);
673 if (!is_array($record)) {
677 $record[
'folder_path'] = $this->content_folder_path($record[
'id'] ?? 0);
682 if (!is_array($data) || !isset($data[
'data']) || !is_array($data[
'data'])) {
686 $type = (string)($data[
'type'] ??
'');
687 $rid = (int)($data[
'data'][
'rid'] ?? 0);
689 if ($type ===
'edit') {
690 $url =
'?dbx_modul=dbxContent_admin&dbx_run1=cms&fid=' . $rid .
'&dbx_window=1';
691 $data[
'data'][
'action'] = $url;
692 $data[
'data'][
'edit_url'] = htmlspecialchars($url, ENT_QUOTES,
'UTF-8');
693 $data[
'data'][
'edit_title'] = htmlspecialchars(
'Content-Ordner im CMS bearbeiten', ENT_QUOTES,
'UTF-8');
694 $data[
'data'][
'class'] =
'openWin';
695 } elseif ($type ===
'delete') {
696 $data[
'data'][
'action'] =
697 '?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=list_folder';
703 private function report_content() {
704 return $this->report_content_grid();
707 private function report_folders() {
708 $db =
dbx()->get_system_obj(
'dbxDB');
710 $deleteId = (int)
dbx()->get_modul_var(
'rid', 0,
'int');
711 if (
dbx()->get_modul_var(
'dbx_do',
'',
'parameter') ===
'row_delete' && $deleteId > 0) {
712 $cms =
dbx()->get_include_obj(
'dbxContent_cms');
713 $cms->delete_folder_record($deleteId);
718 'folder_path' =>
'Ordner',
719 'template' =>
'Template',
720 'group_read' =>
'Leserechte',
724 dbxContentLng::ddFolder(),
726 'id,name,parent_id,template,group_read',
734 if (!is_array($rows)) {
738 $this->content_folders = array();
739 foreach ($rows as
$index => $row) {
740 $folderId = (int)($row[
'id'] ?? 0);
742 $this->content_folders[$folderId] = $row;
744 $rows[
$index][
'folder_path'] =
'';
747 return $this->report_rows(
'list_folder',
$fields, $rows);
750 private function report_media() {
751 $db =
dbx()->get_system_obj(
'dbxDB');
753 $this->media_usages = $this->load_media_usages(
$db);
754 $deleteId = (int)
dbx()->get_modul_var(
'rid', 0,
'int');
755 if (
dbx()->get_modul_var(
'dbx_do',
'',
'parameter') ===
'row_delete' && $deleteId > 0) {
756 $cms =
dbx()->get_include_obj(
'dbxContent_cms');
757 $cms->delete_media_record($deleteId);
758 $this->media_usages = $this->load_media_usages(
$db);
762 'thumbnail' =>
'Vorschau',
765 'media_type' =>
'Typ',
766 'media_folder' =>
'Ordner',
767 'provider' =>
'Provider',
768 'usage' =>
'Verwendung',
775 'id,title,media_type,file_name,media_folder,provider,provider_id,active',
783 if (!is_array($rows)) {
787 foreach ($rows as
$index => $row) {
788 $rows[
$index][
'thumbnail'] =
'';
789 $rows[
$index][
'usage'] =
'';
792 return $this->report_rows(
'list_media',
$fields, $rows);
795 private function media_view() {
796 $id = (int)
dbx()->get_modul_var(
'rid', 0,
'int');
797 $db =
dbx()->get_system_obj(
'dbxDB');
798 $row = $id > 0 ?
$db->select1(
'dbxMedia', $id) : array();
800 if (!is_array($row) || !count($row)) {
801 return dbx()->get_system_obj(
'dbxTPL')->get_tpl(
'dbx|alert-warning', array(
802 'msg' =>
'Medium nicht gefunden.',
806 $type = strtolower(trim((
string)($row[
'media_type'] ??
'file')));
807 $provider = strtolower(trim((
string)($row[
'provider'] ??
'')));
808 $providerId = trim((
string)($row[
'provider_id'] ??
''));
809 $title = trim((
string)($row[
'title'] ?? $row[
'file_name'] ?? (
'Medium #' . $id)));
810 $mime = trim((
string)($row[
'mime'] ??
''));
811 $mediaUrl =
'index.php?dbx_modul=dbxContent&dbx_run1=media&dbx_mid=' . $id;
814 if ($type ===
'image') {
816 '<img src="' . htmlspecialchars($mediaUrl, ENT_QUOTES,
'UTF-8') .
'" ' .
817 'alt="' . htmlspecialchars($title, ENT_QUOTES,
'UTF-8') .
'" ' .
818 'class="img-fluid rounded border bg-light" style="max-height:72vh;object-fit:contain">';
819 } elseif ($type ===
'video') {
820 $poster = !empty($row[
'thumb_file_path'])
821 ?
' poster="' . htmlspecialchars($mediaUrl .
'&dbx_thumb=1', ENT_QUOTES,
'UTF-8') .
'"'
824 '<video class="w-100 rounded border bg-dark" style="max-height:72vh" controls preload="metadata" playsinline' . $poster .
'>' .
825 '<source src="' . htmlspecialchars($mediaUrl, ENT_QUOTES,
'UTF-8') .
'"' .
826 ($mime !==
'' ?
' type="' . htmlspecialchars($mime, ENT_QUOTES,
'UTF-8') .
'"' :
'') .
'>' .
827 'Ihr Browser kann dieses Video nicht wiedergeben.</video>';
828 } elseif ($type ===
'external_video' && $provider ===
'youtube' && preg_match(
'/^[A-Za-z0-9_-]{11}$/', $providerId)) {
829 $embedUrl =
'https://www.youtube.com/embed/' . rawurlencode($providerId);
831 '<div class="ratio ratio-16x9"><iframe src="' . htmlspecialchars($embedUrl, ENT_QUOTES,
'UTF-8') .
'" ' .
832 'title="' . htmlspecialchars($title, ENT_QUOTES,
'UTF-8') .
'" ' .
833 'allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" ' .
834 'allowfullscreen></iframe></div>';
837 '<div class="text-center py-5"><i class="bi bi-file-earmark fs-1 d-block mb-3"></i>' .
838 '<a class="btn btn-primary" href="' . htmlspecialchars($mediaUrl, ENT_QUOTES,
'UTF-8') .
'">' .
839 '<i class="bi bi-box-arrow-up-right"></i> Datei anzeigen</a></div>';
842 return dbx()->get_system_obj(
'dbxTPL')->get_tpl(
'dbxContent_admin|content-admin-media-view', array(
843 'title' => htmlspecialchars($title, ENT_QUOTES,
'UTF-8'),
844 'media' => $mediaHtml,
845 'type' => htmlspecialchars($type, ENT_QUOTES,
'UTF-8'),
846 'folder' => htmlspecialchars((
string)($row[
'media_folder'] ??
''), ENT_QUOTES,
'UTF-8'),
847 'mime' => htmlspecialchars($mime, ENT_QUOTES,
'UTF-8'),
853 if (!is_array($data) || !isset($data[
'data']) || !is_array($data[
'data'])) {
857 $record = is_array($data[
'record'] ??
null) ? $data[
'record'] : array();
858 $type = (string)($data[
'type'] ??
'');
859 $tpl = trim((
string)($record[
'name'] ??
''));
861 if ($type ===
'edit') {
862 $modul = trim((
string)($record[
'modul'] ??
'dbxContent'));
863 $fileType = trim((
string)($record[
'type'] ??
'htm'));
865 $relPath =
'dbx/modules/' . $modul .
'/tpl/' . $fileType .
'/' . $tpl .
'.' . $fileType;
866 $url =
'?dbx_modul=dbxEditor&dbx_run1=edit&file=' . rawurlencode($relPath) .
'&dbx_window=1';
868 $data[
'data'][
'action'] = $url;
869 $data[
'data'][
'edit_url'] = htmlspecialchars($url, ENT_QUOTES,
'UTF-8');
870 $data[
'data'][
'edit_title'] = htmlspecialchars($modul .
'|' . $tpl, ENT_QUOTES,
'UTF-8');
871 $data[
'data'][
'class'] =
'openWin';
872 } elseif ($type ===
'delete' && $tpl !==
'') {
873 $data[
'data'][
'delete_url'] =
874 '?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=templates&dbx_do=row_delete&rid=' .
876 $data[
'data'][
'delete_class'] =
'dbxAjax dbxConfirm text-danger';
877 $data[
'data'][
'delete_title'] =
'Template loeschen';
878 $data[
'data'][
'delete_disabled'] =
'';
879 $data[
'data'][
'confirm'] = $report->_msg_confirm_delete;
885 private function template_dir() {
889 private function normalize_template_name($name) {
890 $name = trim((
string)$name);
891 $name = preg_replace(
'/\.htm$/i',
'', $name);
893 if ($name !==
'' && stripos($name,
'c-') !== 0) {
894 $name =
'c-' . $name;
897 if ($name ===
'' || !preg_match(
'/^c-[a-zA-Z0-9][a-zA-Z0-9_-]*$/', $name)) {
904 private function template_file_path($name) {
905 $name = $this->normalize_template_name($name);
910 $dir = rtrim($this->template_dir(),
'/\\') . DIRECTORY_SEPARATOR;
915 return $dir . $name .
'.htm';
918 private function default_template_markup() {
920<article
id=
"dbx_target_{i}" class=
"c-cms default">
926 <section
class=
"cms-hero {cms:hero_class}" style=
"{hero:style}">
927 <div
class=
"hero">{cms:hero}<div
class=
"hero-text">{cms:hero_text}</div></div>
930 <section
class=
"cms-header header">{cms:header}</section>
932 <section
class=
"gallery {cms:gallery_class}">
933 <div
class=
"gallery-list"
934 style=
"{gallery:style}"{gallery:data_dbx}>{cms:gallery}</div>
937 <section
class=
"cols cols-{cms:cols}">
938 <div
class=
"col col-1">{cms:col1}</div>
939 <div
class=
"col col-2">{cms:col2}</div>
940 <div
class=
"col col-3">{cms:col3}</div>
943 <footer
class=
"footer">{cms:footer}</footer>
950 private function delete_template_file($name) {
951 $path = $this->template_file_path($name);
952 if ($path ===
'' || !is_file($path)) {
956 return @unlink($path);
959 private function create_template_file($name) {
960 $name = $this->normalize_template_name($name);
962 return array(
'ok' =>
false,
'msg' =>
'Ungueltiger Template-Name. Erlaubt: c-name mit Buchstaben, Zahlen, Bindestrich, Unterstrich.');
965 $path = $this->template_file_path($name);
967 return array(
'ok' =>
false,
'msg' =>
'Template-Pfad konnte nicht ermittelt werden.');
970 if (is_file($path)) {
971 return array(
'ok' =>
false,
'msg' =>
'Template existiert bereits: ' . $name .
'.htm');
974 $bytes = @file_put_contents($path, $this->default_template_markup());
975 if ($bytes ===
false) {
976 return array(
'ok' =>
false,
'msg' =>
'Template konnte nicht angelegt werden.');
979 return array(
'ok' =>
true,
'msg' =>
'Template angelegt: ' . $name .
'.htm',
'name' => $name);
982 private function templates_bar_actions() {
983 $url =
'?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=template_new&dbx_window=1';
985 return '<a class="btn btn-primary btn-sm openWin dbx-win" href="' . htmlspecialchars($url, ENT_QUOTES,
'UTF-8') .
'" ' .
986 'data-url="' . htmlspecialchars($url, ENT_QUOTES,
'UTF-8') .
'" ' .
987 'data-title="Neues Content-Template" title="Neues c-* Template anlegen">' .
988 '<i class="bi bi-plus-lg"></i> Neues Template</a>';
991 private function template_new() {
992 $tpl =
dbx()->get_system_obj(
'dbxTPL');
993 $action =
'?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=template_new';
997 if (
dbx()->get_request_var(
'dbx_do',
'',
'parameter') ===
'template_create') {
998 $templateName = trim((
string)
dbx()->get_request_var(
'template_name',
'',
'parameter'));
999 $result = $this->create_template_file($templateName);
1002 return dbx()->redirect(
'?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=templates');
1005 $msg = $tpl->get_tpl(
'dbx|alert-danger', array(
1006 'msg' => (
string)(
$result[
'msg'] ??
'Template konnte nicht angelegt werden.'),
1010 return $tpl->get_tpl(
'dbxContent_admin|content-admin-template-new', array(
1011 'action' => $action,
1012 'template_name' => htmlspecialchars($templateName, ENT_QUOTES,
'UTF-8'),
1017 private function report_templates() {
1020 $dbxDo = (string)
dbx()->get_request_var(
'dbx_do',
'',
'alphanum');
1021 $deleteName = trim((
string)
dbx()->get_request_var(
'rid',
'',
'alphanum'));
1022 if ($dbxDo ===
'row_delete' && $deleteName !==
'') {
1023 if ($this->delete_template_file($deleteName)) {
1024 $msgSuccess =
'Template geloescht: ' . $deleteName .
'.htm';
1026 $msgError =
'Template konnte nicht geloescht werden.';
1030 $dir = $this->template_dir();
1031 $files = is_dir(
$dir) ? glob(
$dir .
'*.htm') : array();
1034 if (is_array($files)) {
1035 sort($files, SORT_NATURAL | SORT_FLAG_CASE);
1036 foreach ($files as $file) {
1037 if (!is_file($file)) {
1041 $name = pathinfo($file, PATHINFO_FILENAME);
1042 if (stripos($name,
'c-') !== 0) {
1047 'modul' =>
'dbxContent',
1049 'type' => pathinfo($file, PATHINFO_EXTENSION),
1050 'size' => (
string)filesize($file),
1051 'modified' => date(
'Y-m-d H:i:s', filemtime($file)),
1058 'name' =>
'Template',
1061 'modified' =>
'Geaendert',
1064 return $this->report_rows(
'templates',
$fields, $rows, $msgSuccess, $msgError);
1067 public function run($work =
'') {
1069 case 'content_grid_read':
1070 $this->content_grid_read();
1073 case 'content_grid_save':
1074 $this->content_grid_save();
1077 case 'content_grid_delete':
1078 $this->content_grid_delete();
1081 case 'content_grid_sync':
1082 $this->content_grid_sync();
1085 case 'list_content':
1086 return $this->report_content();
1089 return $this->render_section(
1091 'Content-Ordner der aktuell gewaehlten Sprache.',
1092 $this->report_folders()
1096 return $this->render_section(
1098 'Zentrale Medien aus dbxMedia.',
1099 $this->report_media()
1103 return $this->media_view();
1106 return $this->render_section_or_ajax(
1107 'Content Templates',
1108 'Verfuegbare Templates aus dbxContent/tpl/htm.',
1109 $this->report_templates(),
1110 $this->templates_bar_actions()
1113 case 'template_new':
1114 return $this->template_new();
1117 return dbx()->get_system_obj(
'dbxTPL')->get_tpl(
'dbx|alert-warning', array(
1118 'msg' =>
'Content-Bereich (' .
dbx()->html($work) .
') ist nicht definiert.',