diff --git a/Services/News/Service/class.InternalDomainService.php b/Services/News/Service/class.InternalDomainService.php index d4055b267754..e9e537f86e12 100644 --- a/Services/News/Service/class.InternalDomainService.php +++ b/Services/News/Service/class.InternalDomainService.php @@ -59,7 +59,7 @@ public function collection(): NewsCollectionService $this->repo_service->cache(), $this->resolver(), $this->objectDataCache(), - $this->access() + $this->DIC->rbac()->system() ); } diff --git a/Services/News/Timeline/class.TimelineManager.php b/Services/News/Timeline/class.TimelineManager.php index 085b35d45107..c23dedf659f6 100644 --- a/Services/News/Timeline/class.TimelineManager.php +++ b/Services/News/Timeline/class.TimelineManager.php @@ -60,7 +60,8 @@ public function getNewsData( if ($ref_id > 0) { return $this->domain->collection()->getNewsForContext( new NewsContext($ref_id), - $criteria + $criteria, + $this->domain->user()->getId() ); } else { return $this->domain->collection()->getNewsForUser( diff --git a/Services/News/classes/class.ilNewsForContextBlockGUI.php b/Services/News/classes/class.ilNewsForContextBlockGUI.php index 82e642eac45c..e5055a577c03 100755 --- a/Services/News/classes/class.ilNewsForContextBlockGUI.php +++ b/Services/News/classes/class.ilNewsForContextBlockGUI.php @@ -105,6 +105,7 @@ private function loadNewsData(): void $collection = $this->domain->collection()->getNewsForContext( new NewsContext($this->std_request->getRefId()), new NewsCriteria(read_user_id: $this->user->getId()), + $this->user->getId(), true ); diff --git a/Services/News/src/Aggregation/NewsAggregator.php b/Services/News/src/Aggregation/NewsAggregator.php index be589e766821..ecdca1f368ad 100644 --- a/Services/News/src/Aggregation/NewsAggregator.php +++ b/Services/News/src/Aggregation/NewsAggregator.php @@ -60,10 +60,10 @@ public function aggregate(array $contexts): array $current = $frontier->dequeue(); // Ensure each context is only visited once - if (array_key_exists($current->getRefId(), $result)) { + if (array_key_exists($current->getObjId(), $result)) { continue; } - $result[$current->getRefId()] = $current; + $result[$current->getObjId()] = $current; // Skip if no processing necessary $strategy = $this->getStrategy($current->getObjType()); @@ -75,7 +75,7 @@ public function aggregate(array $contexts): array foreach ($children as $child) { if ($strategy->isRecursive()) { // Recursive items will be added directly - $result[$child->getRefId()] = $child; + $result[$child->getObjId()] = $child; } else { // Iterative items will be queued for further processing $frontier->enqueue($child); diff --git a/Services/News/src/Domain/NewsCollectionService.php b/Services/News/src/Domain/NewsCollectionService.php index e6640eebd5e8..e34d7b6d80e9 100644 --- a/Services/News/src/Domain/NewsCollectionService.php +++ b/Services/News/src/Domain/NewsCollectionService.php @@ -21,7 +21,6 @@ namespace ILIAS\News\Domain; use ILIAS\News\Aggregation\NewsAggregator; -use ILIAS\News\Data\LazyNewsCollection; use ILIAS\News\Data\NewsCollection; use ILIAS\News\Data\NewsContext; use ILIAS\News\Data\NewsCriteria; @@ -39,7 +38,7 @@ public function __construct( private readonly NewsCache $cache, private readonly UserContextResolver $user_context_resolver, private readonly \ilObjectDataCache $object_data, - private readonly \ilAccessHandler $access, + private readonly \ilRbacSystem $rbac ) { } @@ -74,7 +73,7 @@ public function getNewsForUser(\ilObjUser $user, NewsCriteria $criteria, bool $l } // 4. Query news for resolved contexts [DPL 2-4] - $news_collection = $this->getNewsForContexts($user_contexts, $criteria, $lazy); + $news_collection = $this->getNewsForContexts($user_contexts, $criteria, $user->getId(), $lazy); // 5. Store in cache $this->cache->storeNewsForUser($user->getId(), $criteria, $news_collection); @@ -83,9 +82,13 @@ public function getNewsForUser(\ilObjUser $user, NewsCriteria $criteria, bool $l return $this->applyFinalProcessing($news_collection, $criteria); } - public function getNewsForContext(NewsContext $context, NewsCriteria $criteria, bool $lazy = false): NewsCollection - { - return $this->applyFinalProcessing($this->getNewsForContexts([$context], $criteria, $lazy), $criteria); + public function getNewsForContext( + NewsContext $context, + NewsCriteria $criteria, + int $user_id, + bool $lazy = false + ): NewsCollection { + return $this->applyFinalProcessing($this->getNewsForContexts([$context], $criteria, $user_id, $lazy), $criteria); } public function invalidateCache(int $user_id): void @@ -96,7 +99,7 @@ public function invalidateCache(int $user_id): void /** * @param NewsContext[] $contexts */ - private function getNewsForContexts(array $contexts, NewsCriteria $criteria, bool $lazy): NewsCollection + private function getNewsForContexts(array $contexts, NewsCriteria $criteria, int $user_id, bool $lazy): NewsCollection { // 1. Try context cache first (L1) $cached = $this->cache->getAggregatedContexts($contexts); @@ -117,7 +120,7 @@ private function getNewsForContexts(array $contexts, NewsCriteria $criteria, boo } // 4. Perform access checks [DPL 3] - $aggregated = $this->filterByAccess($hits, $criteria); + $aggregated = $this->filterByAccess($hits, $criteria, $user_id); // 5. Batch load news from the database [DPL 4] return $lazy @@ -156,18 +159,36 @@ private function fetchContextData(array $contexts): array * @param NewsContext[] $contexts * @return NewsContext[] */ - private function filterByAccess(array $contexts, NewsCriteria $criteria): array + private function filterByAccess(array $contexts, NewsCriteria $criteria, int $user_id): array { if ($criteria->isOnlyPublic()) { return $contexts; } - // Preload activation cache which is used in access handler - \ilObjectActivation::preloadData(array_map(fn($context) => $context->getRefId(), $contexts)); + // Remove contexts without news items or outside the criteria + $contexts = $this->repository->filterContext($contexts, $criteria); + + // Preload rbac cache + $this->rbac->preloadRbacPaCache(array_map(fn($context) => $context->getRefId(), $contexts), $user_id); + // Order contexts by level to keep tree hierarchy + usort($contexts, fn($a, $b) => $a->getLevel() <=> $b->getLevel()); $filtered = []; + $ac_result = []; + foreach ($contexts as $context) { - if ($this->access->checkAccess('read', '', $context->getRefId())) { + // Filter object and skip access check if the parent object was denied + if (isset($ac_result[$context->getParentRefId()]) && !$ac_result[$context->getParentRefId()]) { + continue; + } + + $ac_result[$context->getRefId()] = $this->rbac->checkAccess( + 'read', + $context->getRefId(), + $context->getObjType(), + ); + + if ($ac_result[$context->getRefId()]) { $filtered[] = $context; } } diff --git a/Services/News/src/Persistence/NewsRepository.php b/Services/News/src/Persistence/NewsRepository.php index 508b12274001..2e4cfd6713bb 100644 --- a/Services/News/src/Persistence/NewsRepository.php +++ b/Services/News/src/Persistence/NewsRepository.php @@ -64,6 +64,37 @@ public function findByIds(array $news_ids): array return array_map(fn($row) => $this->factory->newsItem($row), $this->db->fetchAll($result)); } + /** + * @param NewsContext[] $contexts + * @return NewsContext[] + */ + public function filterContext(array $contexts, NewsCriteria $criteria): array + { + $obj_ids = array_map(fn($context) => $context->getObjId(), $contexts); + + $values = []; + $types = []; + $query = "SELECT DISTINCT (context_obj_id) AS obj_id FROM il_news_item WHERE "; + $query .= $this->db->in('context_obj_id', $obj_ids, false, \ilDBConstants::T_INTEGER); + + if ($criteria->getPeriod() > 0) { + $query .= " AND creation_date >= %s"; + $values[] = self::parseTimePeriod($criteria->getPeriod()); + $types[] = ilDBConstants::T_TIMESTAMP; + } + + if ($criteria->getStartDate()) { + $query .= " AND creation_date >= %s"; + $values[] = $criteria->getStartDate()->format('Y-m-d H:i:s'); + $types[] = ilDBConstants::T_TIMESTAMP; + } + + $result = $this->db->queryF($query, $types, $values); + $needed_obj_ids = array_column($this->db->fetchAll($result), 'obj_id', 'obj_id'); + + return array_filter($contexts, fn($context) => isset($needed_obj_ids[$context->getObjId()])); + } + /** * @param int[] $news_ids