ILIAS  trunk Revision v12.0_alpha-1540-g00f839d5fa1
NewsCollectionService.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
29
35{
36 public function __construct(
37 private readonly NewsRepository $repository,
38 private readonly NewsCache $cache,
39 private readonly UserContextResolver $user_context_resolver,
40 private readonly \ilObjectDataCache $object_data,
41 private readonly \ilRbacSystem $rbac
42 ) {
43 }
44
45 public function getNewsForUser(\ilObjUser $user, NewsCriteria $criteria, bool $lazy = false): NewsCollection
46 {
47 // 1. Try user cache first
48 $cached_news = $this->cache->getNewsForUser($user->getId(), $criteria);
49 if ($cached_news !== null) {
50 // Transform the lazy collection to a normal collection if needed
51 if (!$lazy) {
52 $news_collection = new NewsCollection($this->repository->findByIds($cached_news->pluck('id')));
53 } else {
54 $news_collection = $cached_news->withFetchCallback(
55 fn(...$args) => $this->repository->loadLazyItems(...$args)
56 );
57 }
58
59 // Apply request-specific filtering [DPL 5]
60 return $this->applyFinalProcessing($news_collection, $criteria);
61 }
62
63 // 2. Add missing criteria and validate it
64 if ($criteria->isIncludeReadStatus() && $criteria->getReadUserId() === null) {
65 $criteria = $criteria->withReadUserId($user->getId());
66 }
67 $criteria->validate();
68
69 // 3. Get user accessible contexts [DPL 1]
70 $user_contexts = $this->user_context_resolver->getAccessibleContexts($user, $criteria);
71 if (empty($user_contexts)) {
72 return new NewsCollection();
73 }
74
75 // 4. Query news for resolved contexts [DPL 2-4]
76 $news_collection = $this->getNewsForContexts($user_contexts, $criteria, $user->getId(), $lazy);
77
78 // 5. Store in cache
79 $this->cache->storeNewsForUser($user->getId(), $criteria, $news_collection);
80
81 // 6. Apply request-specific filtering [DPL 5]
82 return $this->applyFinalProcessing($news_collection, $criteria);
83 }
84
85 public function getNewsForContext(
86 NewsContext $context,
87 NewsCriteria $criteria,
88 int $user_id,
89 bool $lazy = false
91 return $this->applyFinalProcessing($this->getNewsForContexts([$context], $criteria, $user_id, $lazy), $criteria);
92 }
93
94 public function invalidateCache(int $user_id): void
95 {
96 $this->cache->invalidateNewsForUser($user_id);
97 }
98
102 private function getNewsForContexts(array $contexts, NewsCriteria $criteria, int $user_id, bool $lazy): NewsCollection
103 {
104 // 1. Try context cache first (L1)
105 $cached = $this->cache->getAggregatedContexts($contexts);
106 $hits = $cached['hit'];
107
108 if (!empty($cached['missing'])) {
109 // 2. Batch load missing context object information [DPL 2]
110 $remaining = $this->fetchContextData($cached['missing']);
111
112 // 3. Perform aggregation [DPL 3]
113 if (!$criteria->isPreventNesting()) {
114 $aggregated = (new NewsAggregator())->aggregate($remaining);
115 $this->cache->storeAggregatedContexts($remaining, $aggregated);
116 $hits = array_merge($hits, $aggregated);
117 } else {
118 $hits = array_merge($hits, $remaining);
119 }
120 }
121
122 // 4. Add start dates to criteria
123 $criteria = $this->appendStartDateFilter($hits, $criteria);
124
125 // 5. Perform access checks [DPL 3]
126 $aggregated = $this->filterByAccess($hits, $criteria, $user_id);
127
128 // 6. Batch load news from the database [DPL 4]
129 return $lazy
130 ? $this->repository->findByContextsBatchLazy($aggregated, $criteria)
131 : $this->repository->findByContextsBatch($aggregated, $criteria);
132 }
133
138 private function fetchContextData(array $contexts): array
139 {
140 // Batch loads object_data and object_references using preloading
141 $obj_ids = array_filter(array_map(fn($context) => $context->getObjId(), $contexts));
142 $this->object_data->preloadObjectCache($obj_ids);
143
144 for ($i = 0; $i < count($contexts); $i++) {
145 $context = $contexts[$i];
146
147 if ($context->getObjId() === null) {
148 $context->setObjId($this->object_data->lookupObjId($context->getRefId()));
149 }
150
151 if ($context->getObjType() === null) {
152 $context->setObjType($this->object_data->lookupType($context->getObjId()));
153 }
154
155 $contexts[$i] = $context;
156 }
157
158 return $contexts;
159 }
160
165 private function filterByAccess(array $contexts, NewsCriteria $criteria, int $user_id): array
166 {
167 if ($criteria->isOnlyPublic()) {
168 return $contexts;
169 }
170
171 // Remove contexts without news items or outside the criteria
172 $contexts = $this->repository->filterContext($contexts, $criteria);
173
174 // Preload rbac cache
175 $this->rbac->preloadRbacPaCache(array_map(fn($context) => $context->getRefId(), $contexts), $user_id);
176
177 // Order contexts by level to keep tree hierarchy
178 usort($contexts, fn($a, $b) => $a->getLevel() <=> $b->getLevel());
179 $filtered = [];
180 $ac_result = [];
181
182 foreach ($contexts as $context) {
183 // Filter object and skip access check if the parent object was denied
184 if (isset($ac_result[$context->getParentRefId()]) && !$ac_result[$context->getParentRefId()]) {
185 continue;
186 }
187
188 $ac_result[$context->getRefId()] = $this->rbac->checkAccess(
189 'read',
190 $context->getRefId(),
191 $context->getObjType(),
192 );
193
194 if ($ac_result[$context->getRefId()]) {
195 $filtered[] = $context;
196 }
197 }
198 return $filtered;
199 }
200
204 private function appendStartDateFilter(array $contexts, NewsCriteria $criteria): NewsCriteria
205 {
206 $date_filter = [];
207
208 foreach ($contexts as $context) {
209 if (
210 !in_array($context->getObjType(), ['grp', 'crs']) ||
211 !\ilBlockSetting::_lookup('news', 'hide_news_per_date', 0, $context->getObjId())
212 ) {
213 continue;
214 }
215
216 $hide_date = \ilBlockSetting::_lookup('news', 'hide_news_date', 0, $context->getObjId());
217 if (empty($hide_date)) {
218 continue;
219 }
220
221 $date_filter[$context->getObjId()] = new \DateTimeImmutable($hide_date);
222 }
223
224 return $criteria->withStartDates($date_filter);
225 }
226
230 private function applyFinalProcessing(NewsCollection $collection, NewsCriteria $criteria): NewsCollection
231 {
232 return $collection->exclude($criteria->getExcludedNewsIds())->limit($criteria->getLimit());
233 }
234}
News Aggregator aggregates related contexts for a news context using a layer-wise Batching BFS to agg...
Optimized News Collection with memory-efficient data structures to support large news feeds.
exclude(array $news_ids)
Returns a new collection with only the news items that are not in the provided list.
News Context DTO represents a context where news items can be associated with.
Definition: NewsContext.php:29
News Criteria DTO for querying news items supports caching, JSON serialization, and validation.
validate()
Validate criteria parameters.
withReadUserId(?int $read_user_id)
withStartDates(array $start_dates)
News Collection Service orchestrates all news-related operations and provides a high-level API for th...
__construct(private readonly NewsRepository $repository, private readonly NewsCache $cache, private readonly UserContextResolver $user_context_resolver, private readonly \ilObjectDataCache $object_data, private readonly \ilRbacSystem $rbac)
applyFinalProcessing(NewsCollection $collection, NewsCriteria $criteria)
Apply the last steps of the news collection processing pipeline: Exclude, Limit.
getNewsForContext(NewsContext $context, NewsCriteria $criteria, int $user_id, bool $lazy=false)
appendStartDateFilter(array $contexts, NewsCriteria $criteria)
filterByAccess(array $contexts, NewsCriteria $criteria, int $user_id)
getNewsForUser(\ilObjUser $user, NewsCriteria $criteria, bool $lazy=false)
getNewsForContexts(array $contexts, NewsCriteria $criteria, int $user_id, bool $lazy)
User Context Resolver resolves which contexts a user can access for news operations.
Multi-Level News Cache Implementation:
Definition: NewsCache.php:36
News Repository provides basic CRUD operations and optimized database access for news operations with...
static _lookup(string $a_type, string $a_setting, int $a_user=0, int $a_block_id=0)
Lookup setting from database.
User class.
class ilObjectDataCache
class ilRbacSystem system function like checkAccess, addActiveRole ... Supporting system functions ar...
return['delivery_method'=> 'php',]
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples