dbXapp 2.0
RAD, CMS, Module und Runtime-IDE fuer dbXapp
Loading...
Searching...
No Matches
dbxContent_sections.class.php
Go to the documentation of this file.
1<?php
2namespace dbx\dbxContent_admin;
3
4dbx()->use_system_class('dbxReport');
5require_once dirname(__DIR__, 2) . '/dbxContent/include/dbxContent_bootstrap_sync.php';
6
7use dbx\dbxContent\dbxContentLng;
8use dbx\dbxContent\dbxContentLngSync;
9use dbx\dbxContent\dbxContentPageCache;
10
12
13 public function run_body($content) {
14 $record = $this->_record;
15 $path = trim((string)($record['path'] ?? ''));
16
17 if ($path !== '') {
18 $url = '?dbx_modul=dbxEditor&dbx_run1=edit&file=' . rawurlencode($path);
19 $record['edit'] =
20 '<a class="btn btn-outline-primary btn-sm dbx-win" href="' . htmlspecialchars($url, ENT_QUOTES, 'UTF-8') . '" ' .
21 'data-url="' . htmlspecialchars($url, ENT_QUOTES, 'UTF-8') . '" data-title="Content Template bearbeiten" ' .
22 'title="Template bearbeiten"><i class="bi bi-pencil"></i></a>';
23 }
24
25 $editUrl = trim((string)($record['edit_url'] ?? ''));
26 if ($editUrl !== '') {
27 $record['edit'] =
28 '<a class="btn btn-outline-primary btn-sm" href="' . htmlspecialchars($editUrl, ENT_QUOTES, 'UTF-8') . '" ' .
29 'title="Im Content CMS bearbeiten"><i class="bi bi-pencil"></i></a>';
30 }
31
32 $this->_record = $record;
33 return $this->forward_run_body($content);
34 }
35}
36
38
39 private $content_folders = array();
40 private $media_usages = array();
41
42 private function render_section($title, $subtitle, $content, $barActions = '') {
43 return dbx()->get_system_obj('dbxTPL')->get_tpl('dbxContent_admin|content-admin-section', array(
44 'title' => $title,
45 'subtitle' => $subtitle,
46 'content' => $content,
47 'bar_actions' => $barActions,
48 ));
49 }
50
51 private function is_ajax_request() {
52 return (int)dbx()->get_system_var('dbx_ajax', 0, 'int') === 1;
53 }
54
55 private function render_section_or_ajax($title, $subtitle, $content, $barActions = '') {
56 if ($this->is_ajax_request()) {
57 return $content;
58 }
59
60 return $this->render_section($title, $subtitle, $content, $barActions);
61 }
62
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);
67 }
68 return $url;
69 }
70
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();
75 }
76
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);
81 }
82
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));
87 });
88
89 foreach ($folderIds as $folderId) {
90 $path = $this->content_folder_path($folderId);
91 $label = str_replace(array('~', '=', ';'), array(' ', ' ', ' '), $path);
92 $pairs[] = (int)$folderId . '=' . $label;
93 }
94
95 return implode('~', $pairs);
96 }
97
98 private function content_grid_cols() {
99 $folderValues = $this->content_grid_folder_editor_values();
100
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',
108 ));
109 }
110
111 private function load_content_folders_map($db) {
112 $folderRows = $db->select(
113 dbxContentLng::ddFolder(),
114 '',
115 'id,name,parent_id',
116 'id',
117 'ASC',
118 '',
119 0,
120 0,
121 0
122 );
123 $this->content_folders = array();
124 if (is_array($folderRows)) {
125 foreach ($folderRows as $folderRow) {
126 $folderId = (int)($folderRow['id'] ?? 0);
127 if ($folderId > 0) {
128 $this->content_folders[$folderId] = $folderRow;
129 }
130 }
131 }
132 }
133
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']));
138 }
139 $id = (int)($row['id'] ?? 0);
140 if ($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';
143 }
144 return $row;
145 }
146
147 private function grid_sort_from_request() {
148 $rsort = 'id';
149 $rdesc = 'DESC';
150 $allowed = array('id', 'title', 'permalink', 'activ', 'update_date', 'folder');
151 $sort = dbx()->get_request_var('dbx_sorters', '', '*');
152 if ($sort) {
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)) {
157 $rsort = $field;
158 $rdesc = strtolower((string)($sorters[0]['dir'] ?? 'asc')) === 'desc' ? 'DESC' : 'ASC';
159 }
160 }
161 }
162 return array($rsort, $rdesc);
163 }
164
165 private function content_grid_read() {
166 $db = dbx()->get_system_obj('dbxDB');
167 $dd = dbxContentLng::ddContent();
168 $this->load_content_folders_map($db);
169
170 $page = (int)dbx()->get_request_var('page', 0, 'int');
171 $size = (int)dbx()->get_request_var('size', 0, 'int');
172
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();
176
177 $flds = array('id', 'title', 'permalink', 'folder', 'activ', 'update_date');
178
179 if ($page > 0 && $size > 0) {
180 $size = max(1, min(200, $size));
181 $rpos = ($page - 1) * $size;
182 $count = (int)$db->count($dd, $where);
183 $rows = $db->select($dd, $where, $flds, $rsort, $rdesc, '', $size, $rpos);
184 } else {
185 $rows = $db->select($dd, $where, $flds, $rsort, $rdesc, '', 0, 0);
186 $count = is_array($rows) ? count($rows) : 0;
187 }
188 if (!is_array($rows)) {
189 $rows = array();
190 }
191
192 $out = array();
193 foreach ($rows as $row) {
194 if (!is_array($row)) {
195 continue;
196 }
197 $out[] = $this->enrich_content_row($row);
198 }
199
200 $count = (int)$count;
201 $last_page = ($page > 0 && $size > 0) ? max(1, (int)ceil($count / $size)) : 1;
202
203 dbx()->json_response(array(
204 'ok' => 1,
205 'count' => $count,
206 'last_page' => $last_page,
207 'last_row' => $count,
208 'rows' => $out,
209 'server_time' => (new \DateTime())->format('Y-m-d H:i:s.v'),
210 ), true);
211 }
212
213 private function content_grid_sync() {
214 $db = dbx()->get_system_obj('dbxDB');
215 $dd = dbxContentLng::ddContent();
216 $this->load_content_folders_map($db);
217
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', ''));
221 }
222 if ($last_update === '') {
223 $last_update = '1970-01-01 00:00:00';
224 }
225
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)) {
230 $rows = array();
231 }
232
233 $out = array();
234 foreach ($rows as $row) {
235 if (!is_array($row)) {
236 continue;
237 }
238 $out[] = $this->enrich_content_row($row);
239 }
240
241 dbx()->json_response(array(
242 'ok' => 1,
243 'count' => count($out),
244 'rows' => $out,
245 'server_time' => (new \DateTime())->format('Y-m-d H:i:s.v'),
246 ), true);
247 }
248
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')) {
253 $cms->ensure_schema($db);
254 }
255
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'));
260 $saved = array();
261
262 $this->load_content_folders_map($db);
263
264 foreach ($rows as $row) {
265 if (!is_array($row)) {
266 continue;
267 }
268 $id = (int)($row['id'] ?? 0);
269 if ($id <= 0) {
270 continue;
271 }
272
273 $data = array();
274 foreach ($allowed as $field => $_) {
275 if (!array_key_exists($field, $row)) {
276 continue;
277 }
278 if ($field === 'activ' || $field === 'folder') {
279 $data[$field] = (int)$row[$field];
280 } else {
281 $data[$field] = trim((string)$row[$field]);
282 }
283 }
284 if (!$data) {
285 continue;
286 }
287
288 if (array_key_exists('folder', $data)) {
289 $folderId = (int)$data['folder'];
290 if ($folderId < 0 || ($folderId > 0 && !isset($this->content_folders[$folderId]))) {
291 continue;
292 }
293 }
294
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);
302 }
303 }
304 }
305
306 $this->load_content_folders_map($db);
307
308 dbx()->json_response(array(
309 'ok' => 1,
310 'success' => true,
311 'rows' => $saved,
312 ), true);
313 }
314
315 private function content_grid_delete() {
316 $payload = $this->request_json();
317 $id = (int)($payload['id'] ?? 0);
318 if ($id <= 0) {
319 dbx()->json_response(array('ok' => 0, 'success' => false, 'msg' => 'Keine Seiten-ID.'), true);
320 }
321
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();
325
326 dbx()->json_response(array(
327 'ok' => (int)($result['ok'] ?? 0),
328 'success' => (int)($result['ok'] ?? 0) === 1,
329 'msg' => implode(' ', $errors),
330 ), true);
331 }
332
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');
339
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>'
363 );
364
365 return $oReport->run();
366 }
367
368 private function report_rows($action, array $fields, array $rows, $msgSuccess = '', $msgError = '') {
369 $report = new dbxReport_ContentSections();
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;
373 $report->_rflds = $fields;
374 $report->_mode = 'table';
375 $report->_rdata = $rows;
376 $report->_rcount = count($rows);
377 $report->_rrows = count($rows) > 0 ? count($rows) : 1;
378 $report->_rpos = 0;
379
380 if ($msgSuccess !== '') {
381 $report->_msg_success = $msgSuccess;
382 }
383 if ($msgError !== '') {
384 $report->_msg_error = $msgError;
385 }
386
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');
413 }
414
415 return $report->run();
416 }
417
418 private function content_folder_path($folderId) {
419 $folderId = (int)$folderId;
420 if ($folderId <= 0) {
421 return '/';
422 }
423
424 $parts = array();
425 $seen = array();
426
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/");
431
432 if ($name !== '') {
433 array_unshift($parts, $name);
434 }
435
436 $parentId = (int)($folder['parent_id'] ?? 0);
437 if ($parentId === $folderId) {
438 break;
439 }
440 $folderId = $parentId;
441 }
442
443 return '/' . implode('/', $parts);
444 }
445
446 public function content_next_record($report, $record) {
447 if (!is_array($record)) {
448 return $record;
449 }
450
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']));
454 }
455 return $record;
456 }
457
458 public function content_row_action_data($report, $data) {
459 if (!is_array($data) || !isset($data['data']) || !is_array($data['data'])) {
460 return $data;
461 }
462
463 $type = (string)($data['type'] ?? '');
464 $rid = (int)($data['data']['rid'] ?? 0);
465
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';
475 }
476
477 return $data;
478 }
479
480 public function media_next_record($report, $record) {
481 if (!is_array($record)) {
482 return $record;
483 }
484
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>';
491
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';
498 }
499 $preview =
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">' .
507 '</a>';
508 }
509
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';
520 $usageHtml[] =
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') .
528 '</a>';
529 }
530 $record['usage'] = count($usageHtml)
531 ? implode('', $usageHtml)
532 : '<span class="text-muted">Nicht verwendet</span>';
533 $record['_usage_count'] = count($usages);
534 return $record;
535 }
536
537 public function media_row_action_data($report, $data) {
538 if (!is_array($data) || !isset($data['data']) || !is_array($data['data'])) {
539 return $data;
540 }
541
542 if ((string)($data['type'] ?? '') !== 'delete') {
543 return $data;
544 }
545
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'
553 : 'Medium loeschen';
554 $data['data']['delete_disabled'] = $used ? 'aria-disabled="true" tabindex="-1"' : '';
555 $data['data']['confirm'] = $report->_msg_confirm_delete;
556
557 return $data;
558 }
559
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 === '') {
562 return;
563 }
564
565 $key = $type . '|' . $lng . '|' . $id;
566 $map[$mediaId][$key] = array(
567 'lng' => $lng,
568 'id' => $id,
569 'title' => ($type === 'folder' ? 'Ordner: ' : '') . ($title !== '' ? $title : ('#' . $id)),
570 'type' => $type,
571 );
572 }
573
574 private function load_media_usages($db): array {
575 $map = 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();
578
579 foreach (dbxContentLngSync::accessibleLngs() as $lng) {
580 $lng = strtolower(trim((string)$lng));
581 if ($lng === '') {
582 continue;
583 }
584
585 $pages = $db->select(
586 dbxContentLng::ddContent($lng),
587 '',
588 'id,title,hero_image_id,content',
589 'id',
590 'ASC',
591 '',
592 0,
593 0,
594 0
595 );
596 $folders = $db->select(
597 dbxContentLng::ddFolder($lng),
598 '',
599 'id,name,hero_image_id',
600 'id',
601 'ASC',
602 '',
603 0,
604 0,
605 0
606 );
607
608 $pagesById = array();
609 foreach (is_array($pages) ? $pages : array() as $page) {
610 $pageId = (int)($page['id'] ?? 0);
611 if ($pageId <= 0) {
612 continue;
613 }
614 $pagesById[$pageId] = $page;
615
616 $heroId = (int)($page['hero_image_id'] ?? 0);
617 if ($heroId > 0) {
618 $this->add_media_usage($map, $heroId, $lng, $pageId, (string)($page['title'] ?? ''), 'page');
619 }
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');
623 }
624 }
625 }
626
627 $foldersById = array();
628 foreach (is_array($folders) ? $folders : array() as $folder) {
629 $folderId = (int)($folder['id'] ?? 0);
630 if ($folderId <= 0) {
631 continue;
632 }
633 $foldersById[$folderId] = $folder;
634 $heroId = (int)($folder['hero_image_id'] ?? 0);
635 if ($heroId > 0) {
636 $this->add_media_usage($map, $heroId, $lng, $folderId, (string)($folder['name'] ?? ''), 'folder');
637 }
638 }
639
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(
646 $map,
647 $mediaId,
648 $lng,
649 $contentId,
650 (string)($pagesById[$contentId]['title'] ?? ''),
651 'page'
652 );
653 } elseif ($folderId > 0 && isset($foldersById[$folderId])) {
654 $this->add_media_usage(
655 $map,
656 $mediaId,
657 $lng,
658 $folderId,
659 (string)($foldersById[$folderId]['name'] ?? ''),
660 'folder'
661 );
662 }
663 }
664 }
665
666 foreach ($map as $mediaId => $items) {
667 $map[$mediaId] = array_values($items);
668 }
669 return $map;
670 }
671
672 public function folder_next_record($report, $record) {
673 if (!is_array($record)) {
674 return $record;
675 }
676
677 $record['folder_path'] = $this->content_folder_path($record['id'] ?? 0);
678 return $record;
679 }
680
681 public function folder_row_action_data($report, $data) {
682 if (!is_array($data) || !isset($data['data']) || !is_array($data['data'])) {
683 return $data;
684 }
685
686 $type = (string)($data['type'] ?? '');
687 $rid = (int)($data['data']['rid'] ?? 0);
688
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';
698 }
699
700 return $data;
701 }
702
703 private function report_content() {
704 return $this->report_content_grid();
705 }
706
707 private function report_folders() {
708 $db = dbx()->get_system_obj('dbxDB');
709
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);
714 }
715
716 $fields = array(
717 'id' => 'ID',
718 'folder_path' => 'Ordner',
719 'template' => 'Template',
720 'group_read' => 'Leserechte',
721 );
722
723 $rows = $db->select(
724 dbxContentLng::ddFolder(),
725 '',
726 'id,name,parent_id,template,group_read',
727 'name',
728 'ASC',
729 '',
730 0,
731 0,
732 0
733 );
734 if (!is_array($rows)) {
735 $rows = array();
736 }
737
738 $this->content_folders = array();
739 foreach ($rows as $index => $row) {
740 $folderId = (int)($row['id'] ?? 0);
741 if ($folderId > 0) {
742 $this->content_folders[$folderId] = $row;
743 }
744 $rows[$index]['folder_path'] = '';
745 }
746
747 return $this->report_rows('list_folder', $fields, $rows);
748 }
749
750 private function report_media() {
751 $db = dbx()->get_system_obj('dbxDB');
752
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);
759 }
760
761 $fields = array(
762 'thumbnail' => 'Vorschau',
763 'id' => 'ID',
764 'title' => 'Titel',
765 'media_type' => 'Typ',
766 'media_folder' => 'Ordner',
767 'provider' => 'Provider',
768 'usage' => 'Verwendung',
769 'active' => 'Aktiv',
770 );
771
772 $rows = $db->select(
773 'dbxMedia',
774 '',
775 'id,title,media_type,file_name,media_folder,provider,provider_id,active',
776 'id',
777 'DESC',
778 '',
779 0,
780 0,
781 0
782 );
783 if (!is_array($rows)) {
784 $rows = array();
785 }
786
787 foreach ($rows as $index => $row) {
788 $rows[$index]['thumbnail'] = '';
789 $rows[$index]['usage'] = '';
790 }
791
792 return $this->report_rows('list_media', $fields, $rows);
793 }
794
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();
799
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.',
803 ));
804 }
805
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;
812 $mediaHtml = '';
813
814 if ($type === 'image') {
815 $mediaHtml =
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') . '"'
822 : '';
823 $mediaHtml =
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);
830 $mediaHtml =
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>';
835 } else {
836 $mediaHtml =
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>';
840 }
841
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'),
848 'id' => (string)$id,
849 ));
850 }
851
852 public function template_row_action_data($report, $data) {
853 if (!is_array($data) || !isset($data['data']) || !is_array($data['data'])) {
854 return $data;
855 }
856
857 $record = is_array($data['record'] ?? null) ? $data['record'] : array();
858 $type = (string)($data['type'] ?? '');
859 $tpl = trim((string)($record['name'] ?? ''));
860
861 if ($type === 'edit') {
862 $modul = trim((string)($record['modul'] ?? 'dbxContent'));
863 $fileType = trim((string)($record['type'] ?? 'htm'));
864
865 $relPath = 'dbx/modules/' . $modul . '/tpl/' . $fileType . '/' . $tpl . '.' . $fileType;
866 $url = '?dbx_modul=dbxEditor&dbx_run1=edit&file=' . rawurlencode($relPath) . '&dbx_window=1';
867
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=' .
875 rawurlencode($tpl);
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;
880 }
881
882 return $data;
883 }
884
885 private function template_dir() {
886 return dbx_os_path_file(dbx_get_base_dir() . 'dbx/modules/dbxContent/tpl/htm/');
887 }
888
889 private function normalize_template_name($name) {
890 $name = trim((string)$name);
891 $name = preg_replace('/\.htm$/i', '', $name);
892
893 if ($name !== '' && stripos($name, 'c-') !== 0) {
894 $name = 'c-' . $name;
895 }
896
897 if ($name === '' || !preg_match('/^c-[a-zA-Z0-9][a-zA-Z0-9_-]*$/', $name)) {
898 return '';
899 }
900
901 return $name;
902 }
903
904 private function template_file_path($name) {
905 $name = $this->normalize_template_name($name);
906 if ($name === '') {
907 return '';
908 }
909
910 $dir = rtrim($this->template_dir(), '/\\') . DIRECTORY_SEPARATOR;
911 if (!is_dir($dir)) {
912 return '';
913 }
914
915 return $dir . $name . '.htm';
916 }
917
918 private function default_template_markup() {
919 return <<<'HTML'
920<article id="dbx_target_{i}" class="c-cms default">
921
922 <div class="title">
923 <h1>{cms:title}</h1>
924 </div>
925
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>
928 </section>
929
930 <section class="cms-header header">{cms:header}</section>
931
932 <section class="gallery {cms:gallery_class}">
933 <div class="gallery-list"
934 style="{gallery:style}"{gallery:data_dbx}>{cms:gallery}</div>
935 </section>
936
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>
941 </section>
942
943 <footer class="footer">{cms:footer}</footer>
944
945</article>
946
947HTML;
948 }
949
950 private function delete_template_file($name) {
951 $path = $this->template_file_path($name);
952 if ($path === '' || !is_file($path)) {
953 return false;
954 }
955
956 return @unlink($path);
957 }
958
959 private function create_template_file($name) {
960 $name = $this->normalize_template_name($name);
961 if ($name === '') {
962 return array('ok' => false, 'msg' => 'Ungueltiger Template-Name. Erlaubt: c-name mit Buchstaben, Zahlen, Bindestrich, Unterstrich.');
963 }
964
965 $path = $this->template_file_path($name);
966 if ($path === '') {
967 return array('ok' => false, 'msg' => 'Template-Pfad konnte nicht ermittelt werden.');
968 }
969
970 if (is_file($path)) {
971 return array('ok' => false, 'msg' => 'Template existiert bereits: ' . $name . '.htm');
972 }
973
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.');
977 }
978
979 return array('ok' => true, 'msg' => 'Template angelegt: ' . $name . '.htm', 'name' => $name);
980 }
981
982 private function templates_bar_actions() {
983 $url = '?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=template_new&dbx_window=1';
984
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>';
989 }
990
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';
994 $msg = '';
995 $templateName = '';
996
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);
1000
1001 if (!empty($result['ok'])) {
1002 return dbx()->redirect('?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=templates');
1003 }
1004
1005 $msg = $tpl->get_tpl('dbx|alert-danger', array(
1006 'msg' => (string)($result['msg'] ?? 'Template konnte nicht angelegt werden.'),
1007 ));
1008 }
1009
1010 return $tpl->get_tpl('dbxContent_admin|content-admin-template-new', array(
1011 'action' => $action,
1012 'template_name' => htmlspecialchars($templateName, ENT_QUOTES, 'UTF-8'),
1013 'msg' => $msg,
1014 ));
1015 }
1016
1017 private function report_templates() {
1018 $msgSuccess = '';
1019 $msgError = '';
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';
1025 } else {
1026 $msgError = 'Template konnte nicht geloescht werden.';
1027 }
1028 }
1029
1030 $dir = $this->template_dir();
1031 $files = is_dir($dir) ? glob($dir . '*.htm') : array();
1032 $rows = array();
1033
1034 if (is_array($files)) {
1035 sort($files, SORT_NATURAL | SORT_FLAG_CASE);
1036 foreach ($files as $file) {
1037 if (!is_file($file)) {
1038 continue;
1039 }
1040
1041 $name = pathinfo($file, PATHINFO_FILENAME);
1042 if (stripos($name, 'c-') !== 0) {
1043 continue;
1044 }
1045
1046 $rows[] = array(
1047 'modul' => 'dbxContent',
1048 'name' => $name,
1049 'type' => pathinfo($file, PATHINFO_EXTENSION),
1050 'size' => (string)filesize($file),
1051 'modified' => date('Y-m-d H:i:s', filemtime($file)),
1052 );
1053 }
1054 }
1055
1056 $fields = array(
1057 'modul' => 'Modul',
1058 'name' => 'Template',
1059 'type' => 'Typ',
1060 'size' => 'Bytes',
1061 'modified' => 'Geaendert',
1062 );
1063
1064 return $this->report_rows('templates', $fields, $rows, $msgSuccess, $msgError);
1065 }
1066
1067 public function run($work = '') {
1068 switch ($work) {
1069 case 'content_grid_read':
1070 $this->content_grid_read();
1071 break;
1072
1073 case 'content_grid_save':
1074 $this->content_grid_save();
1075 break;
1076
1077 case 'content_grid_delete':
1078 $this->content_grid_delete();
1079 break;
1080
1081 case 'content_grid_sync':
1082 $this->content_grid_sync();
1083 break;
1084
1085 case 'list_content':
1086 return $this->report_content();
1087
1088 case 'list_folder':
1089 return $this->render_section(
1090 'Ordner',
1091 'Content-Ordner der aktuell gewaehlten Sprache.',
1092 $this->report_folders()
1093 );
1094
1095 case 'list_media':
1096 return $this->render_section(
1097 'Medien',
1098 'Zentrale Medien aus dbxMedia.',
1099 $this->report_media()
1100 );
1101
1102 case 'media_view':
1103 return $this->media_view();
1104
1105 case 'templates':
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()
1111 );
1112
1113 case 'template_new':
1114 return $this->template_new();
1115 }
1116
1117 return dbx()->get_system_obj('dbxTPL')->get_tpl('dbx|alert-warning', array(
1118 'msg' => 'Content-Bereich (' . dbx()->html($work) . ') ist nicht definiert.',
1119 ));
1120 }
1121}
1122
1123?>
$index['name']
Definition .dd.php:162
$_record
Aktueller Datensatz im Body-Lauf.
forward_run_body($content)
$fields[]
Definition config.dd.php:16
$field
Definition config.dd.php:4
dbx_os_path_file($path_file)
Definition index.php:164
if( $syncRequest)
Definition index.php:520
dbx_get_base_dir($cut_Data=0)
Definition index.php:157
DBX schema administration.
if(! $db->connect_db_server($server)) $result
$cms
Definition run_job.php:206