ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
NewsItemTable.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
23use Generator;
25use ilCtrl;
27use ilLanguage;
29use ilObject;
30use ilObjUser;
32use ilSetting;
55use Psr\Http\Message\ServerRequestInterface;
57
58class NewsItemTable implements Table
59{
61
62 public const string URL_NS = 'nmit';
63 public const string ROW_ID_PARAMETER = 'news_item_row';
64 public const string ACTION_PARAMETER = 'action';
65 public const string ACTION_TYPE_PARAMETER = 'action_type';
66
67 protected ?StandardFilter $filter = null;
68
69 public function __construct(
70 protected Factory $ui_factory,
71 protected ilLanguage $lng,
72 protected ServerRequestInterface $request,
73 protected ilCtrl $ctrl,
74 protected ilSetting $setting,
75 protected Renderer $ui_renderer,
76 protected NewsAccess $news_access,
77 protected ilUIFilterService $filter_service,
78 protected NewsCollectionService $news_collection_service,
79 protected ilObjUser $user,
80 protected HttpService $http_service,
81 protected ilGlobalTemplateInterface $main_tpl,
82 protected Refinery $refinery,
83 ) {
84 }
85
86 public function getRows(
87 DataRowBuilder $row_builder,
88 array $visible_column_ids,
90 Order $order,
91 mixed $additional_viewcontrol_data,
92 mixed $filter_data,
93 mixed $additional_parameters
94 ): Generator {
95 $records = $this->limitRecords(
96 $this->sortRecords(
97 $this->loadRecords(
98 $this->extractFilterData($filter_data)
99 ),
100 $order
101 ),
102 $range
103 );
104
105 foreach ($records as $record) {
106 yield $this->getTableActions()->onDataRow(
107 $row_builder->buildDataRow((string) $record['id'], $record),
108 $record
109 );
110 }
111 }
112
113 private function getColumns(): array
114 {
115 $column_factory = $this->ui_factory->table()->column();
116
117 $columns = [
118 'post' => $column_factory->text($this->lng->txt('news_news_item_post'))->withIsSortable(false),
119 'assigned_to' => $column_factory->text($this->lng->txt('news_attached_to'))->withIsSortable(false),
120 ];
121
122 if ($this->isInternalRssEnabled()) {
123 $columns['access'] = $column_factory->text($this->lng->txt('access'))->withIsSortable(false);
124 }
125
126 $date_format = $this->user->getDateTimeFormat();
127 $columns += [
128 'author' => $column_factory->text($this->lng->txt('author'))->withIsSortable(false),
129 'created_at' => $column_factory->date($this->lng->txt('created'), $date_format)->withIsSortable(true),
130 'updated_at' => $column_factory->date($this->lng->txt('last_update'), $date_format)->withIsSortable(true),
131 ];
132
133 return $columns;
134 }
135
136 public function getTotalRowCount(mixed $additional_viewcontrol_data, mixed $filter_data, mixed $additional_parameters): ?int
137 {
138 return count($this->loadRecords($this->extractFilterData($filter_data)));
139 }
140
141 private function extractFilterData(mixed $filter_data): ?array
142 {
143 return $filter_data instanceof StandardFilter
144 ? $this->filter_service->getData($filter_data)
145 : null;
146 }
147
148 private function getNewsRequestContext(): array
149 {
150 return [
151 'ref_id' => (int) ($this->request->getQueryParams()['ref_id'] ?? 0),
152 'context_obj_id' => $this->ctrl->getContextObjId() ?? 0,
153 'context_obj_type' => $this->ctrl->getContextObjType() ?? '',
154 'context_sub_obj_id' => 0,
155 ];
156 }
157
158 private function isInternalRssEnabled(): bool
159 {
160 return (bool) $this->setting->get('enable_rss_for_internal');
161 }
162
163 private function sortRecords(array $records, Order $order): array
164 {
165 foreach ($order->get() as $order_field => $order_direction) {
166 usort($records, static function (array $left, array $right) use ($order_field): int {
167 $l = $left[$order_field] ?? null;
168 $r = $right[$order_field] ?? null;
169 return is_string($l) && is_string($r) ? strcmp($l, $r) : $l <=> $r;
170 });
171
172 if ($order_direction !== Order::DESC) {
173 continue;
174 }
175
176 $records = array_reverse($records);
177 }
178
179 return $records;
180 }
181
182 private function limitRecords(array $records, Range $range): array
183 {
184 return array_slice($records, $range->getStart(), $range->getLength());
185 }
186
187 public function getTableId(): string
188 {
189 return self::URL_NS;
190 }
191
195 public function getComponents(URLBuilder $url_builder): array
196 {
197 $this->filter = $this->getFilter();
198 $table = $this->ui_factory->table()->data(
199 $this,
200 $this->lng->txt('news'),
201 $this->getColumns()
202 )
203 ->withRequest($this->request)
204 ->withFilter($this->filter)
205 ->withActions($this->getTableActions()->getEnabledActions(...$this->acquireParameters($url_builder)))
206 ->withId($this->getTableId());
207
208 return [
209 $this->filter,
210 $table
211 ];
212 }
213
217 protected function acquireParameters(URLBuilder $url_builder): array
218 {
219 return $url_builder->acquireParameters(
220 [$this->getTableId()],
221 self::ROW_ID_PARAMETER,
222 self::ACTION_PARAMETER,
223 self::ACTION_TYPE_PARAMETER
224 );
225 }
226
227 protected function getTableActions(): TableActions
228 {
229 return (new NewsItemTableActionsFactory(
230 $this->ui_factory,
231 $this->lng,
232 $this->news_access,
233 $this->ctrl,
234 $this->ui_renderer,
235 $this->refinery,
236 $this->http_service,
237 $this->main_tpl,
238 $this->loadRecords()
239 ))->getTableActions();
240 }
241
242 private function getFilter(): StandardFilter
243 {
244 $author_options = [];
245
246 foreach ($this->loadNewsCollection()->getNewsItems() as $item) {
247 $user_id = $item->getUserId();
248 if ($user_id <= 0) {
249 continue;
250 }
251
253 $author_options[$login] = $login;
254 }
255
256 $field_factory = $this->ui_factory->input()->field();
257 $filter_inputs = [
258 'text' => $field_factory->text($this->lng->txt('news_news_item_content')),
259 ];
260
261 if ($this->isInternalRssEnabled()) {
262 $filter_inputs['access'] = $field_factory->select(
263 $this->lng->txt('access'),
264 [
265 'public' => $this->lng->txt('news_visibility_public'),
266 'users' => $this->lng->txt('news_visibility_users'),
267 ]
268 );
269 }
270
271 $filter_inputs['author'] = $field_factory->select($this->lng->txt('author'), $author_options);
272
273 return $this->filter_service->standard(
274 'news_item_table_filter',
275 $this->ctrl->getLinkTargetByClass(ilNewsItemGUI::class, 'editNews', ''),
276 $filter_inputs,
277 array_fill(0, count($filter_inputs), true),
278 true,
279 true
280 );
281 }
282
283 private function buildOverviewCriteria(string $context_obj_type): NewsCriteria
284 {
285 return new NewsCriteria(
286 limit: null,
287 prevent_nesting: !in_array($context_obj_type, ['cat', 'grp', 'crs', 'root'], true),
288 no_auto_generated: true,
289 include_read_status: false
290 );
291 }
292
294 {
295 $news_request_context = $this->getNewsRequestContext();
296
297 return $this->news_collection_service->getNewsForContext(
298 new NewsContext(
299 $news_request_context['ref_id'],
300 $news_request_context['context_obj_id'],
301 $news_request_context['context_obj_type']
302 ),
303 $this->buildOverviewCriteria($this->getNewsRequestContext()['context_obj_type']),
304 $this->user->getId()
305 );
306 }
307
308 private function loadRecords(?array $filter_data = null): array
309 {
310 $data = $this->loadNewsCollection()->getNewsItems();
311 if ($data === []) {
312 return [];
313 }
314
315 $filter = $filter_data ?? [];
316 $filter_raw = $filter['text'] ?? '';
317 $filter_text = $filter_raw !== '' ? strtolower($filter_raw) : '';
318 $filter_access = $filter['access'] ?? '';
319 $filter_author = $filter['author'] ?? '';
320 $enable_internal_rss = $this->isInternalRssEnabled();
321
322 $records = [];
323 foreach ($data as $item) {
324 $item_matches_filters = $this->itemMatchesFilters(
325 $item,
326 $filter_text,
327 $filter_access,
328 $filter_author,
329 $enable_internal_rss
330 );
331
332 if (!$item_matches_filters || !$this->itemShouldAppearInTable($item)) {
333 continue;
334 }
335
336 $assigned_to = $this->assignedToMarkupOrNull($item);
337 if ($assigned_to === null) {
338 continue;
339 }
340
341 $record = [
342 'id' => (string) $item->getId(),
343 'post' => $this->renderPostCell($item),
344 'assigned_to' => $assigned_to,
345 'author' => $this->renderAuthorDisplay($item),
346 'created_at' => $item->getCreationDate(),
347 'updated_at' => $item->getUpdateDate(),
348 ];
349
350 if ($enable_internal_rss) {
351 $record['access'] = $this->renderAccessDisplay($item);
352 }
353
354 $records[] = $record;
355 }
356
357 return $records;
358 }
359
360 private function itemMatchesFilters(
361 NewsItem $item,
362 string $search_text,
363 string $filter_access,
364 string $filter_author,
365 bool $enable_internal_rss
366 ): bool {
367 if ($search_text !== '') {
368 $search_text = strtolower($search_text);
369
370 if (
371 !str_contains(strtolower($item->getTitle()), $search_text)
372 && !str_contains(strtolower($item->getContent() . $item->getContentLong()), $search_text)
373 ) {
374 return false;
375 }
376 }
377
378 if (
379 $filter_access !== ''
380 && $this->getItemAccess($item, $enable_internal_rss) !== $filter_access
381 ) {
382 return false;
383 }
384
385 if ($filter_author !== '') {
386 $filter_user_id = $item->getUserId();
387 if (
388 $filter_user_id <= 0
389 || ilObjUser::_lookupLogin($filter_user_id) !== $filter_author
390 ) {
391 return false;
392 }
393 }
394
395 return true;
396 }
397
398 private function itemShouldAppearInTable(NewsItem $item): bool
399 {
400 return $item->getId() > 0 && ($this->news_access->canEdit($item) || $this->news_access->canDelete($item));
401 }
402
403 private function renderPostCell(NewsItem $item): string
404 {
405 $post_title = $item->getTitle();
406 if ($post_title === '' || $item->getId() <= 0) {
407 return '';
408 }
409
410 $post_content = "{$item->getContent()}{$item->getContentLong()}";
411 $lightbox_page = $this->ui_factory->modal()->lightboxTextPage(
412 $post_content ?: $this->lng->txt('news_news_item_no_content'),
413 $post_title
414 );
415 $modal = $this->ui_factory->modal()->lightbox([$lightbox_page]);
416 $post_button = $this->ui_factory->button()->shy($post_title, '')
417 ->withOnClick($modal->getShowSignal());
418
419 return $this->ui_renderer->render([$post_button, $modal]);
420 }
421
422 private function renderAccessDisplay(NewsItem $item): string
423 {
424 return $this->lng->txt(
425 $this->getItemAccess($item, true) === 'public'
426 ? 'news_visibility_public'
427 : 'news_visibility_users'
428 );
429 }
430
431 private function renderAuthorDisplay(NewsItem $item): string
432 {
433 $author_user_id = $item->getUserId();
434 return $author_user_id <= 0 ? '' : ilObjUser::_lookupLogin($author_user_id);
435 }
436
437 private function assignedToMarkupOrNull(NewsItem $item): ?string
438 {
439 $context_obj_type = $item->getContextObjType();
440 $context_obj_id = $item->getContextObjId();
441 if ($context_obj_type === '' || $context_obj_id <= 0) {
442 return '';
443 }
444
445 $ref_ids = ilObject::_getAllReferences($context_obj_id);
446 $item_ref_id = array_values($ref_ids)[0] ?? 0;
447
448 if ($item_ref_id <= 0) {
449 return null;
450 }
451
452 $obj_title = ilObject::_lookupTitle($context_obj_id);
453 $obj_type_txt = $this->lng->txt("obj_{$context_obj_type}");
454
455 $this->ctrl->setParameterByClass(ilRepositoryGUI::class, 'ref_id', $item_ref_id);
456 $link_url = $this->ctrl->getLinkTargetByClass(ilRepositoryGUI::class, '');
457 $this->ctrl->setParameterByClass(ilRepositoryGUI::class, 'ref_id', null);
458
459 $obj_link = $this->ui_factory->link()->standard($obj_title, $link_url);
460
461 return "{$obj_type_txt}: {$this->ui_renderer->render($obj_link)}";
462 }
463
464 private function getItemAccess(NewsItem $item, bool $enable_internal_rss): string
465 {
466 if (!$enable_internal_rss) {
467 return '';
468 }
469
470 $public_notifications_for_context = (bool) ilBlockSetting::_lookup(
471 'news',
472 'public_notifications',
473 0,
474 $item->getContextObjId()
475 );
476 $public_via_block_defaults = $item->getPriority() === 0 && $public_notifications_for_context;
477
478 return ($item->getVisibility() === 'public' || $public_via_block_defaults) ? 'public' : 'users';
479 }
480
481 public function execute(URLBuilder $url_builder): ?Modal
482 {
483 return $this->getTableActions()->execute(...$this->acquireParameters($url_builder));
484 }
485}
Builds a Color from either hex- or rgb values.
Definition: Factory.php:31
Builds data types.
Definition: Factory.php:36
Both the subject and the direction need to be specified when expressing an order.
Definition: Order.php:29
const DESC
Definition: Order.php:31
A simple class to express a naive range of whole positive numbers.
Definition: Range.php:29
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 Collection Service orchestrates all news-related operations and provides a high-level API for th...
getRows(DataRowBuilder $row_builder, array $visible_column_ids, Range $range, Order $order, mixed $additional_viewcontrol_data, mixed $filter_data, mixed $additional_parameters)
This is called by the table to retrieve rows; map data-records to rows using the $row_builder e....
itemMatchesFilters(NewsItem $item, string $search_text, string $filter_access, string $filter_author, bool $enable_internal_rss)
acquireParameters(URLBuilder $url_builder)
execute(URLBuilder $url_builder)
getTotalRowCount(mixed $additional_viewcontrol_data, mixed $filter_data, mixed $additional_parameters)
Mainly for the purpose of pagination-support, it is important to know about the total number of recor...
limitRecords(array $records, Range $range)
getItemAccess(NewsItem $item, bool $enable_internal_rss)
itemShouldAppearInTable(NewsItem $item)
__construct(protected Factory $ui_factory, protected ilLanguage $lng, protected ServerRequestInterface $request, protected ilCtrl $ctrl, protected ilSetting $setting, protected Renderer $ui_renderer, protected NewsAccess $news_access, protected ilUIFilterService $filter_service, protected NewsCollectionService $news_collection_service, protected ilObjUser $user, protected HttpService $http_service, protected ilGlobalTemplateInterface $main_tpl, protected Refinery $refinery,)
buildOverviewCriteria(string $context_obj_type)
extractFilterData(mixed $filter_data)
loadRecords(?array $filter_data=null)
sortRecords(array $records, Order $order)
getComponents(URLBuilder $url_builder)
assignedToMarkupOrNull(NewsItem $item)
acquireParameters(array $namespace, string ... $names)
Definition: URLBuilder.php:138
ilSetting $setting
Definition: class.ilias.php:68
Block Setting class.
static _lookup(string $a_type, string $a_setting, int $a_user=0, int $a_block_id=0)
Lookup setting from database.
Class ilCtrl provides processing control methods.
language handling
User Interface for NewsItem entities.
User class.
static _lookupLogin(int $a_user_id)
Class ilObject Basic functions for all objects.
static _getAllReferences(int $id)
get all reference ids for object ID
static _lookupTitle(int $obj_id)
Class ilRepositoryGUI.
ILIAS Setting Class.
A component is the most general form of an entity in the UI.
Definition: Component.php:28
This describes a standard filter.
Definition: Standard.php:27
This describes commonalities between the different modals.
Definition: Modal.php:35
buildDataRow(string $id, array $record)
This is how the factory for UI elements looks.
Definition: Factory.php:38
An entity that renders components to a string output.
Definition: Renderer.php:31
$request
@ilCtrl_Calls ilObjLearningSequenceContentGUI: ilObjLearningSequenceConditionsGUI
Definition: ltiauth.php:39
filter(string $filter_id, array $class_path, string $cmd, bool $activated=true, bool $expanded=true)
global $lng
Definition: privfeed.php:26
if(!file_exists('../ilias.ini.php'))
$data
Definition: storeScorm.php:45