43 private $content_folders = array();
44 private $media_usages = array();
45 private $sectionTexts =
null;
47 private function section_texts() {
48 if ($this->sectionTexts)
return $this->sectionTexts;
49 dbx()->get_system_obj(
'dbxForm',
'use');
51 $texts->init(
'content-section-texts');
52 $texts->_fd =
'dbxContent_admin|rpt-content-list-selection';
53 $texts->load_fd_messages();
54 $texts->set_form_help_enabled(
false);
55 $this->sectionTexts =
$texts;
56 return $this->sectionTexts;
59 private function render_section($title, $subtitle,
$content, $barActions =
'') {
60 return dbx()->get_system_obj(
'dbxTPL')->get_tpl(
'dbxContent_admin|content-admin-section', array(
62 'subtitle' => $subtitle,
64 'bar_actions' => $barActions,
68 private function is_ajax_request() {
69 return (
int)
dbx()->get_system_var(
'dbx_ajax', 0,
'int') === 1;
72 private function render_section_or_ajax($title, $subtitle,
$content, $barActions =
'') {
73 if ($this->is_ajax_request()) {
77 return $this->render_section($title, $subtitle,
$content, $barActions);
80 private function base_url($action, $params = array()) {
81 $url =
'?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=' . rawurlencode((
string)$action);
82 foreach ($params as $key =>
$value) {
83 $url .=
'&' . rawurlencode((
string)$key) .
'=' . rawurlencode((
string)
$value);
88 private function request_json() {
89 $raw = file_get_contents(
'php://input');
90 $data = $raw ? json_decode($raw,
true) : array();
91 return is_array($data) ? $data : array();
94 private function content_grid_folder_editor_values() {
95 if (!$this->content_folders) {
96 $db =
dbx()->get_system_obj(
'dbxDB');
97 $this->load_content_folders_map(
$db);
100 $pairs = array(
'0=/');
101 $folderIds = array_keys($this->content_folders);
102 usort($folderIds,
function ($a, $b) {
103 return strcmp($this->content_folder_path($a), $this->content_folder_path($b));
106 foreach ($folderIds as $folderId) {
107 $path = $this->content_folder_path($folderId);
108 $label = str_replace(array(
'~',
'=',
';'), array(
' ',
' ',
' '), $path);
109 $pairs[] = (int)$folderId .
'=' . $label;
112 return implode(
'~', $pairs);
115 private function content_grid_cols(
$texts) {
116 $folderValues = $this->content_grid_folder_editor_values();
118 return implode(
',', array(
119 'id[ID]:number:p:width=72;hozAlign=center;headerHozAlign=center',
120 'title[' .
$texts->get_fd_message(
'column_title') .
']:text::width=240',
121 'permalink[' .
$texts->get_fd_message(
'column_permalink') .
']:text::width=200',
122 'folder[' .
$texts->get_fd_message(
'column_folder') .
']:text::editor=list;values=' . $folderValues .
';width=220',
123 'activ[' .
$texts->get_fd_message(
'column_active') .
']:text::editor=list;values=0='
124 .
$texts->get_fd_message(
'status_inactive') .
'~1=' .
$texts->get_fd_message(
'status_active') .
';width=110',
125 'update_date[' .
$texts->get_fd_message(
'column_updated') .
']:text:p:width=170',
129 private function load_content_folders_map(
$db) {
130 $folderRows =
$db->select(
131 dbxContentLng::ddFolder(),
141 $this->content_folders = array();
142 if (is_array($folderRows)) {
143 foreach ($folderRows as $folderRow) {
144 $folderId = (int)($folderRow[
'id'] ?? 0);
146 $this->content_folders[$folderId] = $folderRow;
152 private function enrich_content_row(array $row) {
153 $row[
'folder_path'] = $this->content_folder_path($row[
'folder'] ?? 0);
154 if (isset($row[
'update_date'])) {
155 $row[
'update_date'] = preg_replace(
'/\.\d+$/',
'', trim((
string)$row[
'update_date']));
157 $id = (int)($row[
'id'] ?? 0);
159 $row[
'profile_link'] = $this->base_url(
'edit', array(
'cid' => $id)) .
'&dbx_window=1';
160 $row[
'show_link'] =
'?dbx_modul=dbxContent&dbx_run1=show&dbx_cid=' . $id .
'&dbx_window=1';
165 private function grid_sort_from_request() {
168 $allowed = array(
'id',
'title',
'permalink',
'activ',
'update_date',
'folder');
169 $sort =
dbx()->get_request_var(
'dbx_sorters',
'',
'*');
171 $sorters = json_decode($sort,
true);
172 if (is_array($sorters) && isset($sorters[0][
'field'])) {
173 $field = (string)$sorters[0][
'field'];
176 $rdesc = strtolower((
string)($sorters[0][
'dir'] ??
'asc')) ===
'desc' ?
'DESC' :
'ASC';
180 return array($rsort, $rdesc);
183 private function content_grid_read() {
184 $db =
dbx()->get_system_obj(
'dbxDB');
185 $dd = dbxContentLng::ddContent();
186 $this->load_content_folders_map(
$db);
188 $page = (int)
dbx()->get_request_var(
'page', 0,
'int');
189 $size = (int)
dbx()->get_request_var(
'size', 0,
'int');
191 $search =
dbx()->get_request_var(
'dbx_search',
'',
'sqlsearch|max=128');
192 $where =
$db->build_search_where(
$dd, $search, array(
'title',
'permalink'), array(
'id'),
'contains');
193 list($rsort, $rdesc) = $this->grid_sort_from_request();
195 $flds = array(
'id',
'title',
'permalink',
'folder',
'activ',
'update_date');
197 if (
$page > 0 && $size > 0) {
198 $size = max(1, min(200, $size));
199 $rpos = (
$page - 1) * $size;
201 $rows =
$db->select(
$dd, $where, $flds, $rsort, $rdesc,
'', $size, $rpos);
203 $rows =
$db->select(
$dd, $where, $flds, $rsort, $rdesc,
'', 0, 0);
206 if (!is_array(
$rows)) {
211 foreach (
$rows as $row) {
212 if (!is_array($row)) {
215 $out[] = $this->enrich_content_row($row);
219 $last_page = (
$page > 0 && $size > 0) ? max(1, (
int)ceil(
$count / $size)) : 1;
221 dbx()->json_response(array(
224 'last_page' => $last_page,
227 'server_time' => (
new \DateTime())->format(
'Y-m-d H:i:s.v'),
231 private function content_grid_sync() {
232 $db =
dbx()->get_system_obj(
'dbxDB');
233 $dd = dbxContentLng::ddContent();
234 $this->load_content_folders_map(
$db);
236 $last_update = trim((
string)
dbx()->get_request_var(
'last_update',
''));
237 if ($last_update ===
'') {
238 $last_update = trim((
string)
dbx()->get_modul_var(
'last_update',
''));
240 if ($last_update ===
'') {
241 $last_update =
'1970-01-01 00:00:00';
244 $where =
"update_date > '" . str_replace(
"'",
"''", $last_update) .
"'";
245 $flds = array(
'id',
'title',
'permalink',
'folder',
'activ',
'update_date');
246 $rows =
$db->select(
$dd, $where, $flds,
'update_date',
'ASC',
'', 0, 0);
247 if (!is_array(
$rows)) {
252 foreach (
$rows as $row) {
253 if (!is_array($row)) {
256 $out[] = $this->enrich_content_row($row);
259 dbx()->json_response(array(
261 'count' => count(
$out),
263 'server_time' => (
new \DateTime())->format(
'Y-m-d H:i:s.v'),
267 private function content_grid_save() {
268 $db =
dbx()->get_system_obj(
'dbxDB');
269 $cms =
dbx()->get_include_obj(
'dbxContent_cms');
270 if (is_object(
$cms) && method_exists(
$cms,
'ensure_schema')) {
274 $payload = $this->request_json();
275 $rows = is_array($payload[
'rows'] ??
null) ? $payload[
'rows'] : array();
276 $dd = dbxContentLng::ddContent();
277 $allowed = array_flip(array(
'title',
'permalink',
'activ',
'folder'));
280 $this->load_content_folders_map(
$db);
282 foreach (
$rows as $row) {
283 if (!is_array($row)) {
286 $id = (int)($row[
'id'] ?? 0);
293 if (!array_key_exists(
$field, $row)) {
306 if (array_key_exists(
'folder', $data)) {
307 $folderId = (int)$data[
'folder'];
308 if ($folderId < 0 || ($folderId > 0 && !isset($this->content_folders[$folderId]))) {
313 if (array_key_exists(
'permalink', $data)) {
314 if ($data[
'permalink'] ===
'') {
315 $current =
$db->select1(
$dd, $id,
'title,folder', 0);
316 $pageTitle = (string)($data[
'title'] ?? ($current[
'title'] ??
'Seite'));
317 $pageFolder = (int)($data[
'folder'] ?? ($current[
'folder'] ?? 0));
318 $data[
'permalink'] = dbxContent_permalink::build(
320 dbxContentLng::ddFolder(),
325 } elseif (!dbxContent_permalink::isValid($data[
'permalink'])) {
326 dbx()->json_response(array(
329 'field' =>
'permalink',
331 'msg' =>
'Permalink: nur Kleinbuchstaben, Zahlen und einzelne Bindestriche sind erlaubt.',
333 } elseif (dbxContent_permalink::exists(
$db,
$dd, $data[
'permalink'], $id)) {
334 dbx()->json_response(array(
337 'field' =>
'permalink',
339 'msg' =>
'Dieser Permalink wird bereits von einer anderen Seite verwendet.',
344 if (
$db->update(
$dd, $data, $id) >= 0) {
345 dbxContentLngSync::afterPageSave(
$db, $id,
false);
346 dbxContentPageCache::invalidateContent($id);
347 dbxContentPageCache::invalidateAllMenus();
348 $fresh =
$db->select1(
$dd, $id,
'id,title,permalink,folder,activ,update_date', 0);
349 if (is_array($fresh)) {
350 $saved[] = $this->enrich_content_row($fresh);
355 $this->load_content_folders_map(
$db);
357 dbx()->json_response(array(
364 private function content_grid_delete() {
365 $payload = $this->request_json();
366 $id = (int)($payload[
'id'] ?? 0);
368 dbx()->json_response(array(
'ok' => 0,
'success' =>
false,
'msg' =>
'Keine Seiten-ID.'),
true);
371 $cms =
dbx()->get_include_obj(
'dbxContent_cms');
372 $result = is_object(
$cms) ?
$cms->delete_page_record($id) : array(
'ok' => 0,
'errors' => array(
'CMS nicht verfuegbar.'));
375 dbx()->json_response(array(
376 'ok' => (
int)(
$result[
'ok'] ?? 0),
377 'success' => (
int)(
$result[
'ok'] ?? 0) === 1,
378 'msg' => implode(
' ',
$errors),
382 private function report_content_grid() {
383 $oReport =
dbx()->get_system_obj(
'dbxReport');
384 $db =
dbx()->get_system_obj(
'dbxDB');
385 $dd = dbxContentLng::ddContent();
387 $active = (int)
$db->count(
$dd,
'activ = 1');
389 $oReport->init(
'content-admin-content-grid',
'content-admin-content-grid');
390 $oReport->_fd =
'dbxContent_admin|rpt-content-list-selection';
391 $oReport->load_fd_messages();
392 $oReport->add_rep(
'shell_panel_class',
'dbx-grid dbx-content-list-grid');
393 $oReport->add_rep(
'frame_use_form',
'0');
394 $oReport->add_rep(
'bar_title', $oReport->get_fd_message(
'bar_title'));
395 $oReport->add_rep(
'bar_subtitle', $oReport->get_fd_message(
'bar_subtitle'));
396 $oReport->_mode =
'tabulurator';
397 $oReport->_rrows = 600;
398 $oReport->_grid_id =
'dbx_content_list_grid';
399 $oReport->_grid_cols = $this->content_grid_cols($oReport);
400 $oReport->_grid_layout =
'fitDataStretch';
401 $oReport->_grid_read_url = $this->base_url(
'content_grid_read');
402 $oReport->_grid_save_url = $this->base_url(
'content_grid_save');
403 $oReport->_grid_delete_url = $this->base_url(
'content_grid_delete');
404 $oReport->_grid_sync_url = $this->base_url(
'content_grid_sync');
405 $oReport->_grid_synctime =
'1.5';
406 $oReport->add_grid_stats(array(
407 array(
'label' => $oReport->get_fd_message(
'stats_pages'),
'value' => (
string)
$all),
408 array(
'label' => $oReport->get_fd_message(
'stats_active'),
'value' => (
string)$active,
'tone' =>
'ok'),
409 array(
'label' => $oReport->get_fd_message(
'stats_inactive'),
'value' => (
string)max(0,
$all - $active)),
410 ), $oReport->get_fd_message(
'stats_title'));
411 $oReport->add_obj(
'bar_actions',
'obj-value',
412 '<a class="btn btn-outline-primary btn-sm" href="' .
dbx()->
esc($this->base_url(
'edit')) .
'">' .
413 '<i class="bi bi-pencil-square"></i><span>' .
dbx()->
esc($oReport->get_fd_message(
'cms_label')) .
'</span></a>'
416 return $oReport->run();
419 private function report_rows($action, array
$fields, array
$rows, $msgSuccess =
'', $msgError =
'') {
421 $report->init(
'content-admin-report');
422 $report->_fd =
'dbxContent_admin|rpt-content-list-selection';
424 $report->set_form_help_enabled(
false);
425 $report->_action =
'?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=' . rawurlencode($action);
434 if ($msgSuccess !==
'') {
435 $report->_msg_success = $msgSuccess;
437 if ($msgError !==
'') {
438 $report->_msg_error = $msgError;
441 if ($action ===
'list_folder') {
442 $report->set_callback_owner($this);
443 $report->set_callback(
'next_record',
'folder_next_record');
444 $report->set_callback(
'row_action_data',
'folder_row_action_data');
446 $report->_create_row_delete = 1;
447 $report->_msg_confirm_delete =
$report->get_fd_message(
'confirm_folder_delete');
448 $report->set_tabel_tpl(
'tpl_row_edit',
'modul|content-admin-row-edit');
449 } elseif ($action ===
'list_media') {
450 $report->set_callback_owner($this);
451 $report->set_callback(
'next_record',
'media_next_record');
452 $report->set_callback(
'row_action_data',
'media_row_action_data');
453 $report->_create_row_delete = 1;
454 $report->_msg_confirm_delete =
$report->get_fd_message(
'confirm_media_delete');
455 $report->set_tabel_tpl(
'tpl_row_delete',
'modul|content-admin-row-delete-media');
456 } elseif ($action ===
'templates') {
457 $report->set_callback_owner($this);
458 $report->set_callback(
'row_action_data',
'template_row_action_data');
460 $report->_create_row_delete = 1;
462 $report->_msg_confirm_delete =
$report->get_fd_message(
'confirm_template_delete');
463 $report->_rpt_format[
'modified'] =
'php-datetime-usr';
464 $report->_table_buttons =
'left';
465 $report->set_tabel_tpl(
'tpl_row_edit',
'modul|content-admin-row-edit');
466 $report->set_tabel_tpl(
'tpl_row_delete',
'modul|content-admin-row-delete-template');
472 private function content_folder_path($folderId) {
473 $folderId = (int)$folderId;
474 if ($folderId <= 0) {
481 while ($folderId > 0 && isset($this->content_folders[$folderId]) && !isset($seen[$folderId])) {
482 $seen[$folderId] =
true;
483 $folder = $this->content_folders[$folderId];
484 $name = trim((
string)($folder[
'name'] ??
''),
" \t\n\r\0\x0B/");
487 array_unshift($parts, $name);
490 $parentId = (int)($folder[
'parent_id'] ?? 0);
491 if ($parentId === $folderId) {
494 $folderId = $parentId;
497 return '/' . implode(
'/', $parts);
501 if (!is_array($record)) {
505 $record[
'folder_path'] = $this->content_folder_path($record[
'folder'] ?? 0);
506 if (isset($record[
'update_date'])) {
507 $record[
'update_date'] = preg_replace(
'/\.\d+$/',
'', trim((
string)$record[
'update_date']));
513 if (!is_array($data) || !isset($data[
'data']) || !is_array($data[
'data'])) {
517 $type = (string)($data[
'type'] ??
'');
518 $rid = (int)($data[
'data'][
'rid'] ?? 0);
520 if ($type ===
'edit') {
521 $url =
'?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=edit&cid=' . $rid .
'&dbx_window=1';
522 $data[
'data'][
'action'] =
$url;
523 $data[
'data'][
'edit_url'] = htmlspecialchars(
$url, ENT_QUOTES,
'UTF-8');
524 $data[
'data'][
'edit_title'] = htmlspecialchars(
'Content im CMS bearbeiten', ENT_QUOTES,
'UTF-8');
525 $data[
'data'][
'class'] =
'openWin';
526 } elseif ($type ===
'delete') {
527 $data[
'data'][
'action'] =
528 '?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=list_content';
535 if (!is_array($record)) {
539 $id = (int)($record[
'id'] ?? 0);
540 $type = strtolower(trim((
string)($record[
'media_type'] ??
'')));
541 $provider = strtolower(trim((
string)($record[
'provider'] ??
'')));
542 $providerId = trim((
string)($record[
'provider_id'] ??
''));
543 $title = trim((
string)($record[
'title'] ?? $record[
'file_name'] ??
'Medium'));
544 $preview =
'<span class="text-muted"><i class="bi bi-file-earmark"></i></span>';
546 if ($id > 0 && in_array($type, array(
'image',
'video',
'external_video'),
true)) {
547 $mediaUrl =
'index.php?dbx_modul=dbxContent&dbx_run1=media&dbx_mid=' . $id;
548 $url =
'?dbx_modul=dbxContent_admin&dbx_run1=media_view&rid=' . $id .
'&dbx_window=1';
549 $thumbUrl = $mediaUrl .
'&dbx_thumb=1';
550 if ($type ===
'external_video' && $provider ===
'youtube' && preg_match(
'/^[A-Za-z0-9_-]{11}$/', $providerId)) {
551 $thumbUrl =
'https://img.youtube.com/vi/' . rawurlencode($providerId) .
'/hqdefault.jpg';
554 '<a class="openWin dbx-win" href="' . htmlspecialchars(
$url, ENT_QUOTES,
'UTF-8') .
'" ' .
555 'data-url="' . htmlspecialchars(
$url, ENT_QUOTES,
'UTF-8') .
'" ' .
556 'data-title="' . htmlspecialchars($title, ENT_QUOTES,
'UTF-8') .
'" ' .
557 'title="' . htmlspecialchars($title, ENT_QUOTES,
'UTF-8') .
'">' .
558 '<img src="' . htmlspecialchars($thumbUrl, ENT_QUOTES,
'UTF-8') .
'" ' .
559 'alt="' . htmlspecialchars($title, ENT_QUOTES,
'UTF-8') .
'" loading="lazy" ' .
560 'class="rounded border bg-light" style="width:96px;height:64px;object-fit:cover">' .
564 $record[
'thumbnail'] = $preview;
565 $usages = $this->media_usages[$id] ?? array();
566 $usageHtml = array();
568 $lng = strtolower(trim((
string)($usage[
'lng'] ??
'')));
569 $usageId = (int)($usage[
'id'] ?? 0);
570 $type = (string)($usage[
'type'] ??
'page');
571 $label = trim((
string)($usage[
'title'] ??
''));
572 $url =
'?dbx_modul=dbxContent_admin&dbx_run1=cms&dbx_lng=' . rawurlencode(
$lng) .
573 ($type ===
'folder' ?
'&fid=' :
'&cid=') . $usageId .
'&dbx_window=1';
575 '<a class="openWin dbx-win d-block text-decoration-none mb-1" ' .
576 'href="' . htmlspecialchars(
$url, ENT_QUOTES,
'UTF-8') .
'" ' .
577 'data-url="' . htmlspecialchars(
$url, ENT_QUOTES,
'UTF-8') .
'" ' .
578 'data-title="' . htmlspecialchars($label, ENT_QUOTES,
'UTF-8') .
'" ' .
579 'title="' . htmlspecialchars($label, ENT_QUOTES,
'UTF-8') .
'">' .
580 '<span class="badge text-bg-secondary me-1">' . htmlspecialchars(strtoupper(
$lng), ENT_QUOTES,
'UTF-8') .
'</span>' .
581 htmlspecialchars($label, ENT_QUOTES,
'UTF-8') .
584 $record[
'usage'] = count($usageHtml)
585 ? implode(
'', $usageHtml)
586 :
'<span class="text-muted">Nicht verwendet</span>';
587 $record[
'_usage_count'] = count(
$usages);
592 if (!is_array($data) || !isset($data[
'data']) || !is_array($data[
'data'])) {
596 if ((
string)($data[
'type'] ??
'') !==
'delete') {
600 $rid = (int)($data[
'data'][
'rid'] ?? 0);
601 $used = count($this->media_usages[$rid] ?? array()) > 0;
602 $data[
'data'][
'delete_url'] =
603 '?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=list_media&dbx_do=row_delete&rid=' . $rid;
604 $data[
'data'][
'delete_class'] = $used ?
'disabled text-muted' :
'dbxAjax dbxConfirm text-danger';
605 $data[
'data'][
'delete_title'] = $used
606 ?
'Medium wird verwendet und kann nicht geloescht werden'
608 $data[
'data'][
'delete_disabled'] = $used ?
'aria-disabled="true" tabindex="-1"' :
'';
609 $data[
'data'][
'confirm'] =
$report->_msg_confirm_delete;
614 private function add_media_usage(array &$map,
int $mediaId,
string $lng,
int $id,
string $title,
string $type =
'page'): void {
619 $key = $type .
'|' .
$lng .
'|' . $id;
623 'title' => ($type ===
'folder' ?
'Ordner: ' :
'') . ($title !==
'' ? $title : (
'#' . $id)),
628 private function load_media_usages(
$db): array {
630 $usageRows =
$db->select(
'dbxMediaUsage',
'active = 1',
'media_id,content_id,folder_id,content_lng',
'media_id,id',
'ASC',
'', 0, 0, 0);
631 $usageRows = is_array($usageRows) ? $usageRows : array();
633 foreach (dbxContentLngSync::accessibleLngs() as
$lng) {
634 $lng = strtolower(trim((
string)
$lng));
640 dbxContentLng::ddContent(
$lng),
642 'id,title,hero_image_id,seo_image_id,content',
650 $folders =
$db->select(
651 dbxContentLng::ddFolder(
$lng),
653 'id,name,hero_image_id',
662 $pagesById = array();
670 $heroId = (int)(
$page[
'hero_image_id'] ?? 0);
672 $this->add_media_usage($map, $heroId,
$lng,
$pageId, (
string)(
$page[
'title'] ??
''),
'page');
674 $seoId = (int)(
$page[
'seo_image_id'] ?? 0);
676 $this->add_media_usage($map, $seoId,
$lng,
$pageId, (
string)(
$page[
'title'] ??
''),
'page');
678 $inlineIds = array();
680 if (preg_match_all(
'/data-cms-media-id=["\']?(\d+)/i',
$content, $matches)) {
683 if (preg_match_all(
'/(?:dbx_mid|media_id)=(\d+)(?:[^0-9]|$)/i',
$content, $matches)) {
693 $foldersById = array();
694 foreach (is_array($folders) ? $folders : array() as $folder) {
695 $folderId = (int)($folder[
'id'] ?? 0);
696 if ($folderId <= 0) {
699 $foldersById[$folderId] = $folder;
700 $heroId = (int)($folder[
'hero_image_id'] ?? 0);
702 $this->add_media_usage($map, $heroId,
$lng, $folderId, (
string)($folder[
'name'] ??
''),
'folder');
706 foreach ($usageRows as $usage) {
707 if (strtolower(trim((
string)($usage[
'content_lng'] ??
'de'))) !==
$lng)
continue;
708 $mediaId = (int)($usage[
'media_id'] ?? 0);
709 $contentId = (int)($usage[
'content_id'] ?? 0);
710 $folderId = (int)($usage[
'folder_id'] ?? 0);
711 if ($contentId > 0 && isset($pagesById[$contentId])) {
712 $this->add_media_usage(
717 (
string)($pagesById[$contentId][
'title'] ??
''),
720 } elseif ($folderId > 0 && isset($foldersById[$folderId])) {
721 $this->add_media_usage(
726 (
string)($foldersById[$folderId][
'name'] ??
''),
740 if (!is_array($record)) {
744 $record[
'folder_path'] = $this->content_folder_path($record[
'id'] ?? 0);
749 if (!is_array($data) || !isset($data[
'data']) || !is_array($data[
'data'])) {
753 $type = (string)($data[
'type'] ??
'');
754 $rid = (int)($data[
'data'][
'rid'] ?? 0);
756 if ($type ===
'edit') {
757 $url =
'?dbx_modul=dbxContent_admin&dbx_run1=cms&fid=' . $rid .
'&dbx_window=1';
758 $data[
'data'][
'action'] =
$url;
759 $data[
'data'][
'edit_url'] = htmlspecialchars(
$url, ENT_QUOTES,
'UTF-8');
760 $data[
'data'][
'edit_title'] = htmlspecialchars(
761 $this->section_texts()->get_fd_message(
'edit_folder_in_cms'),
765 $data[
'data'][
'class'] =
'openWin';
766 } elseif ($type ===
'delete') {
767 $data[
'data'][
'action'] =
768 '?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=list_folder';
774 private function report_content() {
775 return $this->report_content_grid();
778 private function report_folders() {
779 $db =
dbx()->get_system_obj(
'dbxDB');
780 $texts = $this->section_texts();
782 $deleteId = (int)
dbx()->get_modul_var(
'rid', 0,
'int');
783 if (
dbx()->get_modul_var(
'dbx_do',
'',
'parameter') ===
'row_delete' && $deleteId > 0) {
784 $cms =
dbx()->get_include_obj(
'dbxContent_cms');
785 $cms->delete_folder_record($deleteId);
790 'folder_path' =>
$texts->get_fd_message(
'column_folder'),
791 'template' =>
$texts->get_fd_message(
'column_template'),
792 'group_read' =>
$texts->get_fd_message(
'column_read_rights'),
796 dbxContentLng::ddFolder(),
798 'id,name,parent_id,template,group_read',
806 if (!is_array(
$rows)) {
810 $this->content_folders = array();
812 $folderId = (int)($row[
'id'] ?? 0);
814 $this->content_folders[$folderId] = $row;
819 return $this->report_rows(
'list_folder',
$fields,
$rows);
822 private function report_media() {
823 $db =
dbx()->get_system_obj(
'dbxDB');
824 $texts = $this->section_texts();
826 $this->media_usages = $this->load_media_usages(
$db);
827 $deleteId = (int)
dbx()->get_modul_var(
'rid', 0,
'int');
828 if (
dbx()->get_modul_var(
'dbx_do',
'',
'parameter') ===
'row_delete' && $deleteId > 0) {
829 $cms =
dbx()->get_include_obj(
'dbxContent_cms');
830 $cms->delete_media_record($deleteId);
831 $this->media_usages = $this->load_media_usages(
$db);
835 'thumbnail' =>
$texts->get_fd_message(
'column_preview'),
837 'title' =>
$texts->get_fd_message(
'column_title'),
838 'media_type' =>
$texts->get_fd_message(
'column_type'),
839 'media_folder' =>
$texts->get_fd_message(
'column_folder'),
840 'provider' =>
$texts->get_fd_message(
'column_provider'),
841 'usage' =>
$texts->get_fd_message(
'column_usage'),
842 'active' =>
$texts->get_fd_message(
'column_active'),
848 'id,title,media_type,file_name,media_folder,provider,provider_id,active',
856 if (!is_array(
$rows)) {
865 return $this->report_rows(
'list_media',
$fields,
$rows);
868 private function media_view() {
869 $id = (int)
dbx()->get_modul_var(
'rid', 0,
'int');
870 $db =
dbx()->get_system_obj(
'dbxDB');
871 $row = $id > 0 ?
$db->select1(
'dbxMedia', $id) : array();
873 if (!is_array($row) || !count($row)) {
874 return dbx()->get_system_obj(
'dbxTPL')->get_tpl(
'dbx|alert-warning', array(
875 'msg' => $this->section_texts()->get_fd_message(
'media_not_found'),
879 $type = strtolower(trim((
string)($row[
'media_type'] ??
'file')));
880 $provider = strtolower(trim((
string)($row[
'provider'] ??
'')));
881 $providerId = trim((
string)($row[
'provider_id'] ??
''));
882 $title = trim((
string)($row[
'title'] ?? $row[
'file_name'] ?? (
'Medium #' . $id)));
883 $mime = trim((
string)($row[
'mime'] ??
''));
884 $mediaUrl =
'index.php?dbx_modul=dbxContent&dbx_run1=media&dbx_mid=' . $id;
887 if ($type ===
'image') {
889 '<img src="' . htmlspecialchars($mediaUrl, ENT_QUOTES,
'UTF-8') .
'" ' .
890 'alt="' . htmlspecialchars($title, ENT_QUOTES,
'UTF-8') .
'" ' .
891 'class="img-fluid rounded border bg-light" style="max-height:72vh;object-fit:contain">';
892 } elseif ($type ===
'video') {
893 $poster = !empty($row[
'thumb_file_path'])
894 ?
' poster="' . htmlspecialchars($mediaUrl .
'&dbx_thumb=1', ENT_QUOTES,
'UTF-8') .
'"'
897 '<video class="w-100 rounded border bg-dark" style="max-height:72vh" controls preload="metadata" playsinline' . $poster .
'>' .
898 '<source src="' . htmlspecialchars($mediaUrl, ENT_QUOTES,
'UTF-8') .
'"' .
899 ($mime !==
'' ?
' type="' . htmlspecialchars($mime, ENT_QUOTES,
'UTF-8') .
'"' :
'') .
'>' .
900 'Ihr Browser kann dieses Video nicht wiedergeben.</video>';
901 } elseif ($type ===
'external_video' && $provider ===
'youtube' && preg_match(
'/^[A-Za-z0-9_-]{11}$/', $providerId)) {
902 $embedUrl =
'https://www.youtube.com/embed/' . rawurlencode($providerId);
904 '<div class="ratio ratio-16x9"><iframe src="' . htmlspecialchars($embedUrl, ENT_QUOTES,
'UTF-8') .
'" ' .
905 'title="' . htmlspecialchars($title, ENT_QUOTES,
'UTF-8') .
'" ' .
906 'allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" ' .
907 'allowfullscreen></iframe></div>';
910 '<div class="text-center py-5"><i class="bi bi-file-earmark fs-1 d-block mb-3"></i>' .
911 '<a class="btn btn-primary" href="' . htmlspecialchars($mediaUrl, ENT_QUOTES,
'UTF-8') .
'">' .
912 '<i class="bi bi-box-arrow-up-right"></i> Datei anzeigen</a></div>';
915 return dbx()->get_system_obj(
'dbxTPL')->get_tpl(
'dbxContent_admin|content-admin-media-view', array(
916 'title' => htmlspecialchars($title, ENT_QUOTES,
'UTF-8'),
917 'media' => $mediaHtml,
918 'type' => htmlspecialchars($type, ENT_QUOTES,
'UTF-8'),
919 'folder' => htmlspecialchars((
string)($row[
'media_folder'] ??
''), ENT_QUOTES,
'UTF-8'),
920 'mime' => htmlspecialchars($mime, ENT_QUOTES,
'UTF-8'),
926 if (!is_array($data) || !isset($data[
'data']) || !is_array($data[
'data'])) {
930 $record = is_array($data[
'record'] ??
null) ? $data[
'record'] : array();
931 $type = (string)($data[
'type'] ??
'');
932 $tpl = trim((
string)($record[
'name'] ??
''));
934 if ($type ===
'edit') {
935 $modul = trim((
string)($record[
'modul'] ??
'dbxContent'));
936 $fileType = trim((
string)($record[
'type'] ??
'htm'));
938 $relPath =
'dbx/modules/' . $modul .
'/tpl/' . $fileType .
'/' .
$tpl .
'.' . $fileType;
939 $url =
'?dbx_modul=dbxEditor&dbx_run1=edit&file=' . rawurlencode($relPath) .
'&dbx_window=1';
941 $data[
'data'][
'action'] =
$url;
942 $data[
'data'][
'edit_url'] = htmlspecialchars(
$url, ENT_QUOTES,
'UTF-8');
943 $data[
'data'][
'edit_title'] = htmlspecialchars($modul .
'|' .
$tpl, ENT_QUOTES,
'UTF-8');
944 $data[
'data'][
'class'] =
'openWin';
945 } elseif ($type ===
'delete' &&
$tpl !==
'') {
946 $data[
'data'][
'delete_url'] =
947 '?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=templates&dbx_do=row_delete&rid=' .
949 $data[
'data'][
'delete_class'] =
'dbxAjax dbxConfirm text-danger';
950 $data[
'data'][
'delete_title'] =
'Template loeschen';
951 $data[
'data'][
'delete_disabled'] =
'';
952 $data[
'data'][
'confirm'] =
$report->_msg_confirm_delete;
958 private function template_dir() {
962 private function normalize_template_name($name) {
963 $name = trim((
string)$name);
964 $name = preg_replace(
'/\.htm$/i',
'', $name);
966 if ($name !==
'' && stripos($name,
'c-') !== 0) {
967 $name =
'c-' . $name;
970 if ($name ===
'' || !preg_match(
'/^c-[a-zA-Z0-9][a-zA-Z0-9_-]*$/', $name)) {
977 private function template_file_path($name) {
978 $name = $this->normalize_template_name($name);
983 $dir = rtrim($this->template_dir(),
'/\\') . DIRECTORY_SEPARATOR;
988 return $dir . $name .
'.htm';
991 private function default_template_markup() {
993<article
id=
"dbx_target_{i}" class=
"c-cms default">
999 <section
class=
"cms-hero {cms:hero_class}" style=
"{hero:style}">
1000 <div
class=
"hero">{cms:hero}</div>
1003 <section
class=
"cms-header header">{cms:header}</section>
1005 <section
class=
"gallery {cms:gallery_class}">
1006 <div
class=
"gallery-list"
1007 style=
"{gallery:style}"{gallery:data_dbx}>{cms:gallery}</div>
1010 <section
class=
"cols cols-{cms:cols}">
1011 <div
class=
"col col-1">{cms:col1}</div>
1012 <div
class=
"col col-2">{cms:col2}</div>
1013 <div
class=
"col col-3">{cms:col3}</div>
1016 <footer
class=
"footer">{cms:footer}</footer>
1023 private function delete_template_file($name) {
1024 $path = $this->template_file_path($name);
1025 if ($path ===
'' || !is_file($path)) {
1029 return @unlink($path);
1032 private function create_template_file($name) {
1033 $texts = $this->section_texts();
1034 $name = $this->normalize_template_name($name);
1036 return array(
'ok' =>
false,
'msg' =>
$texts->get_fd_message(
'template_invalid_name'));
1039 $path = $this->template_file_path($name);
1041 return array(
'ok' =>
false,
'msg' =>
$texts->get_fd_message(
'template_path_error'));
1044 if (is_file($path)) {
1045 return array(
'ok' =>
false,
'msg' =>
$texts->format_fd_message(
'template_exists', array(
'name' => $name)));
1048 $bytes = @file_put_contents($path, $this->default_template_markup());
1049 if ($bytes ===
false) {
1050 return array(
'ok' =>
false,
'msg' =>
$texts->get_fd_message(
'template_create_error'));
1055 'msg' =>
$texts->format_fd_message(
'template_created', array(
'name' => $name)),
1060 private function templates_bar_actions() {
1061 $texts = $this->section_texts();
1062 $url =
'?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=template_new&dbx_window=1';
1064 return '<a class="btn btn-primary btn-sm openWin dbx-win" href="' . htmlspecialchars(
$url, ENT_QUOTES,
'UTF-8') .
'" ' .
1065 'data-url="' . htmlspecialchars(
$url, ENT_QUOTES,
'UTF-8') .
'" ' .
1066 'data-title="' . htmlspecialchars(
$texts->get_fd_message(
'template_new_title'), ENT_QUOTES,
'UTF-8') .
1067 '" title="' . htmlspecialchars(
$texts->get_fd_message(
'template_new_action_title'), ENT_QUOTES,
'UTF-8') .
'">' .
1068 '<i class="bi bi-plus-lg"></i> ' .
1069 htmlspecialchars(
$texts->get_fd_message(
'template_new_button'), ENT_QUOTES,
'UTF-8') .
'</a>';
1078 private function template_new() {
1079 $action =
'?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=template_new';
1080 $form =
dbx()->get_system_obj(
'dbxForm');
1081 $form->init(
'content-template-new',
'content-admin-template-new');
1082 $form->_fd =
'dbxContent_admin|rpt-content-list-selection';
1083 $form->load_fd_messages();
1084 $form->_action = $action;
1086 $form->_data = array_merge(
$form->_data, array(
'template_name' =>
''));
1087 $form->_msg_info =
$form->get_fd_message(
'template_new_info');
1091 label:
$form->get_fd_message(
'template_name_label'),
1092 rules:
'parameter|min=3|max=120',
1093 placeholder:
'c-mein-layout',
1094 errormsg:
$form->get_fd_message(
'template_name_error')
1097 $help =
dbx()->get_include_obj(
'dbxAdminHelp',
'dbxAdmin');
1098 $helpButton = is_object(
$help) && method_exists(
$help,
'formButton')
1099 ?
$help->formButton(
'dbxContent_admin',
'content-template-new',
$form->get_fd_message(
'template_new_title'))
1101 $form->add_rep(
'help_button', $helpButton);
1103 if (
$form->submit()) {
1104 if (!
$form->errors()) {
1105 $templateName = trim((
string)
$form->get_post(
'template_name',
'',
'parameter|min=3|max=120'));
1106 $result = $this->create_template_file($templateName);
1108 return dbx()->redirect(
'?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=templates');
1110 $form->add_fld_error(
'template_name', (
string)(
$result[
'msg'] ??
$form->get_fd_message(
'template_create_error')));
1111 $form->_msg_error = (string)(
$result[
'msg'] ??
$form->get_fd_message(
'template_create_error'));
1113 $form->_msg_error =
$form->get_fd_message(
'template_name_validation');
1117 return $form->run();
1120 private function report_templates() {
1121 $texts = $this->section_texts();
1124 $dbxDo = (string)
dbx()->get_request_var(
'dbx_do',
'',
'alphanum');
1125 $deleteName = trim((
string)
dbx()->get_request_var(
'rid',
'',
'alphanum'));
1126 if ($dbxDo ===
'row_delete' && $deleteName !==
'') {
1127 if ($this->delete_template_file($deleteName)) {
1128 $msgSuccess =
$texts->format_fd_message(
'template_deleted', array(
'name' => $deleteName));
1130 $msgError =
$texts->get_fd_message(
'template_delete_error');
1134 $dir = $this->template_dir();
1139 sort(
$files, SORT_NATURAL | SORT_FLAG_CASE);
1141 if (!is_file(
$file)) {
1145 $name = pathinfo(
$file, PATHINFO_FILENAME);
1146 if (stripos($name,
'c-') !== 0) {
1151 'modul' =>
'dbxContent',
1153 'type' => pathinfo(
$file, PATHINFO_EXTENSION),
1154 'size' => (
string)filesize(
$file),
1155 'modified' => date(
'Y-m-d H:i:s', filemtime(
$file)),
1161 'modul' =>
$texts->get_fd_message(
'column_module'),
1162 'name' =>
$texts->get_fd_message(
'column_template'),
1163 'type' =>
$texts->get_fd_message(
'column_type'),
1164 'size' =>
$texts->get_fd_message(
'column_bytes'),
1165 'modified' =>
$texts->get_fd_message(
'column_updated'),
1168 return $this->report_rows(
'templates',
$fields,
$rows, $msgSuccess, $msgError);
1173 case 'content_grid_read':
1174 $this->content_grid_read();
1177 case 'content_grid_save':
1178 $this->content_grid_save();
1181 case 'content_grid_delete':
1182 $this->content_grid_delete();
1185 case 'content_grid_sync':
1186 $this->content_grid_sync();
1189 case 'list_content':
1190 return $this->report_content();
1193 $texts = $this->section_texts();
1194 return $this->render_section(
1195 $texts->get_fd_message(
'section_folders_title'),
1196 $texts->get_fd_message(
'section_folders_subtitle'),
1197 $this->report_folders()
1201 $texts = $this->section_texts();
1202 return $this->render_section(
1203 $texts->get_fd_message(
'section_media_title'),
1204 $texts->get_fd_message(
'section_media_subtitle'),
1205 $this->report_media()
1209 return $this->media_view();
1212 $texts = $this->section_texts();
1213 return $this->render_section_or_ajax(
1214 $texts->get_fd_message(
'section_templates_title'),
1215 $texts->get_fd_message(
'section_templates_subtitle'),
1216 $this->report_templates(),
1217 $this->templates_bar_actions()
1220 case 'template_new':
1221 return $this->template_new();
1224 return dbx()->get_system_obj(
'dbxTPL')->get_tpl(
'dbx|alert-warning', array(
1225 'msg' => $this->section_texts()->format_fd_message(
1226 'section_undefined',
1227 array(
'section' =>
$work)