dbXapp 2.0
RAD, CMS, Module und Runtime-IDE fuer dbXapp
Loading...
Searching...
No Matches
dbxContent_seo.class.php
Go to the documentation of this file.
1<?php
2namespace dbx\dbxContent_admin;
3
4use dbx\dbxContent\dbxContentHome;
5use dbx\dbxContent\dbxContentLng;
6use dbx\dbxContent\dbxContentLngSync;
7use dbx\dbxContent\dbxContentPageCache;
8use dbx\dbxContent\dbxContentPermalinkIndex;
9use dbx\dbxContent\dbxContentRenderer;
10
11require_once dirname(__DIR__, 2) . '/dbxContent/include/dbxContent_bootstrap_sync.php';
12
14
15 private const OG_SOURCE = __DIR__ . '/../files/og/dbxapp-og.png';
16 private const OG_MEDIA_REL = 'media/dbxContent/seo/dbxapp-og.png';
17 private const CONFIG_OG_MEDIA_ID = 'default_seo_image_id';
18
19 private $dd_media = 'dbxMedia';
20
21 public function run($action = 'seo') {
22 $this->apply_lng_context();
23
24 switch ($action) {
25 case 'seo_page':
26 $this->page_json();
27 return '';
28 case 'seo_save':
29 $this->save_json();
30 return '';
31 case 'seo':
32 default:
34 return $this->render_admin();
35 }
36 }
37
38 private function apply_lng_context(): void {
39 $lng = strtolower(trim((string) dbx()->get_request_var('dbx_lng', '')));
40 if ($lng === '') {
41 return;
42 }
43
44 $allowed = dbxContentLngSync::accessibleLngs();
45 if (!in_array($lng, $allowed, true)) {
46 return;
47 }
48
49 dbx()->set_system_var('dbx_lng', $lng);
50 dbx()->set_remember_var('dbx_lng', $lng, 'dbx');
51 }
52
53 private function seo_json_response(array $data): void {
54 dbx()->json_response($data, true);
55 }
56
57 private function base_url($action, $params = array()) {
58 $url = '?dbx_modul=dbxContent_admin&dbx_run1=' . rawurlencode((string)$action);
59 foreach ($params as $key => $value) {
60 $url .= '&' . rawurlencode((string)$key) . '=' . rawurlencode((string)$value);
61 }
62 return $url;
63 }
64
65 private function connect_db($db) {
66 if (!is_object($db)) {
67 return;
68 }
69 $db->connect_db_server('dbx|dbxContent.db3');
70 $db->connect_db_server('dbx|dbxMedia.db3');
71 }
72
73 private function request_json() {
74 $raw = file_get_contents('php://input');
75 if (!is_string($raw) || trim($raw) === '') {
76 return $_POST;
77 }
78 $data = json_decode($raw, true);
79 return is_array($data) ? $data : $_POST;
80 }
81
82 private function meta_robots_values() {
83 return array(
84 'index,follow' => 'index, follow (Standard)',
85 'noindex,follow' => 'noindex, follow',
86 'index,nofollow' => 'index, nofollow',
87 'noindex,nofollow' => 'noindex, nofollow',
88 );
89 }
90
91 private function render_admin() {
92 $db = dbx()->get_system_obj('dbxDB');
93 $this->ensure_cms_schema($db);
94
95 $pages = $db->select(
96 dbxContentLng::ddContent(),
97 '',
98 'id,title,permalink,activ',
99 'title',
100 'ASC',
101 '',
102 0,
103 0,
104 0
105 );
106 if (!is_array($pages)) {
107 $pages = array();
108 }
109
110 $cid = (int)dbx()->get_modul_var('cid', 0, 'int');
111 if ($cid <= 0 && !empty($pages[0]['id'])) {
112 $cid = (int)$pages[0]['id'];
113 }
114
115 $options = '';
116 foreach ($pages as $page) {
117 if (!is_array($page)) continue;
118 $id = (int)($page['id'] ?? 0);
119 if ($id <= 0) continue;
120 $label = trim((string)($page['title'] ?? ''));
121 if ($label === '') $label = 'Seite #' . $id;
122 $permalink = trim((string)($page['permalink'] ?? ''));
123 if ($permalink !== '') $label .= ' (' . $permalink . ')';
124 $selected = ($id === $cid) ? ' selected' : '';
125 $options .= '<option value="' . $id . '"' . $selected . '>' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . '</option>';
126 }
127
128 $form = $this->render_seo_form();
129 $content = dbx()->get_system_obj('dbxTPL')->get_tpl('dbxContent_admin|seo-admin', array(
130 'page_options' => $options,
131 'cid' => $cid,
132 'seo_form' => $form,
133 'page_url' => dbx()->esc($this->base_url('seo_page')),
134 'save_url' => dbx()->esc($this->base_url('seo_save')),
135 'media_url' => dbx()->esc($this->base_url('cms_media')),
136 'media_folders_url' => dbx()->esc($this->base_url('cms_media_folders')),
137 'upload_url' => dbx()->esc($this->base_url('cms_upload')),
138 'external_video_url' => dbx()->esc($this->base_url('cms_external_video')),
139 'upload_max_bytes' => (string)(16 * 1024 * 1024),
140 ));
141
142 return dbx()->get_system_obj('dbxTPL')->get_tpl('dbxContent_admin|content-admin-section', array(
143 'title' => 'SEO',
144 'subtitle' => 'Keywords, Robots, OG-Bild und SEO-Titel pro Seite',
145 'content' => $content,
146 'bar_actions' => '<a class="btn btn-outline-primary btn-sm" href="?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=edit"><i class="bi bi-pencil-square"></i><span>Content CMS</span></a>',
147 ));
148 }
149
150 private function render_seo_form() {
151 $form = dbx()->get_system_obj('dbxForm');
152 $form->init('seo-page', 'seo-admin-form');
153 $form->_dd = dbxContentLng::ddContent();
154 $form->_data = array(
155 'id' => 0,
156 'keywords' => '',
157 'meta_robots' => 'index,follow',
158 'seo_title' => '',
159 'seo_image_id' => 0,
160 );
161 $form->add_fld('id', 'dbxContent_admin|cms-field-hidden', data: array('cms_field' => 'id', 'seo_field' => 'id'));
162 $form->add_fld('keywords', 'dbxContent_admin|cms-field-text', label: 'Keywords', rules: 'varchar', data: array('cms_field' => 'keywords', 'seo_field' => 'keywords'), placeholder: 'Optional, kommagetrennt');
163 $form->add_fld('meta_robots', 'dbxContent_admin|cms-field-select', label: 'Robots', rules: 'varchar', data: array('cms_field' => 'meta_robots', 'seo_field' => 'meta_robots'), options: $this->meta_robots_values());
164 $form->add_fld('seo_title', 'dbxContent_admin|cms-field-text', label: 'SEO-Titel', rules: 'varchar', data: array('cms_field' => 'seo_title', 'seo_field' => 'seo_title'), placeholder: 'Optional, ueberschreibt Seitentitel im head');
165 $form->add_fld('seo_image_id', 'dbxContent_admin|cms-field-hidden', rules: 'int', data: array('cms_field' => 'seo_image_id', 'seo_field' => 'seo_image_id'));
166 return $form->run();
167 }
168
169 private function page_json() {
170 $cid = (int)dbx()->get_modul_var('id', 0, 'int');
171 if ($cid <= 0) {
172 $cid = (int)dbx()->get_modul_var('cid', 0, 'int');
173 }
174 if ($cid <= 0) {
175 $this->seo_json_response(array('ok' => 0, 'error' => 'Keine Seiten-ID.'));
176 }
177
178 $db = dbx()->get_system_obj('dbxDB');
179 $this->connect_db($db);
180 $this->ensure_cms_schema($db);
181 $row = $db->select1(dbxContentLng::ddContent(), $cid, 'id,title,permalink,keywords,meta_robots,seo_title,seo_image_id,description', 0);
182 if (!is_array($row) || (int)($row['id'] ?? 0) <= 0) {
183 $this->seo_json_response(array('ok' => 0, 'error' => 'Seite nicht gefunden.'));
184 }
185
186 $this->seo_json_response(array(
187 'ok' => 1,
188 'row' => $row,
189 'seo_preview_media' => $this->seo_preview_media($db, $row),
190 ));
191 }
192
193 private function save_json() {
194 $payload = $this->request_json();
195 $id = (int)($payload['id'] ?? 0);
196 if ($id <= 0) {
197 $this->seo_json_response(array('ok' => 0, 'error' => 'Keine Seiten-ID.'));
198 }
199
200 $db = dbx()->get_system_obj('dbxDB');
201 $this->connect_db($db);
202 $this->ensure_cms_schema($db);
203
204 $data = array(
205 'keywords' => $this->clean_text($payload['keywords'] ?? '', 254),
206 'meta_robots' => $this->clean_text($payload['meta_robots'] ?? 'index,follow', 32),
207 'seo_title' => $this->clean_text($payload['seo_title'] ?? '', 254),
208 'seo_image_id' => max(0, (int)($payload['seo_image_id'] ?? 0)),
209 );
210
211 if ($db->update(dbxContentLng::ddContent(), $data, $id) !== 1) {
212 $this->seo_json_response(array('ok' => 0, 'error' => 'Speichern fehlgeschlagen.'));
213 }
214
215 dbxContentLngSync::afterPageSave($db, $id, false);
216 dbxContentPageCache::invalidateContent($id);
217 dbxContentPageCache::invalidateAllMenus();
218 $this->sync_page_meta($db, $id);
219
220 $row = $db->select1(dbxContentLng::ddContent(), $id, 'id,title,permalink,keywords,meta_robots,seo_title,seo_image_id,description', 0);
221 $this->seo_json_response(array(
222 'ok' => 1,
223 'row' => is_array($row) ? $row : array(),
224 'seo_preview_media' => $this->seo_preview_media($db, is_array($row) ? $row : array()),
225 ));
226 }
227
228 private function sync_page_meta($db, int $cid) {
229 $cid = (int)$cid;
230 if ($cid <= 0 || !dbxContentPageCache::isConfigEnabled()) {
231 return;
232 }
233
234 $rec = $db->select1(dbxContentLng::ddContent(), $cid, 'permalink,activ,folder,title,seo_title,description,keywords,meta_robots,seo_image_id,update_date,lng_uid', 0);
235 if (!is_array($rec)) {
236 return;
237 }
238
239 $renderer = new dbxContentRenderer();
240 $rights = $renderer->getPublicFolderRights((int)($rec['folder'] ?? 0));
241 dbxContentPageCache::writePageMeta($cid, array(
242 'cid' => $cid,
243 'permalink' => (string)($rec['permalink'] ?? ''),
244 'rights' => $rights,
245 'activ' => (int)($rec['activ'] ?? 1),
246 'saved_at' => date('c'),
247 'seo' => dbxContentRenderer::seoMetaFromRecord($rec),
248 ));
249
250 $permalink = trim((string)($rec['permalink'] ?? ''));
251 if ($permalink !== '' && (int)($rec['activ'] ?? 0) === 1) {
252 dbxContentPermalinkIndex::upsertPage($cid, $permalink, $rights, 1, dbxContentLng::current());
253 }
254
255 if (class_exists('dbx\\dbxContent\\dbxContentSitemap')) {
257 }
258 }
259
260 private function seo_preview_media($db, $row) {
261 if (!is_array($row)) return array();
262 $seo_id = (int)($row['seo_image_id'] ?? 0);
263 if ($seo_id <= 0) return array();
264 $media = $db->select1($this->dd_media, $seo_id);
265 if (!is_array($media) || (int)($media['id'] ?? 0) <= 0) return array();
266 if ((int)($media['active'] ?? 0) !== 1) return array();
267 return $this->media_row_json($media);
268 }
269
270 private function media_row_json(array $media) {
271 $id = (int)($media['id'] ?? 0);
272 $url = $id > 0 ? 'index.php?dbx_modul=dbxContent&dbx_run1=media&dbx_mid=' . $id : '';
273 $thumb = $url;
274 if ($id > 0 && !empty($media['thumb_file_path'])) {
275 $thumb .= '&dbx_thumb=1';
276 }
277 return array(
278 'id' => $id,
279 'title' => (string)($media['title'] ?? ''),
280 'mime' => (string)($media['mime'] ?? ''),
281 'media_type' => (string)($media['media_type'] ?? ''),
282 'url' => $url,
283 'thumb_url' => $thumb,
284 'preview_url' => $thumb,
285 );
286 }
287
288 private function clean_text($value, int $max = 0) {
289 $value = trim((string)$value);
290 if ($max > 0 && strlen($value) > $max) {
291 $value = substr($value, 0, $max);
292 }
293 return $value;
294 }
295
296 private function ensure_cms_schema($db) {
297 $cms = dbx()->get_include_obj('dbxContent_cms');
298 if (is_object($cms) && method_exists($cms, 'ensure_schema')) {
299 $cms->ensure_schema($db);
300 }
301 }
302
303 public function ensure_default_og_image() {
304 $db = dbx()->get_system_obj('dbxDB');
305 if (!is_object($db)) return 0;
306
307 $db->connect_db_server('dbx|dbxContent.db3');
308 $db->connect_db_server('dbx|dbxMedia.db3');
309 $this->ensure_cms_schema($db);
310 $media_id = (int)dbx()->get_config('dbxContent', self::CONFIG_OG_MEDIA_ID);
311 if ($media_id > 0) {
312 $exists = $db->select1($this->dd_media, $media_id, 'id,active', 0);
313 if (is_array($exists) && (int)($exists['id'] ?? 0) > 0 && (int)($exists['active'] ?? 0) === 1) {
314 $this->assign_home_og_image($db, $media_id);
315 return $media_id;
316 }
317 }
318
319 if (!is_file(self::OG_SOURCE)) {
320 return 0;
321 }
322
323 $target_dir = rtrim(dbx_get_file_dir(), '/\\') . '/media/dbxContent/seo/';
324 if (function_exists('dbx_os_path_file')) {
325 $target_dir = dbx_os_path_file($target_dir);
326 }
327 if (!is_dir($target_dir)) {
328 @mkdir($target_dir, 0775, true);
329 }
330
331 $target_file = $target_dir . 'dbxapp-og.png';
332 if (!is_file($target_file) || filemtime(self::OG_SOURCE) > filemtime($target_file)) {
333 @copy(self::OG_SOURCE, $target_file);
334 }
335 if (!is_file($target_file)) {
336 return 0;
337 }
338
339 $rel = self::OG_MEDIA_REL;
340 $existing = $db->select1($this->dd_media, array('file_path' => $rel), 'id,active', 0);
341 if (is_array($existing) && (int)($existing['id'] ?? 0) > 0) {
342 $media_id = (int)$existing['id'];
343 $db->update($this->dd_media, array('active' => 1, 'title' => 'dbXapp OG', 'alt' => 'dbXapp'), $media_id);
344 } else {
345 $size = (int)@filesize($target_file);
346 $info = @getimagesize($target_file);
347 $insert = array(
348 'active' => 1,
349 'content_id' => 0,
350 'folder_id' => 0,
351 'slot' => 'seo',
352 'usage' => 'seo',
353 'sorter' => 1,
354 'template' => '',
355 'title' => 'dbXapp OG',
356 'alt' => 'dbXapp',
357 'file_name' => 'dbxapp-og.png',
358 'file_path' => $rel,
359 'mime' => 'image/png',
360 'size' => $size,
361 'width' => is_array($info) ? (int)($info[0] ?? 0) : 0,
362 'height' => is_array($info) ? (int)($info[1] ?? 0) : 0,
363 'media_type' => 'image',
364 'storage_type' => 'local',
365 'media_folder' => 'dbxContent/seo',
366 );
367 if ($db->insert($this->dd_media, $insert) !== 1) {
368 return 0;
369 }
370 $media_id = (int)$db->get_insert_id();
371 }
372
373 if ($media_id <= 0) {
374 return 0;
375 }
376
377 $config = dbx()->get_config('dbxContent');
378 if (!is_array($config)) $config = array();
379 $config[self::CONFIG_OG_MEDIA_ID] = $media_id;
380 dbx()->set_config('dbxContent', $config);
381
382 $this->assign_home_og_image($db, $media_id);
383 return $media_id;
384 }
385
386 private function assign_home_og_image($db, int $media_id) {
387 $home_cid = dbxContentHome::resolveCid();
388 if ($home_cid <= 0) {
389 $home_cid = dbxContentHome::masterCid();
390 }
391 if ($home_cid <= 0) {
392 return;
393 }
394
395 $row = $db->select1(dbxContentLng::ddContent(), $home_cid, 'seo_image_id', 0);
396 if (!is_array($row)) {
397 return;
398 }
399 if ((int)($row['seo_image_id'] ?? 0) > 0) {
400 return;
401 }
402
403 $db->update(dbxContentLng::ddContent(), array('seo_image_id' => $media_id), $home_cid);
404 dbxContentPageCache::invalidateContent($home_cid);
405 dbxContentPageCache::invalidateAllMenus();
406 $this->sync_page_meta($db, $home_cid);
407 }
408}
409
410?>
$config['version']
Definition config.php:2
dbx_os_path_file($path_file)
Definition index.php:164
dbx_get_file_dir()
Definition index.php:265
DBX schema administration.
$cms
Definition run_job.php:206