Files
awo-hamburg-intranet/module/knowledgecenter_update/Views/post_cardform.php
Moritz Weinmann a0698d621e feat(wiki): Betriebsrat-Berechtigung (r-wiki-br)
Neue Permission r-wiki-br ermöglicht eingeschränkte Wiki-Bearbeitung:

- Neue Permission in main_permission (ID 30: r-wiki-br)
- Neue Spalte betriebsrat_editable in knowledgecenter_categories_update
- Checkbox "Für Betriebsrat bearbeitbar" in der Kategorie-Verwaltung
  (nur für r-wiki-Redakteure sichtbar und speicherbar)

Berechtigungslogik:
- r-wiki schlägt immer: Wer beide Rechte hat, verhält sich wie Redakteur
- Betriebsrat sieht nur Kategorien mit betriebsrat_editable=1
- "Beitrag erstellen"-Button nur in freigegebenen Kategorien sichtbar
- Neue Posts/Bearbeitungen auf betriebsrat_editable-Kategorien beschränkt
- Settings-Tab und Kategorieverwaltung weiterhin r-wiki-only
- Ajax-Endpoints (save_post_files, delete_post_file, upload_inline_image)
  prüfen Kategorie-Berechtigung serverseitig

File Gallery:
- Betriebsrat ist per kc_lock_path auf den Post-eigenen Ordner beschränkt
- Redakteur kann frei navigieren (kein Lock)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 13:16:36 +02:00

