Mentions::action_index()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 0
dl 0
loc 20
ccs 0
cts 11
cp 0
crap 2
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Handles all the mentions actions so members are notified of mentionable actions
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * @version 2.0 Beta 1
11
 *
12
 */
13
14
namespace ElkArte\Controller;
15
16
use ElkArte\AbstractController;
17
use ElkArte\Action;
18
use ElkArte\EventManager;
19
use ElkArte\Exceptions\Exception;
20
use ElkArte\Helper\DataValidator;
21
use ElkArte\Languages\Txt;
22
use ElkArte\Mentions\Mentioning;
23
use ElkArte\User;
24
25
/**
26
 * as liking a post, adding a buddy, @ calling a member in a post
27
 *
28
 * @package Mentions
29
 */
30
class Mentions extends AbstractController
31
{
32
	/** @var array Will hold all available mention types */
33
	protected $_known_mentions = [];
34
35
	/** @var string The type of the mention we are looking at (if empty means all of them) */
36
	protected $_type = '';
37
38
	/** @var string The url of the display mentions button (all, unread, etc.) */
39
	protected $_url_param = '';
40
41
	/** @var int Used for pagination, keeps track of the current start point */
42
	protected $_page = 0;
43
44
	/** @var int Number of items per page */
45
	protected $_items_per_page = 20;
46
47
	/** @var string Default sorting column */
48
	protected $_default_sort = 'log_time';
49
50
	/** @var string User chosen sorting column */
51
	protected $_sort = '';
52
53
	/** @var string[] The sorting methods we know */
54
	protected $_known_sorting = [];
55
56
	/** @var bool Determine if we are looking only at unread mentions or any kind of */
57
	protected $_all = false;
58
59
	/**
60
	 * Good old constructor
61
	 *
62
	 * @param EventManager $eventManager
63
	 */
64
	public function __construct($eventManager)
65
	{
66
		$this->_known_sorting = ['id_member_from', 'type', 'log_time'];
67
68
		parent::__construct($eventManager);
69
	}
70
71
	/**
72
	 * Set up the data for the mention based on what was requested
73
	 * This function is called before the flow is redirected to action_index().
74
	 */
75
	public function pre_dispatch()
76
	{
77
		global $modSettings;
78
79
		// I'm not sure if this is needed, though better have it. :P
80
		if (empty($modSettings['mentions_enabled']))
81
		{
82
			throw new Exception('no_access', false);
83
		}
84
85
		require_once(SUBSDIR . '/Mentions.subs.php');
86
87
		$this->_known_mentions = getMentionTypes(User::$info->id, 'system');
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
88
	}
89
90
	/**
91
	 * The default action is to show the list of mentions
92
	 * This allows ?action=mention to be forwarded to action_list()
93
	 */
94
	public function action_index()
95
	{
96
		global $context;
97
98
		$subActions = [
99
			'fetch' => [$this, 'action_fetch'],
100
			'list' => [$this, 'action_list'],
101
			'results' => [$this, 'action_results'],
102
			'markread' => [$this, 'action_markread'],
103
			'updatestatus' => [$this, 'action_updatestatus'],
104
		];
105
106
		// Set up the action control
107
		$action = new Action('mentions');
108
109
		$subAction = $action->initialize($subActions, 'list');
110
		$context['sub_action'] = $subAction;
111
112
		// Call the right method.
113
		$action->dispatch($subAction);
114
	}
115
116
	/**
117
	 * Fetches number of notifications and number of recently added ones for use
118
	 * in favicon and desktop notifications.  Triggered by URL request from
119
	 * ElkNotifications
120
	 *
121
	 * @todo probably should be placed somewhere else.
122
	 */
123
	public function action_fetch(): void
124
	{
125
		global $context, $txt, $modSettings;
126
127
		if (empty($modSettings['usernotif_favicon_enable']) && empty($modSettings['usernotif_desktop_enable']))
128
		{
129
			die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
130
		}
131
132
		setJsonTemplate();
133
134
		require_once(SUBSDIR . '/Mentions.subs.php');
135
		require_once(SUBSDIR . '/PersonalMessage.subs.php');
136
137
		$lastsentmention = $this->_req->getQuery('lastsentmention', 'intval', 0);
138
		$lastsentpm = $this->_req->getQuery('lastsentpm', 'intval', 0);
139
140
		if (empty($lastsentmention) && !empty($_SESSION['notifications_lastsentmention']))
141
		{
142
			$lastsentmention = (int) $_SESSION['notifications_lastsentmention'];
143
		}
144
		if (empty($lastsentpm) && !empty($_SESSION['notifications_lastsentpm']))
145
		{
146
			$lastsentpm = (int) $_SESSION['notifications_lastsentpm'];
147
		}
148
149
		// We only know AJAX for this particular action
150
		$context['json_data'] = [
151
			'lasttimemention' => getTimeLastMention($this->user->id),
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
152
			'lasttimepm' => getLastPMSentTime($this->user->id)
153
		];
154
155
		// Data to be supplied to JSON template, consumed by favicon-notify.js
156
		if (!empty($modSettings['usernotif_favicon_enable']))
157
		{
158
			$context['json_data']['mentions'] = (int) $this->user->mentions;
0 ignored issues
show
Bug Best Practice introduced by
The property mentions does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
159
			$context['json_data']['pm_unread'] = (int) $this->user->unread_messages;
0 ignored issues
show
Bug Best Practice introduced by
The property unread_messages does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
160
		}
161
162
		// Data to be supplied to Push via desktop-notify.js, used to trigger desktop notifications
163
		// on new mentions (like/quote/etc) and PMs.
164
		if (!empty($modSettings['usernotif_desktop_enable']))
165
		{
166
			$new_mentions = getNewMentions($this->user->id, $lastsentmention);
167
			$new_pms = getNewPMs($this->user->id, $lastsentpm);
168
169
			$context['json_data']['desktop_notifications'] = [
170
				'new_from_last' => $new_mentions + $new_pms,
171
				'title' => sprintf($txt['forum_notification'], strip_tags(un_htmlspecialchars($context['forum_name']))),
172
				'link' => '/index.php?action=mentions',
173
			];
174
			$context['json_data']['desktop_notifications']['message'] = sprintf(
175
				$txt[$new_mentions + $new_pms === 0 ? 'unread_notifications' : 'new_from_last_notifications'],
176
				$context['json_data']['desktop_notifications']['new_from_last']);
177
		}
178
179
		$_SESSION['notifications_lastsentmention'] = $context['json_data']['lasttimemention'];
180
		$_SESSION['notifications_lastsentpm'] = $context['json_data']['lasttimepm'];
181
	}
182
183
	/**
184
	 * Display a list of mentions for the current user.
185
	 *
186
	 *  - Allows them to mark them read or unread
187
	 *  - Can sort the various forms of mentions, such as likes, buddies, quoted, etc.
188
	 */
189
	public function action_list(): void
190
	{
191
		global $context, $txt, $scripturl;
192
193
		// Only registered members can be mentioned
194
		is_not_guest();
195
196
		require_once(SUBSDIR . '/Mentions.subs.php');
197
		Txt::load('Mentions');
198
199
		$this->_buildUrl();
200
201
		$list_options = [
202
			'id' => 'list_mentions',
203
			'title' => empty($this->_all) ? $txt['my_unread_mentions'] : $txt['my_mentions'],
204
			'items_per_page' => $this->_items_per_page,
205
			'base_href' => $scripturl . '?action=mentions;sa=list' . $this->_url_param,
206
			'default_sort_col' => $this->_default_sort,
207
			'default_sort_dir' => 'default',
208
			'no_items_label' => $this->_all ? $txt['no_mentions_yet'] : $txt['no_new_mentions'],
209
			'get_items' => [
210
				'function' => fn(int $start, int $limit, string $sort, bool $all, string $type): array => $this->list_loadMentions($start, $limit, $sort, $all, $type),
211
				'params' => [
212
					$this->_all,
213
					$this->_type,
214
				],
215
			],
216
			'get_count' => [
217
				'function' => fn(bool $all, string $type) => $this->list_getMentionCount($all, $type),
218
				'params' => [
219
					$this->_all,
220
					$this->_type,
221
				],
222
			],
223
			'columns' => [
224
				'id_member_from' => [
225
					'header' => [
226
						'value' => $txt['mentions_from'],
227
					],
228
					'data' => [
229
						'function' => static function ($row) {
230
							global $settings;
231
232
							if (isset($settings['mentions']['mentioner_template']))
233
							{
234
								return str_replace(
235
									[
236
										'{avatar_img}',
237
										'{mem_url}',
238
										'{mem_name}',
239
									],
240
									[
241
										$row['avatar']['image'],
242
										empty($row['id_member_from']) ? '#' : getUrl('action', ['action' => 'profile', 'u' => $row['id_member_from']]),
243
										$row['mentioner'],
244
									],
245
									$settings['mentions']['mentioner_template']);
246
							}
247
248
							return '';
249
						},
250
					],
251
					'sort' => [
252
						'default' => 'mtn.id_member_from',
253
						'reverse' => 'mtn.id_member_from DESC',
254
					],
255
				],
256
				'type' => [
257
					'header' => [
258
						'value' => $txt['mentions_what'],
259
					],
260
					'data' => [
261
						'db' => 'message',
262
					],
263
					'sort' => [
264
						'default' => 'mtn.mention_type',
265
						'reverse' => 'mtn.mention_type DESC',
266
					],
267
				],
268
				'log_time' => [
269
					'header' => [
270
						'value' => $txt['mentions_when'],
271
						'class' => 'mention_log_time',
272
					],
273
					'data' => [
274
						'db' => 'log_time',
275
						'timeformat' => 'html_time',
276
						'class' => 'mention_log_time',
277
					],
278
					'sort' => [
279
						'default' => 'mtn.log_time DESC',
280
						'reverse' => 'mtn.log_time',
281
					],
282
				],
283
				'action' => [
284
					'header' => [
285
						'value' => $txt['mentions_action'],
286
						'class' => 'listaction grid8',
287
					],
288
					'data' => [
289
						'function' => static function ($row) {
290
							global $txt;
291
292
							$mark = empty($row['status']) ? 'read' : 'unread';
293
							$opts = '<a href="' . getUrl('action', ['action' => 'mentions', 'sa' => 'updatestatus', 'mark' => $mark, 'item' => $row['id_mention'], '{session_data}']) . '"><i class="icon i-mark_' . $mark . '" title="' . $txt['mentions_mark' . $mark] . '" /><s>' . $txt['mentions_mark' . $mark] . '</s></i></a>&nbsp;';
294
295
							return $opts . '<a href="' . getUrl('action', ['action' => 'mentions', 'sa' => 'updatestatus', 'mark' => 'delete', 'item' => $row['id_mention'], '{session_data}']) . '"><i class="icon i-remove" title="' . $txt['delete'] . '"><s>' . $txt['delete'] . '</s></i></a>';
296
						},
297
						'class' => 'listaction grid8',
298
					],
299
				],
300
			],
301
			'list_menu' => [
302
				'show_on' => 'top',
303
				'links' => [
304
					[
305
						'href' => getUrl('action', ['action' => 'mentions'] + (empty($this->_all) ? [] : ['all'])),
306
						'is_selected' => empty($this->_type),
307
						'label' => $txt['mentions_type_all']
308
					],
309
				],
310
			],
311
			'additional_rows' => [
312
				[
313
					'position' => 'above_column_headers',
314
					'class' => 'flow_flex_right',
315
					'value' => '<a class="linkbutton" href="' . $scripturl . '?action=mentions' . (empty($this->_all) ? ';all' : '') . str_replace(';all', '', $this->_url_param) . '">' . (empty($this->_all) ? $txt['mentions_all'] : $txt['mentions_unread']) . '</a>',
316
				],
317
				[
318
					'class' => 'submitbutton',
319
					'position' => 'below_table_data',
320
					'value' => '<a class="linkbutton" href="' . $scripturl . '?action=mentions;sa=updatestatus;mark=readall' . str_replace(';all', '', $this->_url_param) . ';' . $context['session_var'] . '=' . $context['session_id'] . '">' . $txt['mentions_mark_all_read'] . '</a>',
321
				],
322
			],
323
		];
324
325
		// Build the available mention tabs
326
		$this->_known_mentions = $this->_all === true ? getMentionTypes(User::$info->id, 'system') : getMentionTypes(User::$info->id);
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
327
		foreach ($this->_known_mentions as $mention)
328
		{
329
			$list_options['list_menu']['links'][] = [
330
				'href' => getUrl('action', ['action' => 'mentions', 'type' => $mention] + (empty($this->_all) ? [] : ['all'])),
331
				'is_selected' => $this->_type === $mention,
332
				'label' => $txt['mentions_type_' . $mention]
333
			];
334
		}
335
336
		createList($list_options);
337
338
		$context['page_title'] = $txt['my_mentions'] . (empty($this->_page) ? '' : ' - ' . sprintf($txt['my_mentions_pages'], $this->_page));
339
		$context['breadcrumbs'][] = [
340
			'url' => getUrl('action', ['action' => 'mentions']),
341
			'name' => $txt['my_mentions'],
342
		];
343
344
		if (!empty($this->_type))
345
		{
346
			$context['breadcrumbs'][] = [
347
				'url' => getUrl('action', ['action' => 'mentions', 'type' => $this->_type]),
348
				'name' => $txt['mentions_type_' . $this->_type],
349
			];
350
		}
351
	}
352
353
	/**
354
	 * Builds the link back, so you return to the right list of mentions
355
	 */
356
	protected function _buildUrl(): void
357
	{
358
		$this->_all = $this->_req->getQuery('all') !== null;
359
		$this->_sort = in_array($this->_req->getQuery('sort', 'trim'), $this->_known_sorting, true) ? $this->_req->getQuery('sort', 'trim') : $this->_default_sort;
360
		$this->_type = in_array($this->_req->getQuery('type', 'trim'), $this->_known_mentions, true) ? $this->_req->getQuery('type', 'trim') : '';
361
		$this->_page = $this->_req->getQuery('start', 'trim', '');
362
363
		$this->_url_param = ($this->_all ? ';all' : '') . (empty($this->_type) ? '' : ';type=' . $this->_type) . ($this->_req->getQuery('start') !== null ? ';start=' . $this->_req->getQuery('start') : '');
364
	}
365
366
	/**
367
	 * Callback for createList(),
368
	 * Returns the number of mentions of $type that a member has
369
	 *
370
	 * @param bool $all : if true, counts all the mentions, otherwise only the unread
371
	 * @param string $type : the type of mention
372
	 *
373
	 * @return array|int
374
	 */
375
	public function list_getMentionCount($all, $type)
376
	{
377
		return countUserMentions($all, $type);
378
	}
379
380
	/**
381
	 * Did you read the mention? Then let's move it to the graveyard.
382
	 * Used by Events registered to the prepare_context event of the Display controller
383
	 */
384
	public function action_markread(): void
385
	{
386
		global $modSettings;
387
388
		checkSession('request');
389
390
		$this->_buildUrl();
391
392
		$id_mention = $this->_req->getQuery('item', 'intval', 0);
393
		$mentioning = new Mentioning(database(), $this->user, new DataValidator(), $modSettings['enabled_mentions']);
394
		$mentioning->updateStatus($id_mention, 'read');
395
	}
396
397
	/**
398
	 * Updating the status from the listing?
399
	 */
400
	public function action_updatestatus(): void
401
	{
402
		global $modSettings;
403
404
		checkSession('request');
405
406
		$mentioning = new Mentioning(database(), $this->user, new DataValidator(), $modSettings['enabled_mentions']);
407
408
		$id_mention = $this->_req->getQuery('item', 'intval', 0);
409
		$mark = $this->_req->getQuery('mark');
410
411
		$this->_buildUrl();
412
413
		switch ($mark)
414
		{
415
			case 'read':
416
			case 'unread':
417
			case 'delete':
418
				$mentioning->updateStatus($id_mention, $mark);
419
				break;
420
			case 'readall':
421
				Txt::load('Mentions');
422
				$mentions = $this->list_loadMentions((int) $this->_page, $this->_items_per_page, $this->_sort, $this->_all, $this->_type);
423
				$mentioning->markread(array_column($mentions, 'id_mention'));
424
				break;
425
		}
426
427
		redirectexit('action=mentions;sa=list' . $this->_url_param);
428
	}
429
430
	/**
431
	 * Callback for createList(),
432
	 * Returns the mentions of a give type (like/buddy/etc.) & (unread or all)
433
	 *
434
	 * @param int $start Start list number
435
	 * @param int $limit How many to show on a page
436
	 * @param string $sort The direction are we listing
437
	 * @param bool $all : If true, load all the mentions or type, otherwise only the unread
438
	 * @param string $type : The type of mention
439
	 *
440
	 * @event view_mentions
441
	 * @return array
442
	 */
443
	public function list_loadMentions($start, $limit, $sort, $all, $type): array
444
	{
445
		$totalMentions = countUserMentions($all, $type);
446
		$mentions = [];
447
		$round = 0;
448
		Txt::load('Mentions');
449
450
		// Register the view_mentions event
451
		$this->_registerEvents($type);
452
453
		while ($round < 2)
454
		{
455
			$possible_mentions = getUserMentions($start, $limit, $sort, $all, $type);
456
			$count_possible = count($possible_mentions);
457
458
			$this->_events->trigger('view_mentions', [$type, &$possible_mentions]);
459
460
			foreach ($possible_mentions as $mention)
461
			{
462
				if (count($mentions) < $limit)
463
				{
464
					$mentions[] = $mention;
465
				}
466
				else
467
				{
468
					break;
469
				}
470
			}
471
472
			$round++;
473
474
			// If nothing has been removed OR, there is not enough
475
			if (($totalMentions - $start < $limit) || count($mentions) !== $count_possible || count($mentions) === $limit)
476
			{
477
				break;
478
			}
479
480
			// Let's start a bit further into the list
481
			$start += $limit;
482
		}
483
484
		// Trigger an unread count update when needed
485
		if ($all !== false)
486
		{
487
			countUserMentions();
488
		}
489
490
		return $mentions;
491
	}
492
493
	/**
494
	 * Register the listeners for a mention type or for all the mentions.
495
	 *
496
	 * @param string|null $type Specific mention type
497
	 */
498
	protected function _registerEvents($type): void
499
	{
500
		if (!empty($type))
501
		{
502
			$to_register = ['\\ElkArte\\Mentions\\MentionType\\Event\\' . ucfirst($type)];
503
		}
504
		else
505
		{
506
			$to_register = array_map(static fn($name) => '\\ElkArte\\Mentions\\MentionType\\Event\\' . ucfirst($name), $this->_known_mentions);
507
		}
508
509
		$this->_registerEvent('view_mentions', 'view', $to_register);
510
	}
511
}
512