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';
19 private $dd_media =
'dbxMedia';
21 public function run($action =
'seo') {
22 $this->apply_lng_context();
34 return $this->render_admin();
38 private function apply_lng_context(): void {
39 $lng = strtolower(trim((string)
dbx()->get_request_var(
'dbx_lng',
'')));
44 $allowed = dbxContentLngSync::accessibleLngs();
45 if (!in_array($lng, $allowed,
true)) {
49 dbx()->set_system_var(
'dbx_lng', $lng);
50 dbx()->set_remember_var(
'dbx_lng', $lng,
'dbx');
53 private function seo_json_response(array $data): void {
54 dbx()->json_response($data, true);
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);
65 private function connect_db(
$db) {
66 if (!is_object(
$db)) {
69 $db->connect_db_server(
'dbx|dbxContent.db3');
70 $db->connect_db_server(
'dbx|dbxMedia.db3');
73 private function request_json() {
74 $raw = file_get_contents(
'php://input');
75 if (!is_string($raw) || trim($raw) ===
'') {
78 $data = json_decode($raw,
true);
79 return is_array($data) ? $data : $_POST;
82 private function meta_robots_values() {
84 'index,follow' =>
'index, follow (Standard)',
85 'noindex,follow' =>
'noindex, follow',
86 'index,nofollow' =>
'index, nofollow',
87 'noindex,nofollow' =>
'noindex, nofollow',
91 private function render_admin() {
92 $db =
dbx()->get_system_obj(
'dbxDB');
93 $this->ensure_cms_schema(
$db);
96 dbxContentLng::ddContent(),
98 'id,title,permalink,activ',
106 if (!is_array($pages)) {
110 $cid = (int)
dbx()->get_modul_var(
'cid', 0,
'int');
111 if ($cid <= 0 && !empty($pages[0][
'id'])) {
112 $cid = (int)$pages[0][
'id'];
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>';
128 $form = $this->render_seo_form();
129 $content =
dbx()->get_system_obj(
'dbxTPL')->get_tpl(
'dbxContent_admin|seo-admin', array(
130 'page_options' => $options,
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),
142 return dbx()->get_system_obj(
'dbxTPL')->get_tpl(
'dbxContent_admin|content-admin-section', array(
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>',
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(
157 'meta_robots' =>
'index,follow',
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'));
169 private function page_json() {
170 $cid = (int)
dbx()->get_modul_var(
'id', 0,
'int');
172 $cid = (int)
dbx()->get_modul_var(
'cid', 0,
'int');
175 $this->seo_json_response(array(
'ok' => 0,
'error' =>
'Keine Seiten-ID.'));
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.'));
186 $this->seo_json_response(array(
189 'seo_preview_media' => $this->seo_preview_media(
$db, $row),
193 private function save_json() {
194 $payload = $this->request_json();
195 $id = (int)($payload[
'id'] ?? 0);
197 $this->seo_json_response(array(
'ok' => 0,
'error' =>
'Keine Seiten-ID.'));
200 $db =
dbx()->get_system_obj(
'dbxDB');
201 $this->connect_db(
$db);
202 $this->ensure_cms_schema(
$db);
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)),
211 if (
$db->update(dbxContentLng::ddContent(), $data, $id) !== 1) {
212 $this->seo_json_response(array(
'ok' => 0,
'error' =>
'Speichern fehlgeschlagen.'));
215 dbxContentLngSync::afterPageSave(
$db, $id,
false);
216 dbxContentPageCache::invalidateContent($id);
217 dbxContentPageCache::invalidateAllMenus();
218 $this->sync_page_meta(
$db, $id);
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(
223 'row' => is_array($row) ? $row : array(),
224 'seo_preview_media' => $this->seo_preview_media(
$db, is_array($row) ? $row : array()),
228 private function sync_page_meta(
$db,
int $cid) {
230 if ($cid <= 0 || !dbxContentPageCache::isConfigEnabled()) {
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)) {
239 $renderer =
new dbxContentRenderer();
240 $rights = $renderer->getPublicFolderRights((
int)($rec[
'folder'] ?? 0));
241 dbxContentPageCache::writePageMeta($cid, array(
243 'permalink' => (
string)($rec[
'permalink'] ??
''),
245 'activ' => (
int)($rec[
'activ'] ?? 1),
246 'saved_at' => date(
'c'),
247 'seo' => dbxContentRenderer::seoMetaFromRecord($rec),
250 $permalink = trim((
string)($rec[
'permalink'] ??
''));
251 if ($permalink !==
'' && (
int)($rec[
'activ'] ?? 0) === 1) {
252 dbxContentPermalinkIndex::upsertPage($cid, $permalink, $rights, 1, dbxContentLng::current());
255 if (class_exists(
'dbx\\dbxContent\\dbxContentSitemap')) {
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);
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 :
'';
274 if ($id > 0 && !empty($media[
'thumb_file_path'])) {
275 $thumb .=
'&dbx_thumb=1';
279 'title' => (
string)($media[
'title'] ??
''),
280 'mime' => (
string)($media[
'mime'] ??
''),
281 'media_type' => (
string)($media[
'media_type'] ??
''),
283 'thumb_url' => $thumb,
284 'preview_url' => $thumb,
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);
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')) {
304 $db =
dbx()->get_system_obj(
'dbxDB');
305 if (!is_object(
$db))
return 0;
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);
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);
319 if (!is_file(self::OG_SOURCE)) {
324 if (function_exists(
'dbx_os_path_file')) {
327 if (!is_dir($target_dir)) {
328 @mkdir($target_dir, 0775,
true);
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);
335 if (!is_file($target_file)) {
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);
345 $size = (int)@filesize($target_file);
346 $info = @getimagesize($target_file);
355 'title' =>
'dbXapp OG',
357 'file_name' =>
'dbxapp-og.png',
359 'mime' =>
'image/png',
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',
367 if (
$db->insert($this->dd_media, $insert) !== 1) {
370 $media_id = (int)
$db->get_insert_id();
373 if ($media_id <= 0) {
379 $config[self::CONFIG_OG_MEDIA_ID] = $media_id;
382 $this->assign_home_og_image(
$db, $media_id);
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();
391 if ($home_cid <= 0) {
395 $row =
$db->select1(dbxContentLng::ddContent(), $home_cid,
'seo_image_id', 0);
396 if (!is_array($row)) {
399 if ((
int)($row[
'seo_image_id'] ?? 0) > 0) {
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);