1046 lines
45 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// post_cardform.php
// ini_set('display_errors', 1);
// error_reporting(E_ALL);
// Aktueller Nutzer (z.B. via Session, hier exemplarisch aus den Globals)
$currentUserId = $GLOBALS["main_contact"]["id"] ?? 0;
// Lade die Berechtigungseinträge (Mandanten, Departments, Rollen) des aktuellen Nutzers
$userMandants = [];
$userDepartments = [];
$userRoles = [];
$userEinrichts = [];
$userFachbereiche = [];
if ($currentUserId) {
$query = "SELECT main_mandant_id, main_department_id, main_role_id, main_einricht_id, main_bereich_id
FROM main_contact_department
WHERE main_contact_id = $currentUserId AND active = 1";
$result = mysqli_query($GLOBALS['mysql_con'], $query);
while ($row = mysqli_fetch_assoc($result)) {
if (!in_array($row['main_mandant_id'], $userMandants)) {
$userMandants[] = $row['main_mandant_id'];
}
if (!in_array($row['main_department_id'], $userDepartments)) {
$userDepartments[] = $row['main_department_id'];
}
if (!in_array($row['main_role_id'], $userRoles)) {
$userRoles[] = $row['main_role_id'];
}
if ($row['main_einricht_id'] != 0 && !in_array($row['main_einricht_id'], $userEinrichts)) {
$userEinrichts[] = $row['main_einricht_id'];
}
if ($row['main_bereich_id'] != 0 && !in_array($row['main_bereich_id'], $userFachbereiche)) {
$userFachbereiche[] = $row['main_bereich_id'];
}
}
}
$saveIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check" viewBox="0 0 16 16">
<path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425z"/>
</svg>';
/**
* Prüft, ob der aktuelle User auf den Beitrag zugreifen darf
* basierend auf den Berechtigungstabellen für Mandanten, Abteilungen und Rollen.
* Der LEFT JOIN-Ansatz überprüft, ob in jedem Bereich (department, mandant, role)
* entweder keine Verknüpfung existiert oder, falls Verknüpfungen vorhanden sind,
* mindestens eine Übereinstimmung mit den erlaubten Werten vorliegt.
*
* @param int $postId Die ID des Beitrags, für den die Berechtigungsprüfung erfolgen soll.
* @return bool True, wenn der User in allen relevanten Bereichen mindestens einen Match hat oder die Einschränkung fehlt; ansonsten false.
*/
function userHasAccessForPost($postId)
{
// Superuser-Bypass: Hat der Nutzer die Berechtigung "super user", darf er den Artikel immer sehen
$mandantId = isset($GLOBALS['main_contact']['master_mandant_id']) ? (int) $GLOBALS['main_contact']['master_mandant_id'] : 0;
$userId = isset($GLOBALS['main_contact']['id']) ? (int) $GLOBALS['main_contact']['id'] : 0;
if (function_exists('get_permission_state')) {
$isSuperUser = get_permission_state('r-wiki', $mandantId, $userId);
if ($isSuperUser == 1) {
return true;
}
}
global $allowedMandants, $allowedDepartments, $allowedRoles, $allowedEinrichts, $allowedFachbereiche;
$sql = "
SELECT
COUNT(DISTINCT pd.department_id) AS cntDept,
SUM(IF(pd.department_id IN (" . ($allowedDepartments ? $allowedDepartments : 'NULL') . "), 1, 0)) AS matchDept,
COUNT(DISTINCT pm.mandant_id) AS cntMand,
SUM(IF(pm.mandant_id IN (" . ($allowedMandants ? $allowedMandants : 'NULL') . "), 1, 0)) AS matchMand,
COUNT(DISTINCT pr.role_id) AS cntRole,
SUM(IF(pr.role_id IN (" . ($allowedRoles ? $allowedRoles : 'NULL') . "), 1, 0)) AS matchRole,
COUNT(DISTINCT pe.einricht_id) AS cntEinricht,
SUM(IF(pe.einricht_id IN (" . ($allowedEinrichts ? $allowedEinrichts : 'NULL') . "), 1, 0)) AS matchEinricht,
COUNT(DISTINCT pfb.fachbereich_id) AS cntFachbereich,
SUM(IF(pfb.fachbereich_id IN (" . ($allowedFachbereiche ? $allowedFachbereiche : 'NULL') . "), 1, 0)) AS matchFachbereich
FROM knowledgecenter_posts AS p
LEFT JOIN knowledgecenter_post_department AS pd ON p.id = pd.post_id
LEFT JOIN knowledgecenter_post_mandant AS pm ON p.id = pm.post_id
LEFT JOIN knowledgecenter_post_role AS pr ON p.id = pr.post_id
LEFT JOIN knowledgecenter_post_einricht AS pe ON p.id = pe.post_id
LEFT JOIN knowledgecenter_post_fachbereich AS pfb ON p.id = pfb.post_id
WHERE p.id = $postId
GROUP BY p.id
";
$res = mysqli_query($GLOBALS['mysql_con'], $sql);
if (!$res || mysqli_num_rows($res) === 0) {
return true;
}
$row = mysqli_fetch_assoc($res);
$access = true;
if ($row['cntDept'] > 0 && $row['matchDept'] == 0) {
$access = false;
}
if ($row['cntMand'] > 0 && $row['matchMand'] == 0) {
$access = false;
}
if ($row['cntRole'] > 0 && $row['matchRole'] == 0) {
$access = false;
}
if ($row['cntEinricht'] > 0 && $row['matchEinricht'] == 0) {
$access = false;
}
if ($row['cntFachbereich'] > 0 && $row['matchFachbereich'] == 0) {
$access = false;
}
return $access;
}
// Hole alle Sprachen aus der Datenbank:
$allLanguages = [];
$sqlLang = "SELECT id, code, name FROM main_language";
$resLang = mysqli_query($GLOBALS['mysql_con'], $sqlLang);
while ($lang = mysqli_fetch_assoc($resLang)) {
$allLanguages[] = $lang;
}
$initialRedirectCategoryId = 0;
// akzeptiere beide Namen, falls du sie mal unterschiedlich geschickt hast
if (isset($_GET['initial_category_id'])) {
$initialRedirectCategoryId = (int)$_GET['initial_category_id'];
} elseif (isset($_GET['initial_category'])) {
$initialRedirectCategoryId = (int)$_GET['initial_category'];
}
if ($initialRedirectCategoryId > 0) {
// Ziel: /intranet/de/wiki/?detail=CATEGORYID&route=CATEGORYID
$redirectURL = '/intranet/de/wiki/?detail=' . $initialRedirectCategoryId . '&route=' . $initialRedirectCategoryId;
} elseif (!empty($_GET['redirectURL'])) {
// optional: hier könntest du noch erlaubte Domains/Prefixes whitelisten
$redirectURL = (string)$_GET['redirectURL'];
} else {
$redirectURL = '/';
}
// Prüfe, ob ein neuer Beitrag erstellt werden soll (Parameter new=1)
$isNew = isset($_GET['new']) && $_GET['new'] == 1;
// Initiale Kategorie aus der URL übernehmen (bei Neuerstellung)
$initialCategoryId = isset($_GET['InitialCategory']) ? (int) $_GET['InitialCategory'] : 0;
$InitialProduct = isset($_GET['InitialProduct']) ? (int) $_GET['InitialProduct'] : 0;
if ($isNew) {
require_wiki_edit_permission();
// Betriebsrat darf nur in Kategorien erstellen, die als betriebsrat_editable markiert sind
if (!is_wiki_redakteur() && $initialCategoryId > 0) {
$brCatRes = mysqli_query($GLOBALS['mysql_con'],
"SELECT betriebsrat_editable FROM knowledgecenter_categories_update WHERE id = $initialCategoryId LIMIT 1");
$brCatRow = mysqli_fetch_assoc($brCatRes);
if (!$brCatRow || !(int)$brCatRow['betriebsrat_editable']) {
echo "<p>Sie haben nicht die Berechtigung, in dieser Kategorie zu erstellen.</p>";
exit;
}
}
$canEditPost = true; // Berechtigung schon oben geprüft
$postId = 0;
$post = ['id' => 0];
$versionCount = 0;
$currentVersion = [
'id' => 0,
'title' => '',
'image' => '',
'teaser' => '',
'content' => '',
'version_number' => 0,
'created_at' => date("Y-m-d H:i:s")
];
} else {
// Ermitteln der Post-ID aus dem Query-String
if (!isset($_GET['id'])) {
echo "<p>Kein Beitrag ausgewählt.</p>";
exit;
}
$postId = (int) $_GET['id'];
// Sicherheitscheck: Prüfe, ob der Nutzer auf diesen Beitrag zugreifen darf
if (!userHasAccessForPost($postId)) {
echo "<p>Sie haben nicht die notwendigen Berechtigungen.</p>";
exit;
}
// Schreibrechte: Redakteur darf alles; Betriebsrat nur in freigegebenen Kategorien
$canEditPost = is_wiki_redakteur();
if (!$canEditPost && is_wiki_betriebsrat()) {
$brEditRes = mysqli_query($GLOBALS['mysql_con'],
"SELECT COUNT(*) AS cnt
FROM knowledgecenter_category_post kcp
JOIN knowledgecenter_categories_update kcu ON kcu.id = kcp.category_id
WHERE kcp.post_id = $postId AND kcu.betriebsrat_editable = 1");
$brEditRow = mysqli_fetch_assoc($brEditRes);
$canEditPost = ($brEditRow && (int)$brEditRow['cnt'] > 0);
}
// Beitrag aus der Haupttabelle abrufen
$sqlPost = "SELECT * FROM knowledgecenter_posts WHERE id = $postId LIMIT 1";
$resPost = mysqli_query($GLOBALS['mysql_con'], $sqlPost);
if (!$resPost || mysqli_num_rows($resPost) == 0) {
echo "<p>Beitrag nicht gefunden.</p>";
exit;
}
$post = mysqli_fetch_assoc($resPost);
// Anzahl Dateien ermitteln
$sqlCountFiles = "SELECT COUNT(*) AS fileCount FROM knowledgecenter_post_files WHERE post_id = $postId";
$resCountFiles = mysqli_query($GLOBALS['mysql_con'], $sqlCountFiles);
if ($resCountFiles && mysqli_num_rows($resCountFiles) > 0) {
$row = mysqli_fetch_assoc($resCountFiles);
$fileCount = (int) $row['fileCount'];
} else {
$fileCount = 0;
}
// Anzahl Versionen für die aktuell ausgewählte Sprache ermitteln
$sqlCountVersions = "SELECT COUNT(*) AS versionCount FROM knowledgecenter_post_versions WHERE post_id = $postId AND language_id = $languageId";
$resCountVersions = mysqli_query($GLOBALS['mysql_con'], $sqlCountVersions);
if ($resCountVersions && mysqli_num_rows($resCountVersions) > 0) {
$rowCountVersions = mysqli_fetch_assoc($resCountVersions);
$versionCount = (int) $rowCountVersions['versionCount'];
} else {
$versionCount = 0;
}
// Anzahl Ansprechpartner ermitteln
$sqlCountContacts = "SELECT COUNT(*) AS contactCount FROM knowledgecenter_post_contacts WHERE post_id = $postId";
$resCountContacts = mysqli_query($GLOBALS['mysql_con'], $sqlCountContacts);
if ($resCountContacts && mysqli_num_rows($resCountContacts) > 0) {
$row = mysqli_fetch_assoc($resCountContacts);
$contactCount = (int) $row['contactCount'];
} else {
$contactCount = 0;
}
// Prüfe, ob ein spezieller Versionsparameter übergeben wurde
if (isset($_GET['version']) && !empty($_GET['version'])) {
$versionId = (int) $_GET['version'];
// Versuche, die angeforderte Version zu laden aber nur, wenn sie aktuell gültig ist:
$sqlVersion = "SELECT kpv.*, mc.name AS author
FROM knowledgecenter_post_versions AS kpv
LEFT JOIN main_contact AS mc ON kpv.author_id = mc.id
WHERE kpv.post_id = $postId
AND kpv.language_id = $languageId
AND kpv.id = $versionId
LIMIT 1";
// var_dump($sqlVersion);
$resVersion = mysqli_query($GLOBALS['mysql_con'], $sqlVersion);
if ($resVersion && mysqli_num_rows($resVersion) > 0) {
// Die angeforderte Version existiert und ist aktuell gültig
$currentVersion = mysqli_fetch_assoc($resVersion);
} else {
// Die angeforderte Version ist entweder ungültig (z. B. noch in der Zukunft) oder existiert nicht
// => Ladevorgang: Suche nach der neuesten gültigen Version
$sqlVersion = "SELECT kpv.*, mc.name AS author
FROM knowledgecenter_post_versions AS kpv
LEFT JOIN main_contact AS mc ON kpv.author_id = mc.id
WHERE kpv.post_id = $postId
AND kpv.language_id = $languageId
AND kpv.state = 1
AND (kpv.valid_from IS NULL OR kpv.valid_from <= NOW())
AND (kpv.valid_until IS NULL OR kpv.valid_until >= NOW())
ORDER BY kpv.version_number DESC
LIMIT 1";
// var_dump($sqlVersion);
$resVersion = mysqli_query($GLOBALS['mysql_con'], $sqlVersion);
if ($resVersion && mysqli_num_rows($resVersion) > 0) {
$currentVersion = mysqli_fetch_assoc($resVersion);
} else {
// Es gibt überhaupt keine aktuell gültige Version, also setze einen Standard-Platzhalter
$currentVersion = [
'id' => 0,
'title' => 'Keine gültige Version vorhanden',
'image' => '',
'teaser' => '',
'content' => '',
'version_number' => 0,
'created_at' => date("Y-m-d H:i:s"),
'author' => ''
];
}
}
} else {
// Kein spezieller Versionsparameter übergeben lade die neueste gültige Version
$sqlVersion = "SELECT kpv.*, mc.name AS author
FROM knowledgecenter_post_versions AS kpv
LEFT JOIN main_contact AS mc ON kpv.author_id = mc.id
WHERE kpv.post_id = $postId
AND kpv.language_id = $languageId
AND kpv.state = 1
AND (kpv.valid_from IS NULL OR kpv.valid_from <= NOW())
AND (kpv.valid_until IS NULL OR kpv.valid_until >= NOW())
ORDER BY kpv.version_number DESC
LIMIT 1";
// var_dump($sqlVersion);
$resVersion = mysqli_query($GLOBALS['mysql_con'], $sqlVersion);
if ($resVersion && mysqli_num_rows($resVersion) > 0) {
$currentVersion = mysqli_fetch_assoc($resVersion);
} else {
// Fallback: Falls keine aktuell gültige Version existiert, wird kein Draft geladen
// (Alternativ kannst du hier einen Hinweis ausgeben)
$currentVersion = [
'id' => 0,
'title' => 'Keine gültige Version vorhanden',
'image' => '',
'teaser' => '',
'content' => '',
'version_number' => 0,
'created_at' => date("Y-m-d H:i:s"),
'author' => ''
];
}
}
}
// Expose current post title to JS for setting the page H1
echo "<script>window.kcPostTitle = " . json_encode(isset($currentVersion['title']) ? $currentVersion['title'] : null) . ";</script>";
// Alle Versionen für diesen Beitrag (absteigend sortiert) für die aktuell gewählte Sprache abrufen
$sqlAllVersions = "SELECT kpv.*, mc.name AS author
FROM knowledgecenter_post_versions AS kpv
LEFT JOIN main_contact AS mc ON kpv.author_id = mc.id
WHERE kpv.post_id = $postId
AND kpv.language_id = $languageId
ORDER BY kpv.version_number DESC";
$resAllVersions = mysqli_query($GLOBALS['mysql_con'], $sqlAllVersions);
$versions = [];
while ($v = mysqli_fetch_assoc($resAllVersions)) {
$versions[] = $v;
}
// Lade translations für alle Sprachen: Für jede Sprache wird die neueste Version für diesen Beitrag geladen
$translations = [];
foreach ($allLanguages as $lang) {
$langId = (int) $lang['id'];
$sqlTrans = "SELECT * FROM knowledgecenter_post_versions
WHERE post_id = $postId
AND language_id = $langId
ORDER BY version_number DESC
LIMIT 1";
$resTrans = mysqli_query($GLOBALS['mysql_con'], $sqlTrans);
if ($resTrans && mysqli_num_rows($resTrans) > 0) {
$translations[$langId] = mysqli_fetch_assoc($resTrans);
} else {
$translations[$langId] = [
'id' => 0,
'title' => '',
'image' => '',
'teaser' => '',
'content' => '',
'version_number' => 0,
'created_at' => ''
];
}
}
$linkedFormId = null;
$sqlLinkedForm = "SELECT form_id
FROM knowledgecenter_post_form
WHERE post_id = $postId
LIMIT 1";
$resLinkedForm = mysqli_query($GLOBALS['mysql_con'], $sqlLinkedForm);
if ($resLinkedForm && mysqli_num_rows($resLinkedForm) > 0) {
$rowLinkedForm = mysqli_fetch_assoc($resLinkedForm);
$linkedFormId = (int) $rowLinkedForm['form_id'];
}
?>
<div class="post-detail-container">
<!-- Tabbar -->
<div class="post-detail-header">
<!-- <a href="<?php echo htmlspecialchars($redirectURL); ?>&embedded=<?= $isEmbedded ?>" class="back-link">←</a> -->
<a href="<?php echo htmlspecialchars($redirectURL); ?>" class="back-link">←</a>
<div class="tab-bar">
<a href="#tab-article" class="active"><?= $translation->get("wiki_post") ?></a>
<a href="#tab-files"><?= $translation->get("files") ?> (<?php echo $fileCount; ?>)</a>
<a href="#tab-contacts"><?= $translation->get("Ansprechpartner") ?> (<?php echo $contactCount; ?>)</a>
<a href="#tab-versions"><?= $translation->get("version_history") ?> (<?php echo $versionCount; ?>)</a>
<?php if (is_wiki_redakteur()) { ?>
<a href="#tab-settings"><?= $translation->get("settings") ?></a>
<?php } ?>
</div>
</div>
<!-- Tab Content: Artikel (initial sichtbar) -->
<div id="tab-article" class="tab-content active">
<?php
require_once 'post_cardform_article.php';
if (!empty($linkedFormId)) {
// (Optional) read form title/description for display
$formRes = mysqli_query(
$GLOBALS['mysql_con'],
"SELECT description
FROM contactform_header
WHERE id = $linkedFormId
LIMIT 1"
);
$formRow = mysqli_fetch_assoc($formRes);
echo '<hr>';
echo '<div class="linked-contactform-wrapper">';
// Provide form id to the contactform module
$sitepart_id = $linkedFormId;
// Render the linked contact form
require MODULE_PATH . 'contactform/show_contactform.inc.php';
echo '</div>';
}
?>
<script>
document.addEventListener("DOMContentLoaded", function () {
const formBlock = document.querySelector('.linked-contactform-wrapper');
const previewBlock = document.querySelector('.post-preview-embed');
const contentBlock = document.querySelector('#displayContent');
if (formBlock) {
if (previewBlock && previewBlock.parentNode) {
previewBlock.parentNode.insertBefore(formBlock, previewBlock.nextSibling);
} else if (contentBlock && contentBlock.parentNode) {
contentBlock.parentNode.insertBefore(formBlock, contentBlock.nextSibling);
}
}
});
</script>
</div>
<!-- Tab Content: Diskussion -->
<div id="tab-files" class="tab-content">
<?php
require_once 'post_cardform_files.php';
?>
</div>
<div id="tab-contacts" class="tab-content">
<?php
require_once 'post_cardform_contacts.php';
?>
</div>
<!-- Tab Content: Versionsgeschichte -->
<div id="tab-versions" class="tab-content">
<?php
require_once 'post_cardform_version_history.php';
?>
</div>
<?php if (is_wiki_redakteur()) { ?>
<div id="tab-settings" class="tab-content">
<?php
require_once 'post_cardform_settings.php';
?>
</div>
<?php } ?>
</div>
<!-- Aside-Bereich zum Bearbeiten / Erstellen des Beitrags (initial versteckt) -->
<aside class="post-edit-aside" id="editArea" style="display:none;">
<?php
require_once 'post_cardform_edit_aside.php';
?>
</aside>
<script>
// Stelle sicher, dass jQuery geladen ist
$(document).ready(function () {
// Setze H1-Titel (#intranetTitle) anhand des Post-Titels mit jQuery; Fallback "Wiki"
(function setPostTitle() {
var $h1 = $('#intranetTitle');
if (!$h1.length) return;
var t = (typeof window.kcPostTitle !== 'undefined' && window.kcPostTitle) ? String(window.kcPostTitle) : '';
$h1.text(t && $.trim(t) !== '' ? t : 'Wiki');
})();
// InitialCategory aus PHP übernehmen
var initialCategoryId = <?= $initialCategoryId ?>;
var InitialProduct = <?= $InitialProduct ?>;
var isNewPost = <?= $isNew ? 'true' : 'false' ?>;
var draftInitRequestRunning = false;
var mode = ""; // "update" oder "create"
// Für bestehende Beiträge ist der Zielordner direkt bekannt.
window.kcPostGalleryPath = <?= json_encode((!$isNew && $postId > 0) ? ('knowledgecenter_update/posts/' . substr(sha1('kc_post_' . $postId), 0, 24)) : '') ?>;
function ensureExistingPostGalleryFolder() {
if (isNewPost) {
return;
}
var currentPostId = parseInt($("#editVersionForm input[name='post_id']").val(), 10) || 0;
if (currentPostId <= 0) {
return;
}
$.ajax({
type: "POST",
url: "/module/knowledgecenter_update/Ajax/Ajax.php",
dataType: "json",
data: {
action: "ensure_post_gallery_folder",
post_id: currentPostId
},
success: function (response) {
if (response && response.success && typeof response.gallery_start_path === "string" && response.gallery_start_path.length > 0) {
window.kcPostGalleryPath = response.gallery_start_path;
}
},
error: function (jqXHR, textStatus, errorThrown) {
console.error("ensure_post_gallery_folder AJAX Fehler", textStatus, errorThrown);
}
});
}
function initializeDraftPost() {
if (!isNewPost || draftInitRequestRunning) {
return;
}
var $postIdInput = $("#editVersionForm input[name='post_id']");
if (!$postIdInput.length) {
return;
}
var currentPostId = parseInt($postIdInput.val(), 10) || 0;
if (currentPostId > 0) {
return;
}
draftInitRequestRunning = true;
$.ajax({
type: "POST",
url: "/module/knowledgecenter_update/Ajax/Ajax.php",
dataType: "json",
data: {
action: "create_post_draft"
},
success: function (response) {
if (response && response.success && response.new_post_id > 0) {
$postIdInput.val(response.new_post_id);
if (typeof response.gallery_start_path === "string" && response.gallery_start_path.length > 0) {
window.kcPostGalleryPath = response.gallery_start_path;
}
} else {
console.error("create_post_draft fehlgeschlagen", response);
}
},
error: function (jqXHR, textStatus, errorThrown) {
console.error("create_post_draft AJAX Fehler", textStatus, errorThrown);
},
complete: function () {
draftInitRequestRunning = false;
}
});
}
<?php if ($isNew): ?>
// Setze den Modus auf "create" und passe ggf. den Titel des Editierbereichs an
mode = "create";
$("#editAreaTitle").text("Erstellen");
// Blende den Editierbereich ein und füge den Blur-Effekt hinzu
$("#editArea").appendTo("body").show(function () {
$(".post-detail-container").addClass("blurred");
});
initializeDraftPost();
<?php endif; ?>
ensureExistingPostGalleryFolder();
// Event-Handler für die Sprache-Tabs im Bearbeitungsbereich
$(".language-tabs div").on("click", function () {
$(".language-tabs div").removeClass("active");
$(this).addClass("active");
var langId = $(this).data("lang");
// Alle sprachspezifischen Bereiche ausblenden und nur den angeklickten anzeigen
$(".lang-form").hide();
$("#lang_" + langId).show();
// Sidebar-Meta ebenfalls umschalten
$(".lang-form-meta").hide();
$(".lang-form-meta[data-lang='" + langId + "']").show();
});
// Funktion zum Aktualisieren des URL-Query-Parameters "version"
function updateVersionParamInURL(versionId) {
var currentUrl = window.location.href;
var newUrl;
if (currentUrl.indexOf('version=') !== -1) {
newUrl = currentUrl.replace(/(version=)[^\&]+/, '$1' + versionId);
} else {
newUrl = currentUrl + "&version=" + versionId;
}
history.pushState(null, '', newUrl);
}
// Tab-Wechsel in der Hauptnavigation
$(".tab-bar a").on("click", function (e) {
e.preventDefault();
var target = $(this).attr("href"); // z.B. "#tab-files"
// Aktualisiere das visuelle Tab-Layout
$(".tab-bar a").removeClass("active");
$(this).addClass("active");
$(".tab-content").removeClass("active");
$(target).addClass("active");
// Entferne das führende '#' aus dem Ziel (z. B. "tab-files")
var tabParam = target.substring(1);
// Aktualisiere den URL-Parameter "tab-content" mithilfe der History-API
var url = new URL(window.location);
url.searchParams.set("tab-content", tabParam);
window.history.pushState(null, "", url.toString());
});
var urlParams = new URLSearchParams(window.location.search);
var tabContent = urlParams.get("tab-content");
// console.log("URL-Parameter tab-content:", tabContent);
var selector = ".tab-bar a[href='#" + tabContent + "']";
// console.log("Selektor für Tab:", selector);
if ($(selector).length > 0) {
$(selector).trigger("click");
} else {
console.warn("Kein Tab gefunden für den Selektor: " + selector);
}
// Speichere alle Versionen in einer JS-Variablen (nur für Anzeige und Editierung)
var versionsData = <?php echo json_encode($versions); ?>;
// Klick auf ein Listenelement in der Versionsliste: Anzeige aktualisieren und URL anpassen
$("#versionList div").on("click", function () {
$("#versionList div").removeClass("version_selected");
$(this).addClass("version_selected");
var selectedId = $(this).data("id");
var selectedVersion = versionsData.find(function (ver) {
return ver.id == selectedId;
});
if (selectedVersion) {
$("#displayTitle").text(selectedVersion.title);
if (selectedVersion.image) {
$("#displayImage").attr("src", "/userdata/" + selectedVersion.image).show();
} else {
$("#displayImage").hide();
}
$("#displayTeaser").html(selectedVersion.teaser ? nl2br(selectedVersion.teaser) : "");
$("#displayContent").html(selectedVersion.content);
// Aktualisiere die URL, sodass ?version= gesetzt wird
updateVersionParamInURL(selectedId);
}
// Setze den Artikel-Tab aktiv:
$(".tab-bar a[href='#tab-article']").trigger("click");
});
// Hilfsfunktion, um Zeilenumbrüche in <br> umzuwandeln
function nl2br(str) {
return str.replace(/\n/g, "<br>");
}
// Klick auf Edit-Button: Update-Modus
$("#editBtn").on("click", function () {
mode = "update";
$("#editAreaTitle").text("Version bearbeiten");
$("#keep_modified_at").prop("checked", false);
// Ermitteln der aktiven Sprache (z.B. Sprache mit ID)
var activeLang = $(".language-tabs div.active").data("lang");
// Finde die aktuell ausgewählte Version
var currentId = $("#versionList div.version_selected").data("id");
var currentVersion = versionsData.find(function (ver) {
return ver.id == currentId;
});
if (currentVersion) {
// Setze den Hidden-Wert für die Version-ID im aktiven Sprachbereich
$("#lang_" + activeLang + " input[name='version_id[" + activeLang + "]']").val(currentVersion.id);
// Befülle die textbasierten Felder
$("#title_" + activeLang).val(currentVersion.title);
$("#image_" + activeLang).val(currentVersion.image);
$(".preview").attr("src", "/userdata/" + currentVersion.image);
$("#teaser_" + activeLang).val(currentVersion.teaser);
// Falls du einen Quill-Editor verwendest, setze den Inhalt über seine API:
if (quillEditors[activeLang]) {
// dangerouslyPasteHTML statt innerHTML=, damit Quill 2.x
// alte Listen-Inhalte beim Re-Normalisieren nicht droppt.
quillEditors[activeLang].setContents([], 'silent');
quillEditors[activeLang].clipboard.dangerouslyPasteHTML(0, currentVersion.content || '', 'silent');
}
}
$("#editArea").appendTo("body").show(function () {
$(".post-detail-container").addClass("blurred");
});
});
// Klick auf Create-Button: Create-Modus
$("#createBtn").on("click", function () {
mode = "create";
$("#editAreaTitle").text("Erstellen");
$("#keep_modified_at").prop("checked", false);
// Ermitteln der aktiven Sprache (z.B. Sprache mit ID)
var activeLang = $(".language-tabs div.active").data("lang");
// Aktuell angezeigte Version in der Versionsliste abrufen
var currentId = $("#versionList div.version_selected").data("id");
var currentVersion = versionsData.find(function (ver) {
return ver.id == currentId;
});
// Wenn es eine aktuell angezeigte Version gibt, diese vorbefüllen, ansonsten leere Felder setzen
if (currentVersion) {
// Im Create-Modus setzen wir die version_id auf leer, da eine neue Version erstellt wird.
$("#lang_" + activeLang + " input[name='version_id[" + activeLang + "]']").val("");
$("#title_" + activeLang).val(currentVersion.title);
$("#image_" + activeLang).val(currentVersion.image);
$(".preview").attr("src", "/userdata/" + currentVersion.image);
$("#teaser_" + activeLang).val(currentVersion.teaser);
if (quillEditors[activeLang]) {
// dangerouslyPasteHTML statt innerHTML=, damit Quill 2.x
// alte Listen-Inhalte beim Re-Normalisieren nicht droppt.
quillEditors[activeLang].setContents([], 'silent');
quillEditors[activeLang].clipboard.dangerouslyPasteHTML(0, currentVersion.content || '', 'silent');
}
} else {
$("#lang_" + activeLang + " input[name='version_id[" + activeLang + "]']").val("");
$("#title_" + activeLang).val("");
$("#image_" + activeLang).val("");
$("#teaser_" + activeLang).val("");
if (quillEditors[activeLang]) {
quillEditors[activeLang].setContents([], 'silent');
}
}
// Editierbereich anzeigen und ggf. den Blur-Effekt hinzufügen
$("#editArea").appendTo("body").show(function () {
$(".post-detail-container").addClass("blurred");
});
});
// Klick auf Abbrechen (Cancel) im Bearbeitungsbereich: Edit-Bereich schließen und Events reaktivieren
$("#cancelEditBtn").on("click", function () {
$("#editArea").hide(function () {
// Entferne den Blur-Effekt, wenn das Aside geschlossen wurde
$(".post-detail-container").removeClass("blurred");
});
});
$("#versionList div").on("click", function () {
$("#versionList div").removeClass("version_selected");
$(this).addClass("version_selected");
var selectedId = $(this).data("id");
var selectedVersion = versionsData.find(function (ver) {
return ver.id == selectedId;
});
if (!selectedVersion) return;
// 1) Title, Image, Teaser, Content wie gehabt
$("#displayTitle").text(selectedVersion.title);
if (selectedVersion.image) {
$("#displayImage").attr("src", "/userdata/" + selectedVersion.image).show();
} else {
$("#displayImage").hide();
}
$("#displayTeaser").html(selectedVersion.teaser ? nl2br(selectedVersion.teaser) : "");
$("#displayContent").html(selectedVersion.content);
// 2) Meta (Datum + Autor) aktualisieren
// a) in JS formatieren wir auf Deutsch:
var dt = new Date(selectedVersion.modified_at);
var formattedDate = dt.toLocaleString('de-DE', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
// b) in ein und denselben Container schreiben:
$(".post-meta-item").text(formattedDate + " - " + selectedVersion.author);
});
// 1) BetterTablePlugin vor allen Instanzen registrieren
Quill.register({
'modules/better-table': quillBetterTable
}, true);
var quillEditors = {};
// 2) imageHandler: Datei-Upload statt Base64-Embed.
// Lädt das Bild via AJAX in den Post-Ordner und fügt nur die URL als <img src> ein.
function imageHandler() {
var quill = this.quill;
var range = quill.getSelection(true);
var fileInput = this.container.querySelector('input.ql-image[type=file]');
if (!fileInput) {
fileInput = document.createElement('input');
fileInput.setAttribute('type', 'file');
fileInput.setAttribute('accept', 'image/jpeg,image/png,image/gif,image/webp');
fileInput.style.display = 'none';
fileInput.addEventListener('change', function () {
var file = fileInput.files[0];
fileInput.value = '';
if (!file) return;
uploadInlineImage(quill, range, file);
});
this.container.appendChild(fileInput);
}
fileInput.click();
}
function getCurrentPostIdForUpload() {
var id = parseInt($("#editVersionForm input[name='post_id']").val(), 10) || 0;
return id;
}
// Stellt sicher, dass für einen neuen Entwurf eine Post-ID + Ordner existiert,
// bevor wir hochladen. Resolved mit einer post_id > 0 oder rejected.
function ensurePostIdReady() {
return new Promise(function (resolve, reject) {
var current = getCurrentPostIdForUpload();
if (current > 0) { resolve(current); return; }
if (!isNewPost) { reject(new Error('Keine Post-ID verfügbar.')); return; }
$.ajax({
type: "POST",
url: "/module/knowledgecenter_update/Ajax/Ajax.php",
dataType: "json",
data: { action: "create_post_draft" }
}).done(function (response) {
if (response && response.success && response.new_post_id > 0) {
$("#editVersionForm input[name='post_id']").val(response.new_post_id);
if (typeof response.gallery_start_path === "string" && response.gallery_start_path.length > 0) {
window.kcPostGalleryPath = response.gallery_start_path;
}
resolve(response.new_post_id);
} else {
reject(new Error((response && response.error) || 'Entwurf konnte nicht angelegt werden.'));
}
}).fail(function () {
reject(new Error('Netzwerkfehler beim Anlegen des Entwurfs.'));
});
});
}
function uploadInlineImage(quill, range, file) {
ensurePostIdReady().then(function (postId) {
var fd = new FormData();
fd.append('post_id', postId);
fd.append('file', file);
var insertIndex = (range && typeof range.index === 'number') ? range.index : quill.getLength();
quill.insertText(insertIndex, '⏳', 'silent');
$.ajax({
type: "POST",
url: "/module/knowledgecenter_update/Ajax/endpoints/upload_inline_image.php",
data: fd,
dataType: "json",
processData: false,
contentType: false
}).done(function (response) {
quill.deleteText(insertIndex, 1, 'silent');
if (response && response.success && response.url) {
quill.insertEmbed(insertIndex, 'image', response.url, 'user');
quill.setSelection(insertIndex + 1, 0, 'silent');
} else {
alert("Bild-Upload fehlgeschlagen: " + ((response && response.error) || 'unbekannter Fehler'));
}
}).fail(function (jqXHR) {
quill.deleteText(insertIndex, 1, 'silent');
var msg = 'Upload fehlgeschlagen.';
try {
var j = JSON.parse(jqXHR.responseText);
if (j && j.error) msg = j.error;
} catch (e) {}
alert("Bild-Upload fehlgeschlagen: " + msg);
});
}).catch(function (err) {
alert("Bild-Upload fehlgeschlagen: " + err.message);
});
}
// 3) Pro SprachForm genau eine QuillInstanz
$('.lang-form').each(function () {
var $form = $(this);
var langId = $form.data('lang');
var $editor = $form.find('.editor');
var editorId = $editor.attr('id');
var quill = new Quill('#' + editorId, {
theme: 'snow',
modules: {
// a) Toolbar = Goldstandard-Preset aus shared_components/quill/toolbar_presets.js
toolbar: {
container: window.QuillToolbarPresets.full(),
handlers: {
image: imageHandler,
table: function () {
// 'this' ist der ToolbarHandler, 'this.quill' Deine Instanz
const tableModule = this.quill.getModule('better-table');
tableModule.insertTable(3, 3); // hier beliebige Größe
}
}
},
// b) BetterTable
table: false,
'better-table': window.QuillToolbarPresets.betterTableConfig(),
// c) Wichtige KeyboardBindings für BetterTable
keyboard: {
bindings: quillBetterTable.keyboardBindings
},
// d) Drag-Resize für Bilder (shared module aus /module/shared_components/quill/)
imageResize: true
}
});
quillEditors[langId] = quill;
});
// AJAX-Funktion zum Speichern der Versionen
window.saveVersionAjax = function () {
// HTML-Inhalt aus jedem Editor in das zugehörige hidden-Feld übernehmen
for (var lang in quillEditors) {
if (quillEditors.hasOwnProperty(lang)) {
var htmlContent = quillEditors[lang].root.innerHTML;
$("#hiddenContent_" + lang).val(htmlContent);
}
}
// Formularserialisierung
var formData = $("#editVersionForm").serialize();
var postBackgroundColor = $("#postBackgroundColor").val();
var postTextColor = $("#postTextColor").val();
if (postBackgroundColor) {
formData += "&background_color=" + encodeURIComponent(postBackgroundColor);
}
if (postTextColor) {
formData += "&text_color=" + encodeURIComponent(postTextColor);
}
// InitialCategory zum POST-Daten hinzufügen
if (typeof initialCategoryId !== 'undefined' && initialCategoryId > 0) {
formData += "&initial_category_id=" + initialCategoryId;
}
if (typeof InitialProduct !== 'undefined' && InitialProduct > 0) {
formData += "&InitialProduct=" + InitialProduct;
}
// Modusabhängige Aktion anhängen (update oder create)
if (mode === "update") {
formData += "&action=update_post_version";
} else if (mode === "create") {
formData += "&action=insert_post_version";
}
// AJAX-Request über jQuery
$.ajax({
type: "POST",
url: "/module/knowledgecenter_update/Ajax/Ajax.php",
data: formData,
dataType: "json",
success: function (response) {
if (response.success) {
// Wenn ein neuer Beitrag erstellt wurde, leite weiter, ansonsten Seite neu laden
if (typeof response.new_post_id !== "undefined" && response.new_post_id > 0) {
// Übergibt den Redirect-Parameter aus PHP in JS
var redirectURL = "<?php echo htmlspecialchars($redirectURL, ENT_QUOTES, 'UTF-8'); ?>";
// Falls redirectURL nicht leer sein sollte, anhängen
var newUrl = "?action=Post&id=" + encodeURIComponent(response.new_post_id)
+ "&embedded=<?= rawurlencode((string) $isEmbedded) ?>";
if (typeof initialCategoryId !== 'undefined' && initialCategoryId > 0) {
newUrl += "&initial_category_id=" + encodeURIComponent(initialCategoryId);
}
if (typeof InitialProduct !== 'undefined' && InitialProduct > 0) {
newUrl += "&InitialProduct=" + encodeURIComponent(InitialProduct);
}
window.location.href = newUrl;
} else {
location.reload();
}
} else {
alert("Fehler: " + response.error);
}
},
error: function (jqXHR, textStatus, errorThrown) {
console.error("AJAX Fehler: ", textStatus, errorThrown);
}
});
}
// Ersetze den bisherigen Click-Handler vom Button "saveVersionBtn" durch den direkten Aufruf der AJAX-Funktion
$("#saveVersionBtn").on("click", function (e) {
e.preventDefault();
saveVersionAjax();
});
// In Deinem $(document).ready()-Block:
$(".deleteVersionBtn").on("click", function () {
var versionId = $(this).data("id"); // eventuell nicht mehr benötigt
var versionNumber = $(this).data("version-number");
// Stelle sicher, dass Du auch die Post-ID übergeben kannst entweder aus einem versteckten Feld oder als globale Variable:
var postId = <?php echo json_encode($postId); ?>;
if (confirm("Wollen Sie diese Version wirklich löschen?")) {
$.post("/module/knowledgecenter_update/Ajax/Ajax.php", {
action: "delete_post_version",
post_id: postId,
embedded: <?= $isEmbedded ?>,
version_number: versionNumber
}, function (response) {
if (response.success) {
if (response.redirect) {
window.location.href = response.redirect;
} else {
location.reload();
}
} else {
alert("Fehler beim Löschen: " + response.error);
}
}, "json");
}
});
});
</script>