feat(wiki): Kategorienvererbung für betriebsrat_editable, Ajax-Absicherung

- is_category_betriebsrat_editable(): läuft den Kategoriebaum nach oben durch
  (via knowledgecenter_category_links), sodass Unterkategorien einer
  freigegebenen Kategorie automatisch ebenfalls als editierbar gelten
- ajax_is_category_editable_for_br(): gleiche Logik für den Ajax-Kontext
- ajax_can_edit_post(): nutzt jetzt Vererbung statt direkter Kategorienprüfung
- create_post_draft(): ajax_has_wiki_edit()-Check hinzugefügt
- update_post_version(): Permission-Check mit Sonderfall neue Drafts (noch
  keine Kategorien → Prüfung auf has_wiki_edit begrenzt)
- update_post_categories(): Betriebsrat darf nur freigegebene Kategorien
  (inkl. Vererbung) zuweisen
- upload_inline_image.php: Bild-Upload prüft ebenfalls den Kategoriebaum

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 08:08:59 +02:00
parent a0698d621e
commit 4d6cc807c9
3 changed files with 140 additions and 17 deletions

View File

@@ -33,26 +33,56 @@ function ajax_has_wiki_edit(): bool
|| get_permission_state('r-wiki-br', $mid, $uid) == 1;
}
// Betriebsrat darf einen Post nur bearbeiten, wenn er in einer freigegebenen Kategorie liegt.
// Gibt false zurück wenn Bearbeitung verboten, true wenn erlaubt.
// Prüft ob eine Kategorie (oder ein Vorfahre) betriebsrat_editable=1 hat.
function ajax_is_category_editable_for_br(int $categoryId): bool
{
$visited = [];
$queue = [$categoryId];
while (!empty($queue)) {
$current = array_shift($queue);
if (isset($visited[$current])) {
continue;
}
$visited[$current] = true;
$res = mysqli_query($GLOBALS['mysql_con'],
"SELECT betriebsrat_editable FROM knowledgecenter_categories_update WHERE id = $current LIMIT 1");
$row = mysqli_fetch_assoc($res);
if ($row && (int)$row['betriebsrat_editable'] === 1) {
return true;
}
$pRes = mysqli_query($GLOBALS['mysql_con'],
"SELECT parent_category_id FROM knowledgecenter_category_links WHERE child_category_id = $current");
while ($pRow = mysqli_fetch_assoc($pRes)) {
$pid = (int)$pRow['parent_category_id'];
if (!isset($visited[$pid])) {
$queue[] = $pid;
}
}
}
return false;
}
// Betriebsrat darf einen Post nur bearbeiten, wenn er in einer freigegebenen
// Kategorie (oder Unterkategorie einer freigegebenen Kategorie) liegt.
function ajax_can_edit_post(int $postId): bool
{
$mid = $GLOBALS['main_contact']['master_mandant_id'] ?? 0;
$uid = $GLOBALS['main_contact']['id'] ?? 0;
if (get_permission_state('r-wiki', $mid, $uid) == 1) {
return true; // Redakteur darf immer
return true;
}
if (get_permission_state('r-wiki-br', $mid, $uid) != 1) {
return false; // Kein Wiki-Recht
return false;
}
// Betriebsrat: Post muss in betriebsrat_editable Kategorie liegen
// Betriebsrat: prüfe alle dem Post zugeordneten Kategorien inkl. Vorfahren
$res = 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");
$row = mysqli_fetch_assoc($res);
return $row && (int)$row['cnt'] > 0;
"SELECT category_id FROM knowledgecenter_category_post WHERE post_id = $postId");
while ($row = mysqli_fetch_assoc($res)) {
if (ajax_is_category_editable_for_br((int)$row['category_id'])) {
return true;
}
}
return false;
}
// AJAX-Handler: Falls per POST eine Aktion übergeben wurde, diese verarbeiten und das Skript beenden.
@@ -719,6 +749,22 @@ function update_post_version()
echo json_encode(['error' => 'Ungültige Beitrags-ID']);
exit;
}
if (!ajax_has_wiki_edit()) {
echo json_encode(['error' => 'Keine Berechtigung']);
exit;
}
// Betriebsrat darf bestehende Posts nur bearbeiten wenn der Post in einer freigegebenen Kategorie liegt.
// Neue Drafts haben noch keine Kategorien → Prüfung überspringen (Zugang wurde schon bei create_post_draft gesichert).
if ($postId > 0) {
$catCountRes = mysqli_query($GLOBALS['mysql_con'],
"SELECT COUNT(*) AS cnt FROM knowledgecenter_category_post WHERE post_id = $postId");
$catCountRow = mysqli_fetch_assoc($catCountRes);
if ((int)$catCountRow['cnt'] > 0 && !ajax_can_edit_post($postId)) {
echo json_encode(['error' => 'Keine Berechtigung für diesen Beitrag']);
exit;
}
}
$keepModifiedAt = isset($_POST['keep_modified_at']) && (int) $_POST['keep_modified_at'] === 1;
$backgroundColor = normalize_hex_color($_POST['background_color'] ?? '#FFFFFF', '#FFFFFF');
@@ -1062,6 +1108,10 @@ function delete_post_gallery_dir($postId)
function create_post_draft()
{
if (!ajax_has_wiki_edit()) {
echo json_encode(['error' => 'Keine Berechtigung']);
exit;
}
$insertPostQuery = "INSERT INTO knowledgecenter_posts (created_at) VALUES (NOW())";
if (!mysqli_query($GLOBALS['mysql_con'], $insertPostQuery)) {
echo json_encode(['error' => 'Fehler beim Erstellen des Entwurfs: ' . mysqli_error($GLOBALS['mysql_con'])]);
@@ -1276,6 +1326,27 @@ function update_post_categories()
exit;
}
$mid = $GLOBALS['main_contact']['master_mandant_id'] ?? 0;
$uid = $GLOBALS['main_contact']['id'] ?? 0;
$isRedakteur = get_permission_state('r-wiki', $mid, $uid) == 1;
$isBetriebsrat = get_permission_state('r-wiki-br', $mid, $uid) == 1;
if (!$isRedakteur && !$isBetriebsrat) {
echo json_encode(['error' => 'Keine Berechtigung']);
exit;
}
// Betriebsrat darf nur Kategorien zuweisen, die für ihn freigegeben sind (inkl. Vererbung)
if (!$isRedakteur && $isBetriebsrat) {
foreach ($categories as $catId) {
$catId = (int)$catId;
if ($catId > 0 && !ajax_is_category_editable_for_br($catId)) {
echo json_encode(['error' => "Kategorie $catId ist nicht für den Betriebsrat freigegeben"]);
exit;
}
}
}
// Bestehende Einträge für diesen Beitrag löschen
$deleteQuery = "DELETE FROM knowledgecenter_category_post WHERE post_id = $postId";
if (!mysqli_query($GLOBALS['mysql_con'], $deleteQuery)) {

View File

@@ -74,14 +74,36 @@ if (!$verifyQ || mysqli_num_rows($verifyQ) === 0) {
respond_error('Beitrag existiert nicht.', 404);
}
// Betriebsrat darf Bilder nur in freigegebene Kategorien hochladen
// Betriebsrat darf Bilder nur für Posts in freigegebenen Kategorien hochladen (inkl. Vererbung)
if (!$hasRwiki && $hasRwikiBr) {
$brAllowed = false;
$brRes = 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");
$brRow = mysqli_fetch_assoc($brRes);
if (!$brRow || (int)$brRow['cnt'] === 0) {
"SELECT category_id FROM knowledgecenter_category_post WHERE post_id = $postId");
while ($brRow = mysqli_fetch_assoc($brRes)) {
$catId = (int)$brRow['category_id'];
// Baum nach oben absuchen
$visited = [];
$queue = [$catId];
while (!empty($queue)) {
$current = array_shift($queue);
if (isset($visited[$current])) continue;
$visited[$current] = true;
$chkRes = mysqli_query($GLOBALS['mysql_con'],
"SELECT betriebsrat_editable FROM knowledgecenter_categories_update WHERE id = $current LIMIT 1");
$chkRow = mysqli_fetch_assoc($chkRes);
if ($chkRow && (int)$chkRow['betriebsrat_editable'] === 1) {
$brAllowed = true;
break 2;
}
$pRes = mysqli_query($GLOBALS['mysql_con'],
"SELECT parent_category_id FROM knowledgecenter_category_links WHERE child_category_id = $current");
while ($pRow = mysqli_fetch_assoc($pRes)) {
$pid = (int)$pRow['parent_category_id'];
if (!isset($visited[$pid])) $queue[] = $pid;
}
}
}
if (!$brAllowed) {
respond_error('Keine Berechtigung für diesen Beitrag.', 403);
}
}

View File

@@ -867,6 +867,36 @@ function require_wiki_edit_permission(): void
}
}
// Prüft ob eine Kategorie (oder irgendein Vorfahre über knowledgecenter_category_links)
// betriebsrat_editable=1 hat. Läuft den Baum nach oben durch.
function is_category_betriebsrat_editable(int $categoryId): bool
{
$visited = [];
$queue = [$categoryId];
while (!empty($queue)) {
$current = array_shift($queue);
if (isset($visited[$current])) {
continue;
}
$visited[$current] = true;
$res = mysqli_query($GLOBALS['mysql_con'],
"SELECT betriebsrat_editable FROM knowledgecenter_categories_update WHERE id = $current LIMIT 1");
$row = mysqli_fetch_assoc($res);
if ($row && (int)$row['betriebsrat_editable'] === 1) {
return true;
}
$pRes = mysqli_query($GLOBALS['mysql_con'],
"SELECT parent_category_id FROM knowledgecenter_category_links WHERE child_category_id = $current");
while ($pRow = mysqli_fetch_assoc($pRes)) {
$pid = (int)$pRow['parent_category_id'];
if (!isset($visited[$pid])) {
$queue[] = $pid;
}
}
}
return false;
}
// Switch-Case für die verschiedenen Aktionen
$action = $_GET["action"] ?? "List";
switch ($action) {