feat(wiki): Beitragsliste für Betriebsrat-Nutzer auf zugängliche Kategorien filtern

BR-Nutzer (r-wiki-br ohne r-wiki) sehen in der Beitragsliste und im
Kategorie-Dropdown nur Beiträge und Kategorien, die ihren freigegebenen
Kategorien (betriebsrat_editable=1 + alle Nachfahren per BFS) zugeordnet sind.

Der Filter wird server-seitig in post_listform.php berechnet und als Parameter
an den Ajax-Endpunkt übergeben, da get_permission_state() im Ajax-Kontext
nicht verfügbar ist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 10:07:48 +02:00
parent 5da7916651
commit 60eea92f11
4 changed files with 152 additions and 27 deletions

View File

@@ -62,6 +62,39 @@ function ajax_is_category_editable_for_br(int $categoryId): bool
return false;
}
// Alle Kategorie-IDs, die für Betriebsrat zugänglich sind (direkt betriebsrat_editable=1
// plus alle Nachfahren im Baum BFS nach unten).
function ajax_get_all_betriebsrat_accessible_category_ids(): array
{
$seeds = [];
$res = mysqli_query($GLOBALS['mysql_con'],
"SELECT id FROM knowledgecenter_categories_update WHERE betriebsrat_editable = 1");
while ($row = mysqli_fetch_assoc($res)) {
$seeds[] = (int)$row['id'];
}
if (empty($seeds)) {
return [];
}
$visited = [];
$queue = $seeds;
while (!empty($queue)) {
$current = array_shift($queue);
if (isset($visited[$current])) {
continue;
}
$visited[$current] = true;
$cRes = mysqli_query($GLOBALS['mysql_con'],
"SELECT child_category_id FROM knowledgecenter_category_links WHERE parent_category_id = $current");
while ($cRow = mysqli_fetch_assoc($cRes)) {
$cid = (int)$cRow['child_category_id'];
if (!isset($visited[$cid])) {
$queue[] = $cid;
}
}
}
return array_keys($visited);
}
// 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
@@ -1419,7 +1452,7 @@ function get_post_list()
// nur Posts, die in dieser Kategorie sind
$categoryCondition = "
AND EXISTS (
SELECT 1
SELECT 1
FROM knowledgecenter_category_post AS cp
WHERE cp.post_id = kp.id
AND cp.category_id = $categoryId
@@ -1428,20 +1461,39 @@ function get_post_list()
$categoryCondition = "";
}
// Betriebsrat darf nur Posts sehen, die in einer freigegebenen Kategorie liegen.
// Die erlaubten Kategorie-IDs werden von post_listform.php berechnet und übergeben,
// da get_permission_state() im Ajax-Kontext nicht verfügbar ist.
$brCategoryCondition = "";
$rawBrIds = isset($_POST['br_category_ids']) && $_POST['br_category_ids'] !== 'null'
? json_decode($_POST['br_category_ids'], true)
: null;
if (is_array($rawBrIds)) {
if (count($rawBrIds) === 0) {
$brCategoryCondition = "AND 1=0";
} else {
$brCatIdList = implode(',', array_map('intval', $rawBrIds));
$brCategoryCondition = "AND EXISTS (
SELECT 1 FROM knowledgecenter_category_post AS cp_br
WHERE cp_br.post_id = kp.id AND cp_br.category_id IN ($brCatIdList)
)";
}
}
// Beiträge abfragen (nur PostID und Titel der aktuellen Version)
$sqlPosts = "
SELECT
SELECT
kp.id AS post_id,
pv.title AS current_title
FROM knowledgecenter_posts AS kp
LEFT JOIN knowledgecenter_post_versions AS pv
ON kp.id = pv.post_id
LEFT JOIN knowledgecenter_post_versions AS pv
ON kp.id = pv.post_id
AND pv.language_id = $languageId
/* … Bedingungen für gültige Version und höchste version_number … */
WHERE 1=1
$searchCondition
$categoryCondition
$brCategoryCondition
GROUP BY kp.id
ORDER BY kp.modified_at DESC
";

View File

@@ -867,6 +867,39 @@ function require_wiki_edit_permission(): void
}
}
// Gibt alle Kategorie-IDs zurück, auf die Betriebsrat Zugriff hat:
// direkt betriebsrat_editable=1 sowie alle ihre Nachfahren im Baum (BFS nach unten).
function get_all_betriebsrat_accessible_category_ids(): array
{
$seeds = [];
$res = mysqli_query($GLOBALS['mysql_con'],
"SELECT id FROM knowledgecenter_categories_update WHERE betriebsrat_editable = 1");
while ($row = mysqli_fetch_assoc($res)) {
$seeds[] = (int)$row['id'];
}
if (empty($seeds)) {
return [];
}
$visited = [];
$queue = $seeds;
while (!empty($queue)) {
$current = array_shift($queue);
if (isset($visited[$current])) {
continue;
}
$visited[$current] = true;
$cRes = mysqli_query($GLOBALS['mysql_con'],
"SELECT child_category_id FROM knowledgecenter_category_links WHERE parent_category_id = $current");
while ($cRow = mysqli_fetch_assoc($cRes)) {
$cid = (int)$cRow['child_category_id'];
if (!isset($visited[$cid])) {
$queue[] = $cid;
}
}
}
return array_keys($visited);
}
// 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

View File

