2namespace dbx\dbxContent;
4require_once __DIR__ .
'/dbxContent_bootstrap.php';
5require_once __DIR__ .
'/dbxContentTranslate.class.php';
6require_once __DIR__ .
'/dbxContent_permalink.class.php';
11 $lng = strtolower(trim((string)
dbx()->get_config(
'dbx',
'default_lng',
'de')));
12 if ($lng ===
'' || $lng ===
'undef') {
19 if (function_exists(
'dbx_accessible_lngs')) {
20 return dbx_accessible_lngs();
23 return array(self::masterLng());
26 public static function isMasterLng(?
string $lng =
null): bool {
27 if ($lng === null || $lng ===
'') {
31 return strtolower(trim((
string) $lng)) === self::masterLng();
34 public static function newUid(
string $prefix =
'p'): string {
35 $prefix = preg_replace(
'/[^a-z]/',
'', strtolower($prefix));
41 $rand = bin2hex(random_bytes(8));
42 }
catch (\Throwable $e) {
43 $rand = substr(sha1(uniqid((
string) mt_rand(),
true)), 0, 16);
46 return $prefix .
'_' . $rand;
50 if (!is_object(
$db)) {
59 foreach (self::accessibleLngs() as $lng) {
60 $contentTable =
'content_' . $lng;
61 $folderTable =
'content_folder_' . $lng;
62 if (!self::tableExists(
$db,
$server, $contentTable) && !self::tableExists(
$db,
$server, $folderTable)) {
66 self::ensureLngColumns(
$db,
$server, $contentTable);
67 self::ensureLngColumns(
$db,
$server, $folderTable);
68 self::ensureLngIndex(
$db,
$server, $contentTable);
69 self::ensureLngIndex(
$db,
$server, $folderTable);
74 if (!is_object(
$db)) {
78 self::ensureSchema(
$db);
79 $master = self::masterLng();
85 if (!is_object(
$db)) {
89 self::ensureSchema(
$db);
91 foreach (self::accessibleLngs() as $lng) {
92 $isMaster = $lng === self::masterLng();
100 if ($id <= 0 || !is_object(
$db)) {
104 $row =
$db->select1($dd, $id,
'lng_uid,lng_sync,lng_rev,lng_synced_rev', 0);
105 if (!is_array($row)) {
109 $uid = trim((
string) ($row[
'lng_uid'] ??
''));
114 $uid = self::newUid($prefix);
115 $sync = self::isMasterLng(self::lngFromDd($dd)) ?
'auto' :
'manual';
116 $db->update($dd, array(
120 'lng_synced_rev' => 0,
128 if ($id <= 0 || !is_object(
$db)) {
133 self::ensureRecordUid(
$db, $dd, $id,
'p');
135 if (!self::isMasterLng()) {
136 self::markManual(
$db, $dd, $id);
141 $row =
$db->select1($dd, $id,
'lng_rev,lng_sync', 0);
142 $rev = (int) ($row[
'lng_rev'] ?? 0);
144 $db->update($dd, array(
'lng_rev' => 1,
'lng_sync' =>
'auto'), $id, 0, 1, 1, 0);
147 $row =
$db->select1($dd, $id,
'lng_rev', 0);
148 $rev = max(1, (
int) ($row[
'lng_rev'] ?? 1)) + 1;
149 $db->update($dd, array(
'lng_rev' => $rev), $id, 0, 1, 1, 0);
155 if ($id <= 0 || !is_object(
$db)) {
160 self::ensureRecordUid(
$db, $dd, $id,
'f');
162 if (!self::isMasterLng()) {
163 self::markManual(
$db, $dd, $id);
168 $db->update($dd, array(
'lng_rev' => 1,
'lng_sync' =>
'auto'), $id, 0, 1, 1, 0);
170 $row =
$db->select1($dd, $id,
'lng_rev', 0);
171 $rev = max(1, (
int) ($row[
'lng_rev'] ?? 1)) + 1;
172 $db->update($dd, array(
'lng_rev' => $rev), $id, 0, 1, 1, 0);
178 if ($id <= 0 || !is_object(
$db)) {
182 $db->update($dd, array(
'lng_sync' =>
'manual'), $id, 0, 1, 1, 0);
186 $lngUid = trim($lngUid);
187 if ($lngUid ===
'' || !is_object(
$db)) {
195 $dd = self::ddForLng($dd, $lng);
196 $rows =
$db->select($dd,
"lng_uid = '" . str_replace(
"'",
"''", $lngUid) .
"'",
'id',
'id',
'ASC',
'', 1, 0, 0);
197 if (!is_array($rows) || !isset($rows[0][
'id'])) {
201 return (
int) $rows[0][
'id'];
205 $entity = $entity ===
'folder' ?
'folder' :
'page';
206 $masterId = (int) $masterId;
207 if ($masterId <= 0 || !is_object(
$db) || !self::isMasterLng()) {
211 $slaveLngs = self::slaveLngs();
212 if (!count($slaveLngs)) {
216 $master = self::masterLng();
217 $masterDd = $entity ===
'folder' ? dbxContentLng::ddFolder($master) : dbxContentLng::ddContent($master);
218 $masterRow =
$db->select1($masterDd, $masterId,
'lng_uid', 0);
219 $lngUid = trim((
string) ($masterRow[
'lng_uid'] ??
''));
220 if ($lngUid ===
'') {
224 $dd = $entity ===
'folder' ? dbxContentLng::ddFolder() : dbxContentLng::ddContent();
225 foreach ($slaveLngs as $lng) {
226 if (self::resolveIdByUid(
$db, $dd, $lngUid, $lng) <= 0) {
235 $lngUid = trim($lngUid);
236 $entity = $entity ===
'folder' ?
'folder' :
'page';
238 'lng_uid' => $lngUid,
240 'master_lng' => self::masterLng(),
242 'languages' => array(),
245 if ($lngUid ===
'' || !is_object(
$db)) {
251 foreach (self::accessibleLngs() as $lng) {
252 $lngDd = self::ddForLng($dd, $lng);
253 $row = self::selectByUid(
$db, $lngDd, $lngUid);
259 if (is_array($row)) {
260 $id = (int) ($row[
'id'] ?? 0);
261 $sync = strtolower(trim((
string) ($row[
'lng_sync'] ??
'auto')));
265 $title = $entity ===
'folder'
266 ? (string) ($row[
'name'] ??
'')
267 : (string) ($row[
'title'] ??
'');
268 $status = $lng === self::masterLng() ?
'master' : $sync;
271 $out[
'languages'][$lng] = array(
277 'is_master' => $lng === self::masterLng() ? 1 : 0,
285 $languages = is_array($coverage[
'languages'] ?? null) ? $coverage[
'languages'] : array();
286 if (!count($languages)) {
291 foreach ($languages as $lng => $item) {
292 if (!is_array($item)) {
295 $status = (string) ($item[
'status'] ??
'missing');
296 $class =
'dbx-cms-lng-badge is-' . preg_replace(
'/[^a-z]/',
'', $status);
297 $title = strtoupper($lng) .
': ' . $status;
298 if (!empty($item[
'title'])) {
299 $title .=
' — ' . $item[
'title'];
301 $parts[] =
'<span class="' . $class .
'" data-lng="' . htmlspecialchars($lng, ENT_QUOTES,
'UTF-8') .
'" title="' . htmlspecialchars($title, ENT_QUOTES,
'UTF-8') .
'">' . htmlspecialchars(strtoupper($lng), ENT_QUOTES,
'UTF-8') .
'</span>';
304 if (!count($parts)) {
308 return '<span class="dbx-cms-lng-badges" aria-label="Sprachabdeckung">' . implode(
'', $parts) .
'</span>';
314 foreach (self::accessibleLngs() as $lng) {
315 if ($lng !== $master) {
323 $masterFolderId = (int) $masterFolderId;
324 if ($masterFolderId <= 0 || !is_object(
$db)) {
329 if (!is_array($row)) {
333 $uid = trim((
string) ($row[
'lng_uid'] ??
''));
338 return self::resolveIdByUid(
$db, dbxContentLng::ddFolder(), $uid, $targetLng);
345 $masterFolderId = (int) $masterFolderId;
346 if ($masterFolderId <= 0 || !is_object(
$db) || $depth > 100) {
350 $targetLng = strtolower(trim($targetLng));
351 $master = self::masterLng();
352 if ($targetLng ===
'' || $targetLng === $master) {
353 return $masterFolderId;
356 $existing = self::resolveFolderIdInLng(
$db, $masterFolderId, $targetLng);
361 $masterDd = dbxContentLng::ddFolder($master);
362 $masterRow =
$db->select1($masterDd, $masterFolderId);
363 if (!is_array($masterRow)) {
367 $lngUid = trim((
string) ($masterRow[
'lng_uid'] ??
''));
368 if ($lngUid ===
'') {
369 $lngUid = self::ensureRecordUid(
$db, $masterDd, $masterFolderId,
'f');
371 if ($lngUid ===
'') {
375 $existing = self::resolveIdByUid(
$db, dbxContentLng::ddFolder(), $lngUid, $targetLng);
380 $parentTargetId = self::ensureFolderIdInLng(
$db, (
int) ($masterRow[
'parent_id'] ?? 0), $targetLng, $depth + 1);
382 $name = dbxContentTranslate::translate((
string) ($masterRow[
'name'] ??
''), $master, $targetLng,
'folder_name');
383 if ($name ===
'' && trim((
string) ($masterRow[
'name'] ??
'')) !==
'') {
384 $name = (string) $masterRow[
'name'];
390 $masterRev = max(1, (
int) ($masterRow[
'lng_rev'] ?? 1));
391 $data = self::copyFolderStructure($masterRow);
392 $data[
'name'] = $name;
393 $data[
'parent_id'] = $parentTargetId;
394 $data[
'lng_uid'] = $lngUid;
395 $data[
'lng_sync'] =
'auto';
396 $data[
'lng_rev'] = 0;
397 $data[
'lng_synced_rev'] = $masterRev;
399 $targetDd = dbxContentLng::ddFolder($targetLng);
400 if (
$db->insert($targetDd, $data, 0, 1, 0, 1) !== 1) {
404 return (
int)
$db->get_insert_id();
407 public static function previewProvision(
$db,
string $entity,
int $masterId, array $lngs = array()): array {
408 $entity = $entity ===
'folder' ?
'folder' :
'page';
409 $masterId = (int) $masterId;
410 $master = self::masterLng();
413 'master_id' => $masterId,
414 'master_lng' => $master,
419 if ($masterId <= 0 || !is_object(
$db) || !self::isMasterLng()) {
424 $masterRow =
$db->select1($masterDd, $masterId);
425 if (!is_array($masterRow)) {
429 $lngUid = trim((
string) ($masterRow[
'lng_uid'] ??
''));
430 if ($lngUid ===
'') {
431 $lngUid = self::ensureRecordUid(
$db, $masterDd, $masterId, $entity ===
'folder' ?
'f' :
'p');
433 $out[
'lng_uid'] = $lngUid;
435 if (!is_array($lngs) || !count($lngs)) {
436 $lngs = self::slaveLngs();
439 foreach ($lngs as $lng) {
440 $lng = strtolower(trim((
string) $lng));
441 if ($lng ===
'' || $lng === $master) {
445 $targetDd = $entity ===
'folder' ? dbxContentLng::ddFolder($lng) : dbxContentLng::ddContent($lng);
446 $existingId = self::resolveIdByUid(
$db, $targetDd, $lngUid, $lng);
447 $existing = $existingId > 0 ?
$db->select1($targetDd, $existingId) :
null;
450 'exists' => $existingId > 0 ? 1 : 0,
452 'lng_sync' => is_array($existing) ? (
string) ($existing[
'lng_sync'] ??
'auto') :
'',
454 'warnings' => array(),
457 if ($entity ===
'folder') {
458 $name = (string) ($masterRow[
'name'] ??
'');
459 $item[
'name'] = dbxContentTranslate::translate($name, $master, $lng,
'folder_name');
460 if ($item[
'name'] ===
'' && $name !==
'') {
461 $item[
'name'] = $name;
464 $title = (string) ($masterRow[
'title'] ??
'');
465 $item[
'title'] = dbxContentTranslate::translate($title, $master, $lng,
'title');
466 if ($item[
'title'] ===
'' && $title !==
'') {
467 $item[
'title'] = $title;
469 $item[
'description'] = dbxContentTranslate::translate((
string) ($masterRow[
'description'] ??
''), $master, $lng,
'description');
470 $item[
'keywords'] = dbxContentTranslate::translate((
string) ($masterRow[
'keywords'] ??
''), $master, $lng,
'keywords');
471 $item[
'content'] = dbxContentTranslate::translate((
string) ($masterRow[
'content'] ??
''), $master, $lng,
'content');
473 $folderId = self::resolveFolderIdInLng(
$db, (
int) ($masterRow[
'folder'] ?? 0), $lng);
474 if ((
int) ($masterRow[
'folder'] ?? 0) > 0 && $folderId <= 0) {
475 $item[
'warnings'][] =
'Ordnerstruktur in ' . strtoupper($lng) .
' wird bei Uebernahme automatisch angelegt.';
477 $item[
'folder'] = $folderId;
478 $item[
'permalink'] = dbxContent_permalink::build(
480 dbxContentLng::ddFolder($lng),
482 (
string) $item[
'title']
486 $out[
'items'][] = $item;
493 $entity = $entity ===
'folder' ?
'folder' :
'page';
494 $masterId = (int) $masterId;
495 $result = array(
'ok' => 0,
'created' => array(),
'updated' => array(),
'errors' => array());
497 if ($masterId <= 0 || !is_object(
$db) || !self::isMasterLng()) {
498 $result[
'errors'][] =
'Nur in der Master-Sprache moeglich.';
502 $master = self::masterLng();
504 $masterRow =
$db->select1($masterDd, $masterId);
505 if (!is_array($masterRow)) {
506 $result[
'errors'][] =
'Master-Datensatz nicht gefunden.';
510 $lngUid = trim((
string) ($masterRow[
'lng_uid'] ??
''));
511 if ($lngUid ===
'') {
512 $lngUid = self::ensureRecordUid(
$db, $masterDd, $masterId, $entity ===
'folder' ?
'f' :
'p');
515 $masterRev = max(1, (
int) ($masterRow[
'lng_rev'] ?? 1));
517 foreach ($items as $item) {
518 if (!is_array($item)) {
521 if ((
int) ($item[
'enabled'] ?? 0) !== 1) {
525 $lng = strtolower(trim((
string) ($item[
'lng'] ??
'')));
526 if ($lng ===
'' || $lng === $master) {
530 $targetDd = $entity ===
'folder' ? dbxContentLng::ddFolder($lng) : dbxContentLng::ddContent($lng);
531 $existingId = self::resolveIdByUid(
$db, $targetDd, $lngUid, $lng);
533 if ($entity ===
'folder') {
534 $name = trim((
string) ($item[
'name'] ??
''));
536 $result[
'errors'][] =
'Name fuer ' . strtoupper($lng) .
' fehlt.';
539 $data = self::copyFolderStructure($masterRow);
540 $data[
'name'] = $name;
541 $data[
'lng_uid'] = $lngUid;
542 $data[
'lng_sync'] =
'auto';
543 $data[
'lng_rev'] = 0;
544 $data[
'lng_synced_rev'] = $masterRev;
545 $data[
'parent_id'] = self::ensureFolderIdInLng(
$db, (
int) ($masterRow[
'parent_id'] ?? 0), $lng);
547 $title = trim((
string) ($item[
'title'] ??
''));
549 $result[
'errors'][] =
'Titel fuer ' . strtoupper($lng) .
' fehlt.';
552 $folderId = (int) ($item[
'folder'] ?? 0);
553 if ($folderId <= 0) {
554 $folderId = self::ensureFolderIdInLng(
$db, (
int) ($masterRow[
'folder'] ?? 0), $lng);
556 if ((
int) ($masterRow[
'folder'] ?? 0) > 0 && $folderId <= 0) {
557 $result[
'errors'][] =
'Ordnerstruktur in ' . strtoupper($lng) .
' konnte nicht angelegt werden.';
560 $data = self::copyPageStructure($masterRow);
562 $data[
'folder'] = $folderId;
563 $data[
'title'] = $title;
564 $data[
'permalink'] = trim((
string) ($item[
'permalink'] ??
''));
565 if ($data[
'permalink'] ===
'') {
566 $data[
'permalink'] = dbxContent_permalink::build(
$db, dbxContentLng::ddFolder($lng), $folderId, $title);
568 $data[
'description'] = (string) ($item[
'description'] ??
'');
569 $data[
'keywords'] = (string) ($item[
'keywords'] ??
'');
570 $data[
'content'] = (string) ($item[
'content'] ??
'');
571 $data[
'lng_uid'] = $lngUid;
572 $data[
'lng_sync'] =
'auto';
573 $data[
'lng_rev'] = 0;
574 $data[
'lng_synced_rev'] = $masterRev;
577 if ($existingId > 0) {
578 $ok =
$db->update($targetDd, $data, $existingId, 0, 1, 1, 0);
580 $result[
'updated'][] = array(
'lng' => $lng,
'id' => $existingId);
582 $result[
'errors'][] =
'Update in ' . strtoupper($lng) .
' fehlgeschlagen.';
585 $ok =
$db->insert($targetDd, $data, 0, 1, 0, 1);
587 $newId = (int)
$db->get_insert_id();
588 $result[
'created'][] = array(
'lng' => $lng,
'id' => $newId);
590 $result[
'errors'][] =
'Anlage in ' . strtoupper($lng) .
' fehlgeschlagen.';
600 $entity = $entity ===
'folder' ?
'folder' :
'page';
601 $masterId = (int) $masterId;
602 $result = array(
'updated' => array(),
'skipped' => array(),
'errors' => array());
604 if ($masterId <= 0 || !is_object(
$db) || !self::isMasterLng()) {
610 $master = self::masterLng();
612 $masterRow =
$db->select1($masterDd, $masterId);
613 if (!is_array($masterRow)) {
614 $result[
'errors'][] =
'Master-Datensatz nicht gefunden.';
618 $lngUid = trim((
string) ($masterRow[
'lng_uid'] ??
''));
619 if ($lngUid ===
'') {
623 $masterRev = max(1, (
int) ($masterRow[
'lng_rev'] ?? 1));
625 foreach (self::slaveLngs() as $lng) {
626 $targetDd = $entity ===
'folder' ? dbxContentLng::ddFolder($lng) : dbxContentLng::ddContent($lng);
627 $slaveId = self::resolveIdByUid(
$db, $targetDd, $lngUid, $lng);
630 $result[
'skipped'][] = array(
'lng' => $lng,
'reason' =>
'missing');
634 $slaveRow =
$db->select1($targetDd, $slaveId);
635 if (!is_array($slaveRow)) {
636 $result[
'skipped'][] = array(
'lng' => $lng,
'reason' =>
'not_found');
640 $sync = strtolower(trim((
string) ($slaveRow[
'lng_sync'] ??
'auto')));
641 if ($sync !==
'auto') {
642 $result[
'skipped'][] = array(
'lng' => $lng,
'reason' =>
'manual');
646 $syncedRev = (int) ($slaveRow[
'lng_synced_rev'] ?? 0);
647 if ($syncedRev >= $masterRev) {
648 $result[
'skipped'][] = array(
'lng' => $lng,
'reason' =>
'up_to_date');
652 if ($entity ===
'folder') {
653 $name = dbxContentTranslate::translate((
string) ($masterRow[
'name'] ??
''), $master, $lng,
'folder_name');
654 if ($name ===
'' && trim((
string) ($masterRow[
'name'] ??
'')) !==
'') {
655 $name = (string) $masterRow[
'name'];
658 $result[
'errors'][] =
'Name fuer ' . strtoupper($lng) .
' leer.';
662 $data = self::copyFolderStructure($masterRow);
663 $data[
'name'] = $name;
664 $data[
'parent_id'] = self::ensureFolderIdInLng(
$db, (
int) ($masterRow[
'parent_id'] ?? 0), $lng);
665 $data[
'lng_synced_rev'] = $masterRev;
667 $title = dbxContentTranslate::translate((
string) ($masterRow[
'title'] ??
''), $master, $lng,
'title');
668 if ($title ===
'' && trim((
string) ($masterRow[
'title'] ??
'')) !==
'') {
669 $title = (string) $masterRow[
'title'];
672 $result[
'errors'][] =
'Titel fuer ' . strtoupper($lng) .
' leer.';
676 $folderId = self::ensureFolderIdInLng(
$db, (
int) ($masterRow[
'folder'] ?? 0), $lng);
677 if ((
int) ($masterRow[
'folder'] ?? 0) > 0 && $folderId <= 0) {
678 $result[
'skipped'][] = array(
'lng' => $lng,
'reason' =>
'folder_missing');
682 $data = self::copyPageStructure($masterRow);
683 $data[
'activ'] = (int) ($slaveRow[
'activ'] ?? 0);
684 $data[
'folder'] = $folderId;
685 $data[
'title'] = $title;
686 $data[
'description'] = dbxContentTranslate::translate((
string) ($masterRow[
'description'] ??
''), $master, $lng,
'description');
687 $data[
'keywords'] = dbxContentTranslate::translate((
string) ($masterRow[
'keywords'] ??
''), $master, $lng,
'keywords');
688 $data[
'content'] = dbxContentTranslate::translate((
string) ($masterRow[
'content'] ??
''), $master, $lng,
'content');
689 $data[
'permalink'] = dbxContent_permalink::build(
$db, dbxContentLng::ddFolder($lng), $folderId, $title);
690 $data[
'lng_synced_rev'] = $masterRev;
693 $ok =
$db->update($targetDd, $data, $slaveId, 0, 1, 1, 0);
695 $result[
'updated'][] = array(
'lng' => $lng,
'id' => $slaveId,
'entity' => $entity);
697 $result[
'errors'][] =
'Sync nach ' . strtoupper($lng) .
' fehlgeschlagen.';
705 $folderId = (int) $folderId;
706 $out = array(
'deletable' => 0,
'reason' =>
'');
708 if ($folderId <= 0 || !is_object(
$db)) {
709 $out[
'reason'] =
'Ungueltiger Ordner.';
715 $childFolders = (int)
$db->count($folderDd,
'parent_id = ' . $folderId);
716 $childPages = (int)
$db->count($contentDd,
'folder = ' . $folderId);
718 if ($childFolders > 0 || $childPages > 0) {
720 if ($childFolders > 0) {
721 $parts[] = $childFolders .
' Unterordner';
723 if ($childPages > 0) {
724 $parts[] = $childPages .
' Seite(n)';
726 $out[
'reason'] = implode(
' und ', $parts) .
' vorhanden.';
730 $out[
'deletable'] = 1;
735 $entity = $entity ===
'folder' ?
'folder' :
'page';
737 $master = self::masterLng();
743 'master_lng' => $master,
744 'current_lng' => $current,
745 'is_master' => self::isMasterLng() ? 1 : 0,
749 if ($id <= 0 || !is_object(
$db)) {
754 $row =
$db->select1($dd, $id);
755 if (!is_array($row)) {
759 $lngUid = trim((
string) ($row[
'lng_uid'] ??
''));
760 if ($lngUid ===
'') {
761 $lngUid = self::ensureRecordUid(
$db, $dd, $id, $entity ===
'folder' ?
'f' :
'p');
763 $out[
'lng_uid'] = $lngUid;
765 foreach (self::accessibleLngs() as $lng) {
766 $targetDd = $entity ===
'folder' ? dbxContentLng::ddFolder($lng) : dbxContentLng::ddContent($lng);
767 $targetId = ($lng === $current) ? $id : self::resolveIdByUid(
$db, $targetDd, $lngUid, $lng);
768 if ($targetId <= 0) {
772 $targetRow =
$db->select1($targetDd, $targetId);
773 if (!is_array($targetRow)) {
777 $sync = strtolower(trim((
string) ($targetRow[
'lng_sync'] ??
'auto')));
782 $label = $entity ===
'folder'
783 ? (string) ($targetRow[
'name'] ??
'')
784 : (string) ($targetRow[
'title'] ??
'');
786 $label = $entity ===
'folder' ?
'Ordner #' . $targetId :
'Seite #' . $targetId;
794 'is_master' => $lng === $master ? 1 : 0,
795 'checked' => ($lng === $master || $sync ===
'auto') ? 1 : 0,
797 'block_reason' =>
'',
800 if ($entity ===
'folder') {
801 $check = self::folderDeletable(
$db, $lng, $targetId);
802 $item[
'deletable'] = (int) (
$check[
'deletable'] ?? 0);
803 $item[
'block_reason'] = (string) (
$check[
'reason'] ??
'');
804 if ($item[
'deletable'] !== 1) {
805 $item[
'checked'] = 0;
809 $out[
'items'][] = $item;
816 $entity = $entity ===
'folder' ?
'folder' :
'page';
820 if ($id <= 0 || !is_object(
$db) || !count($deleteLngs)) {
826 $row =
$db->select1($dd, $id,
'lng_uid', 0);
827 if (!is_array($row)) {
831 $lngUid = trim((
string) ($row[
'lng_uid'] ??
''));
832 if ($lngUid ===
'') {
833 $lngUid = self::ensureRecordUid(
$db, $dd, $id, $entity ===
'folder' ?
'f' :
'p');
836 foreach ($deleteLngs as $lng) {
837 $lng = strtolower(trim((
string) $lng));
838 if ($lng ===
'' || !in_array($lng, self::accessibleLngs(),
true)) {
842 $targetDd = $entity ===
'folder' ? dbxContentLng::ddFolder($lng) : dbxContentLng::ddContent($lng);
843 $targetId = ($lng === $current) ? $id : self::resolveIdByUid(
$db, $targetDd, $lngUid, $lng);
845 $out[] = array(
'lng' => $lng,
'id' => $targetId,
'entity' => $entity);
852 public static function resetSyncToAuto(
$db,
string $entity,
int $masterId, array $lngs = array()): array {
853 $entity = $entity ===
'folder' ?
'folder' :
'page';
854 $masterId = (int) $masterId;
855 $result = array(
'updated' => array(),
'skipped' => array(),
'errors' => array());
857 if ($masterId <= 0 || !is_object(
$db) || !self::isMasterLng()) {
858 $result[
'errors'][] =
'Nur in der Master-Sprache moeglich.';
862 $master = self::masterLng();
864 $masterRow =
$db->select1($masterDd, $masterId,
'lng_uid', 0);
865 if (!is_array($masterRow)) {
866 $result[
'errors'][] =
'Master-Datensatz nicht gefunden.';
870 $lngUid = trim((
string) ($masterRow[
'lng_uid'] ??
''));
871 if ($lngUid ===
'') {
872 $lngUid = self::ensureRecordUid(
$db, $masterDd, $masterId, $entity ===
'folder' ?
'f' :
'p');
875 if (!is_array($lngs) || !count($lngs)) {
876 $lngs = self::slaveLngs();
879 foreach ($lngs as $lng) {
880 $lng = strtolower(trim((
string) $lng));
881 if ($lng ===
'' || $lng === $master) {
885 $targetDd = $entity ===
'folder' ? dbxContentLng::ddFolder($lng) : dbxContentLng::ddContent($lng);
886 $slaveId = self::resolveIdByUid(
$db, $targetDd, $lngUid, $lng);
888 $result[
'skipped'][] = array(
'lng' => $lng,
'reason' =>
'missing');
892 $ok =
$db->update($targetDd, array(
'lng_sync' =>
'auto'), $slaveId, 0, 1, 1, 0);
894 $result[
'updated'][] = array(
'lng' => $lng,
'id' => $slaveId,
'entity' => $entity);
896 $result[
'errors'][] =
'Auto-Sync fuer ' . strtoupper($lng) .
' fehlgeschlagen.';
904 $rootFolderId = (int) $rootFolderId;
905 if ($rootFolderId <= 0 || !is_object(
$db)) {
909 $master = self::masterLng();
913 $queue = array($rootFolderId);
915 while (count($queue)) {
916 $current = (int) array_shift($queue);
917 if ($current <= 0 || isset($seen[$current])) {
921 $ordered[] = $current;
923 $rows =
$db->select($dd,
'parent_id = ' . $current,
'id',
'sorter,id',
'ASC',
'', 0, 0, 0);
924 if (!is_array($rows)) {
927 foreach ($rows as $row) {
928 $id = (int) ($row[
'id'] ?? 0);
929 if ($id > 0 && !isset($seen[$id])) {
939 if (!is_object(
$db) || !count($folderIds)) {
943 $folderIds = array_values(array_filter(array_map(
'intval', $folderIds),
function ($id) {
946 if (!count($folderIds)) {
950 $master = self::masterLng();
951 $dd = dbxContentLng::ddContent($master);
952 $rows =
$db->select($dd,
'folder IN (' . implode(
',', $folderIds) .
')',
'id',
'sorter,id',
'ASC',
'', 0, 0, 0);
953 if (!is_array($rows)) {
958 foreach ($rows as $row) {
959 $id = (int) ($row[
'id'] ?? 0);
968 $masterFolderId = (int) $masterFolderId;
971 'master_folder_id' => $masterFolderId,
972 'folders' => array(),
977 if ($masterFolderId <= 0 || !is_object(
$db) || !self::isMasterLng()) {
978 $result[
'errors'][] =
'Nur in der Master-Sprache moeglich.';
982 $folderIds = self::collectFolderSubtreeIds(
$db, $masterFolderId);
983 if (!count($folderIds)) {
984 $result[
'errors'][] =
'Ordner nicht gefunden.';
988 foreach ($folderIds as $folderId) {
989 $preview = self::previewProvision(
$db,
'folder', (
int) $folderId, $lngs);
991 foreach (is_array($preview[
'items'] ??
null) ? $preview[
'items'] : array() as $item) {
992 if (!is_array($item)) {
995 $item[
'enabled'] = 1;
998 $prov = self::provisionFromPreview(
$db,
'folder', (
int) $folderId, $items);
1000 'master_id' => (
int) $folderId,
1003 if (is_array($prov[
'errors'] ??
null)) {
1004 $result[
'errors'] = array_merge(
$result[
'errors'], $prov[
'errors']);
1008 $pageIds = self::collectPageIdsInFolders(
$db, $folderIds);
1009 foreach ($pageIds as $pageId) {
1010 $preview = self::previewProvision(
$db,
'page', (
int) $pageId, $lngs);
1012 foreach (is_array($preview[
'items'] ??
null) ? $preview[
'items'] : array() as $item) {
1013 if (!is_array($item)) {
1016 $item[
'enabled'] = 1;
1019 $prov = self::provisionFromPreview(
$db,
'page', (
int) $pageId, $items);
1021 'master_id' => (
int) $pageId,
1024 if (is_array($prov[
'errors'] ??
null)) {
1025 $result[
'errors'] = array_merge(
$result[
'errors'], $prov[
'errors']);
1030 $result[
'ok'] = $hasWork ? 1 : 0;
1034 private static function copyPageStructure(array $masterRow): array {
1035 $skip = array(
'id',
'create_date',
'create_uid',
'update_date',
'update_uid',
'owner',
'title',
'permalink',
'description',
'keywords',
'content',
'lng_uid',
'lng_sync',
'lng_rev',
'lng_synced_rev');
1037 foreach ($masterRow as $key => $value) {
1038 if (in_array($key, $skip,
true)) {
1041 $data[$key] = $value;
1046 private static function copyFolderStructure(array $masterRow): array {
1047 $skip = array(
'id',
'create_date',
'create_uid',
'update_date',
'update_uid',
'owner',
'name',
'parent_id',
'lng_uid',
'lng_sync',
'lng_rev',
'lng_synced_rev');
1049 foreach ($masterRow as $key => $value) {
1050 if (in_array($key, $skip,
true)) {
1053 $data[$key] = $value;
1059 $tpl =
dbx()->get_system_obj(
'dbxTPL');
1060 $master = self::masterLng();
1064 foreach (self::accessibleLngs() as $lng) {
1065 $active = $lng === $current ?
' is-active' :
'';
1066 $isMaster = $lng === $master ?
' is-master' :
'';
1067 $label = strtoupper($lng);
1068 if ($lng === $master) {
1071 $url =
'?dbx_modul=dbxContent_admin&dbx_run1=cms&dbx_lng=' . rawurlencode($lng);
1072 $items[] =
'<a class="dbx-cms-lng-tab' . $active . $isMaster .
'" href="' . htmlspecialchars($url, ENT_QUOTES,
'UTF-8') .
'" data-cms-lng="' . htmlspecialchars($lng, ENT_QUOTES,
'UTF-8') .
'">' . htmlspecialchars($label, ENT_QUOTES,
'UTF-8') .
'</a>';
1075 return $tpl->get_tpl(
'dbxContent_admin|cms-admin-lng-bar', array(
1076 'lng_bar_items' => implode(
'', $items),
1077 'master_lng' => $master,
1078 'current_lng' => $current,
1082 private static function backfillTableUids(
$db,
string $dd,
string $prefix,
bool $isMaster =
false): void {
1083 $rows =
$db->select($dd,
"(lng_uid IS NULL OR TRIM(lng_uid) = '')",
'id',
'id',
'ASC',
'', 0, 0, 0);
1084 if (!is_array($rows)) {
1088 $sync = $isMaster ?
'auto' :
'manual';
1090 foreach ($rows as $row) {
1091 if (!is_array($row)) {
1094 $id = (int) ($row[
'id'] ?? 0);
1098 $db->update($dd, array(
1099 'lng_uid' => self::newUid($prefix),
1100 'lng_sync' => $sync,
1102 'lng_synced_rev' => 0,
1103 ), $id, 0, 1, 1, 0);
1107 private static function selectByUid(
$db,
string $dd,
string $lngUid): ?array {
1108 $lngUid = str_replace(
"'",
"''", trim($lngUid));
1109 if ($lngUid ===
'') {
1113 $rows =
$db->select($dd,
"lng_uid = '" . $lngUid .
"'",
'*',
'id',
'ASC',
'', 1, 0, 0);
1114 if (!is_array($rows) || !isset($rows[0]) || !is_array($rows[0])) {
1121 private static function ddForLng(
string $dd,
string $lng): string {
1122 if (strpos($dd,
'content_folder_') === 0 || $dd === dbxContentLng::ddFolder()) {
1123 return dbxContentLng::ddFolder($lng);
1126 return dbxContentLng::ddContent($lng);
1129 private static function lngFromDd(
string $dd): string {
1130 if (preg_match(
'/_(de|en|es|[a-z]{2})$/', $dd, $m)) {
1134 return dbxContentLng::current();
1137 private static function ensureLngColumns(
$db,
string $server,
string $table): void {
1143 self::ensureColumn(
$db,
$server,
$table,
'lng_sync',
"TEXT DEFAULT 'auto'");
1145 self::ensureColumn(
$db,
$server,
$table,
'lng_synced_rev',
'INTEGER DEFAULT 0');
1148 private static function ensureLngIndex(
$db,
string $server,
string $table): void {
1155 $exists =
$db->select_query(
$server,
"SELECT name FROM sqlite_master WHERE type='index' AND name='" . str_replace(
"'",
"''",
$index) .
"'");
1156 if (is_array($exists) && !empty($exists)) {
1163 private static function ensureColumn(
$db,
string $server,
string $table,
string $name,
string $type): void {
1165 $name = preg_replace(
'/[^A-Za-z0-9_]+/',
'', $name);
1166 if (
$table ===
'' || $name ===
'') {
1171 if (is_array($cols)) {
1172 foreach ($cols as $col) {
1173 if (isset($col[
'name']) && strtolower((
string) $col[
'name']) === strtolower($name)) {
1179 $db->exec(
$server,
'ALTER TABLE ' .
$table .
' ADD COLUMN ' . $name .
' ' . $type);
1182 private static function tableExists(
$db,
string $server,
string $table): bool {
1188 $rows =
$db->select_query(
$server,
"SELECT name FROM sqlite_master WHERE type='table' AND name='" . str_replace(
"'",
"''",
$table) .
"' LIMIT 1");
1189 $row = is_array($rows) ? ($rows[0] ?? array()) : array();
1190 return is_array($row) && !empty($row[
'name']);
static ddFolder(string $lng='')
static ddContent(string $lng='')
static resolveFolderIdInLng($db, int $masterFolderId, string $targetLng)
static newUid(string $prefix='p')
static coverageForUid($db, string $entity, string $lngUid)
static resetSyncToAuto($db, string $entity, int $masterId, array $lngs=array())
static resolveIdByUid($db, string $dd, string $lngUid, string $lng='')
static afterPageSave($db, int $id, bool $isNew=false)
static collectPageIdsInFolders($db, array $folderIds)
static provisionFromPreview($db, string $entity, int $masterId, array $items)
static backfillMasterLng($db)
static folderDeletable($db, string $lng, int $folderId)
static ensureRecordUid($db, string $dd, int $id, string $prefix='p')
static previewDelete($db, string $entity, int $id)
static ensureFolderIdInLng($db, int $masterFolderId, string $targetLng, int $depth=0)
Ordner in Zielsprache aufloesen oder rekursiv aus Master-Struktur anlegen.
static collectFolderSubtreeIds($db, int $rootFolderId)
static markManual($db, string $dd, int $id)
static afterFolderSave($db, int $id, bool $isNew=false)
static syncSlavesFromMaster($db, string $entity, int $masterId)
static provisionFolderTree($db, int $masterFolderId, array $lngs=array())
static resolveDeleteIds($db, string $entity, int $id, array $deleteLngs)
static badgesHtml(array $coverage)
static hasMissingSlaveLng($db, string $entity, int $masterId)
static previewProvision($db, string $entity, int $masterId, array $lngs=array())
static isMasterLng(?string $lng=null)
DBX schema administration.
if(! $db->connect_db_server($server)) $result
foreach( $topics as $file=> $html)