ILIAS  trunk Revision v12.0_alpha-1540-g00f839d5fa1
NewsRepository.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
23use DateTimeImmutable;
31
37{
38 public function __construct(
39 protected readonly \ilDBInterface $db,
40 protected readonly Factory $factory
41 ) {
42 }
43
44 public function findById(int $news_id): ?NewsItem
45 {
46 $query = "SELECT * FROM il_news_item WHERE id = %s";
47 $result = $this->db->queryF($query, [\ilDBConstants::T_INTEGER], [$news_id]);
48
49 return $result->numRows()
50 ? $this->factory->newsItem($this->db->fetchAssoc($result))
51 : null;
52 }
53
58 public function findByIds(array $news_ids): array
59 {
60 if (empty($news_ids)) {
61 return [];
62 }
63
64 $result = $this->db->query($this->buildFindQuery($news_ids));
65 return array_map(fn($row) => $this->factory->newsItem($row), $this->db->fetchAll($result));
66 }
67
72 public function filterContext(array $contexts, NewsCriteria $criteria): array
73 {
74 $obj_ids = array_map(fn($context) => $context->getObjId(), $contexts);
75
76 $values = [];
77 $types = [];
78 $query = "SELECT DISTINCT (context_obj_id) AS obj_id FROM il_news_item WHERE ";
79 $query .= $this->db->in('context_obj_id', $obj_ids, false, \ilDBConstants::T_INTEGER);
80
81 if ($criteria->getPeriod() > 0) {
82 $query .= " AND creation_date >= %s";
83 $values[] = self::parseTimePeriod($criteria->getPeriod());
85 }
86
87 if ($criteria->getStartDates() !== []) {
88 $query .= " AND id NOT IN ({$this->buildExcludeByDateQuery($criteria->getStartDates())})";
89 }
90
91 $result = $this->db->queryF($query, $types, $values);
92 $needed_obj_ids = array_column($this->db->fetchAll($result), 'obj_id', 'obj_id');
93
94 return array_filter($contexts, fn($context) => isset($needed_obj_ids[$context->getObjId()]));
95 }
96
97
103 public function loadLazyItems(array $news_ids, array $group_context_types): array
104 {
105 if (empty($news_ids)) {
106 return [];
107 }
108
109 $result = $this->db->query($this->buildFindQuery($news_ids));
110 $news_items = [];
111 $additional_obj_ids = [];
112
113 foreach ($this->db->fetchAll($result) as $row) {
114 $news_item = $this->factory->newsItem($row);
115
116 if (in_array($news_item->getContextObjType(), $group_context_types)) {
117 $additional_obj_ids[] = $news_item->getContextObjId();
118 }
119
120 $news_items[] = $news_item;
121 }
122
123 if (empty($additional_obj_ids)) {
124 return $news_items;
125 }
126
127 // Fetch all additional items with same context_obj_id for grouping
128 $query = $this->buildFindQuery()
129 . " WHERE " . $this->db->in('context_obj_id', $additional_obj_ids, false, \ilDBConstants::T_INTEGER)
130 . " AND " . $this->db->in('id', $news_ids, true, \ilDBConstants::T_INTEGER);
131 $result = $this->db->query($query);
132
133 return array_merge(
134 $news_items,
135 array_map(fn($row) => $this->factory->newsItem($row), $this->db->fetchAll($result))
136 );
137 }
138
139 private function buildFindQuery(?array $news_ids = null): string
140 {
141 $query = "
142 SELECT il_news_item.*,
143 COALESCE(
144 (SELECT ref_id FROM object_reference WHERE object_reference.obj_id = il_news_item.context_obj_id LIMIT 1),
145 0
146 ) AS ref_id
147 FROM il_news_item ";
148
149 if ($news_ids !== null) {
150 $query .= "WHERE " . $this->db->in('id', $news_ids, false, \ilDBConstants::T_INTEGER);
151 }
152
153 return $query;
154 }
155
159 public function findByContextsBatch(array $contexts, NewsCriteria $criteria): NewsCollection
160 {
161 if (empty($contexts)) {
162 return new NewsCollection();
163 }
164
165 $obj_ids = array_map(fn($context) => $context->getObjId(), $contexts);
166 $result = $this->db->queryF(...$this->buildBatchQuery($obj_ids, $criteria));
167
168 $items = [];
169 $user_read = [];
170
171 while ($row = $this->db->fetchAssoc($result)) {
172 $items[] = $this->factory->newsItem($row);
173 $user_read[$row['id']] = isset($row['user_read']) && $row['user_read'] !== 0;
174 }
175
176 $collection = new NewsCollection($items);
177 if ($criteria->isIncludeReadStatus()) {
178 $collection->setUserReadStatus($criteria->getReadUserId(), $user_read);
179 }
180
181 return $collection;
182 }
183
187 public function findByContextsBatchLazy(array $contexts, NewsCriteria $criteria): NewsCollection
188 {
189 if (empty($contexts)) {
190 return new NewsCollection();
191 }
192
193 $obj_ids = array_map(fn($context) => $context->getObjId(), $contexts);
194 $result = $this->db->queryF(...$this->buildBatchQuery($obj_ids, $criteria, true));
195
196 $items = [];
197 $user_read = [];
198 while ($row = $this->db->fetchAssoc($result)) {
199 $items[] = $row['id'];
200 $user_read[$row['id']] = isset($row['user_read']) && $row['user_read'] !== 0;
201 }
202
203 $collection = new LazyNewsCollection($items, fn(...$args) => $this->loadLazyItems(...$args));
204 if ($criteria->isIncludeReadStatus()) {
205 $collection->setUserReadStatus($criteria->getReadUserId(), $user_read);
206 }
207
208 return $collection;
209 }
210
215 public function countByContextsBatch(array $contexts): array
216 {
217 $context_map = [];
218 foreach ($contexts as $context) {
219 $context_map[$context->getObjId()] = $context;
220 }
221
222 $in_clause = $this->db->in('context_obj_id', array_keys($context_map), false, ilDBConstants::T_INTEGER);
223 $query = "SELECT context_obj_id, count(context_obj_id) as count FROM il_news_item WHERE {$in_clause} GROUP BY context_obj_id";
224 $result = $this->db->query($query);
225
226 $count = [];
227 foreach ($this->db->fetchAll($result) as $row) {
228 $count[] = [
229 $context_map[$row['context_obj_id']],
230 $row['count']
231 ];
232 }
233
234 return $count;
235 }
236
237 private function buildBatchQuery(array $obj_ids, NewsCriteria $criteria, bool $only_id = false): array
238 {
239 $values = [];
240 $types = [];
241 $joins = '';
242
243 if ($only_id) {
244 $columns = ['il_news_item.id'];
245 } else {
246 $columns = [
247 'il_news_item.*',
248 'COALESCE((SELECT ref_id FROM object_reference WHERE object_reference.obj_id = il_news_item.context_obj_id LIMIT 1), 0) AS ref_id'
249 ];
250 }
251
252 if ($criteria->isIncludeReadStatus()) {
253 if ($criteria->getReadUserId() === null) {
254 throw new \InvalidArgumentException("Read user id is required for read status");
255 }
256
257 $columns[] = 'il_news_read.user_id AS user_read';
258 $joins .= 'LEFT JOIN il_news_read ON il_news_item.id = il_news_read.news_id AND il_news_read.user_id = %s ';
259
260 $values[] = $criteria->getReadUserId();
261 $types[] = ilDBConstants::T_INTEGER;
262 }
263
264 $query = "SELECT " . join(', ', $columns) . " FROM il_news_item {$joins} WHERE "
265 . $this->db->in('context_obj_id', $obj_ids, false, ilDBConstants::T_INTEGER);
266
267 if ($criteria->getPeriod() > 0) {
268 $query .= " AND creation_date >= %s";
269 $values[] = self::parseTimePeriod($criteria->getPeriod());
271 }
272
273 if ($criteria->isNoAutoGenerated()) {
274 $query .= " AND priority = 1 AND content_type = 'text'";
275 }
276
277 if ($criteria->getMinPriority() !== null || $criteria->getMaxPriority() !== null) {
278 $operator = $criteria->getMinPriority() !== null ? '>=' : '<=';
279 $query .= " AND n.priority {$operator} %s";
280 $values[] = $criteria->getMinPriority();
281 $types[] = ilDBConstants::T_INTEGER;
282 }
283
284 if ($criteria->isOnlyPublic()) {
285 $query .= " AND visibility = '" . NEWS_PUBLIC . "'";
286 }
287
288 if ($criteria->getStartDates() !== []) {
289 $query .= " AND id NOT IN ({$this->buildExcludeByDateQuery($criteria->getStartDates())})";
290 }
291
292 $query .= " ORDER BY creation_date DESC";
293
294 return [$query, $types, $values];
295 }
296
300 private function buildExcludeByDateQuery(array $start_dates): string
301 {
302 $conditions = [];
303 foreach ($start_dates as $obj_id => $date) {
304 $conditions[] = " (context_obj_id = {$obj_id} AND creation_date < '{$date->format('Y-m-d H:i:s')}') ";
305 }
306
307 return "SELECT id FROM il_news_item WHERE" . implode('OR', $conditions);
308 }
309
310 private static function parseTimePeriod(string|int $time_period): string
311 {
312 // time period is a number of days
313 if (is_numeric($time_period) && $time_period > 0) {
314 return date('Y-m-d H:i:s', time() - ($time_period * 24 * 60 * 60));
315 }
316
317 // time period is datetime (string)
318 if (preg_match("/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/", $time_period)) {
319 return $time_period;
320 }
321
322 return '';
323 }
324}
factory()
Factory for creating News DTOs from database results (arrays)
Definition: Factory.php:29
This class is a special implementation of a NewsCollection that is designed to load the complete News...
Optimized News Collection with memory-efficient data structures to support large news feeds.
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.
News Item DTO for transfer of news items.
Definition: NewsItem.php:29
News Repository provides basic CRUD operations and optimized database access for news operations with...
filterContext(array $contexts, NewsCriteria $criteria)
findByContextsBatchLazy(array $contexts, NewsCriteria $criteria)
__construct(protected readonly \ilDBInterface $db, protected readonly Factory $factory)
static parseTimePeriod(string|int $time_period)
loadLazyItems(array $news_ids, array $group_context_types)
buildBatchQuery(array $obj_ids, NewsCriteria $criteria, bool $only_id=false)
findByContextsBatch(array $contexts, NewsCriteria $criteria)
const NEWS_PUBLIC
Class ilDBConstants.
Interface ilDBInterface.