Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Services/News/Service/class.InternalDomainService.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function collection(): NewsCollectionService
$this->repo_service->cache(),
$this->resolver(),
$this->objectDataCache(),
$this->access()
$this->DIC->rbac()->system()
);
}

Expand Down
3 changes: 2 additions & 1 deletion Services/News/Timeline/class.TimelineManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions Services/News/classes/class.ilNewsForContextBlockGUI.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
);

Expand Down
6 changes: 3 additions & 3 deletions Services/News/src/Aggregation/NewsAggregator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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);
Expand Down
45 changes: 33 additions & 12 deletions Services/News/src/Domain/NewsCollectionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
) {
}

Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand All @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -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;
}
}
Expand Down
31 changes: 31 additions & 0 deletions Services/News/src/Persistence/NewsRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down