@@ -186,29 +186,44 @@ $_kcBrOnly = !is_wiki_redakteur() && is_wiki_betriebsrat();
* gefiltert, sodass in den Bereichen (Abteilung, Mandant, Rolle) entweder kein Eintrag vorliegt
* oder der Eintrag mit den erlaubten Werten übereinstimmt.
*/
// Für Betriebsrat: nur Beiträge zeigen, die mindestens einer freigegebenen Kategorie zugeordnet sind
$brCategoryFilter = '';
if ($_kcBrOnly) {
$brCatIds = get_all_betriebsrat_accessible_category_ids();
if (empty($brCatIds)) {
$brCategoryFilter = 'AND 1=0'; // kein Zugriff
} else {
$brCatIdList = implode(',', $brCatIds);
$brCategoryFilter = "AND EXISTS (
SELECT 1 FROM knowledgecenter_category_post kcp_br
WHERE kcp_br.post_id = kp.id AND kcp_br.category_id IN ($brCatIdList)
)";
}
}
$sqlPosts = "
SELECT
kp.id,
kp.modified_at,
pv.title,
pv.image,
SELECT
kp.id,
kp.modified_at,
pv.title,
pv.image,
pv.link,
pv.link_label,
pv.version_number
FROM
FROM
knowledgecenter_category_post AS kcp
JOIN
JOIN
knowledgecenter_posts AS kp ON kcp.post_id = kp.id
LEFT JOIN
knowledgecenter_post_versions AS pv ON kp.id = pv.post_id
AND pv.language_id = $languageId
LEFT JOIN
knowledgecenter_post_versions AS pv ON kp.id = pv.post_id
AND pv.language_id = $languageId
AND pv.state = 1
AND (pv.valid_from IS NULL OR pv.valid_from <= NOW())
AND (pv.valid_until IS NULL OR pv.valid_until >= NOW())
AND pv.version_number = (
SELECT MAX(v2.version_number)
SELECT MAX(v2.version_number)
FROM knowledgecenter_post_versions AS v2
WHERE v2.post_id = kp.id
WHERE v2.post_id = kp.id
AND v2.language_id = $languageId
AND v2.state = 1
AND (v2.valid_from IS NULL OR v2.valid_from <= NOW())
@@ -232,9 +247,10 @@ $_kcBrOnly = !is_wiki_redakteur() && is_wiki_betriebsrat();
AND ( pr.role_id IS NULL OR pr.role_id IN ($allowedRoles) )
AND ( pe.einricht_id IS NULL OR pe.einricht_id IN ($allowedEinrichts) )
AND ( pfb.fachbereich_id IS NULL OR pfb.fachbereich_id IN ($allowedFachbereiche) )
GROUP BY
$brCategoryFilter
GROUP BY
kp.id
ORDER BY
ORDER BY
$orderBy $orderDir
";

View File

@@ -3,14 +3,32 @@
// Redirect-URL für den Zurück-Button bzw. Navigation
$redirectURL = isset($_GET['redirectURL']) ? $_GET['redirectURL'] : '?action=Category';
// Kategorien für das Dropdown holen (state = 1)
$catsQuery = "
SELECT c.id, t.title
FROM knowledgecenter_categories_update AS c
LEFT JOIN knowledgecenter_category_translations AS t
ON t.category_id = c.id AND t.language_id = $languageId
ORDER BY t.title ASC
";
// Kategorien für das Dropdown holen
$_isBrOnly = !is_wiki_redakteur() && is_wiki_betriebsrat();
if ($_isBrOnly) {
$_brCatIds = get_all_betriebsrat_accessible_category_ids();
if (empty($_brCatIds)) {
$catsQuery = "SELECT NULL LIMIT 0";
} else {
$_brCatIdList = implode(',', $_brCatIds);
$catsQuery = "
SELECT c.id, t.title
FROM knowledgecenter_categories_update AS c
LEFT JOIN knowledgecenter_category_translations AS t
ON t.category_id = c.id AND t.language_id = $languageId
WHERE c.id IN ($_brCatIdList)
ORDER BY t.title ASC
";
}
} else {
$catsQuery = "
SELECT c.id, t.title
FROM knowledgecenter_categories_update AS c
LEFT JOIN knowledgecenter_category_translations AS t
ON t.category_id = c.id AND t.language_id = $languageId
ORDER BY t.title ASC
";
}
$catsResult = mysqli_query($GLOBALS['mysql_con'], $catsQuery);
$allCategories = [];
while ($r = mysqli_fetch_assoc($catsResult)) {
@@ -44,9 +62,14 @@ while ($r = mysqli_fetch_assoc($catsResult)) {
</div>
<?php
$_brCategoryIds = $_isBrOnly ? ($_brCatIds ?? []) : null;
$_brCategoryIdsJson = json_encode($_brCategoryIds);
?>
<script>
var myRedirectURL = window.location.href;
var debounceTimer;
var brCategoryIds = <?= $_brCategoryIdsJson ?>;
function loadPostList() {
var searchQuery = $('#searchQuery').val();
@@ -61,7 +84,8 @@ while ($r = mysqli_fetch_assoc($catsResult)) {
language_id: '<?= $languageId ?>',
redirectURL: myRedirectURL,
query: searchQuery,
category_id: categoryId // hier mitgeben
category_id: categoryId,
br_category_ids: brCategoryIds !== null ? JSON.stringify(brCategoryIds) : null
},
dataType: 'json',
beforeSend: function () { $('#overlayLoader').show(); },