Issues (1693)

sources/ElkArte/Controller/Mentions.php (6 issues)

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 dev
11
 *
12
 */
13
14
namespace ElkArte\Controller;
15
16
use ElkArte\AbstractController;
17
use ElkArte\EventManager;
18
use ElkArte\Exceptions\Exception;
19
use ElkArte\Helper\DataValidator;
20
use ElkArte\Languages\Txt;
21
use ElkArte\Mentions\Mentioning;
22
use ElkArte\User;
23
24
/**
25
 * as liking a post, adding a buddy, @ calling a member in a post
26
 *
27
 * @package Mentions
28
 */
29
class Mentions extends AbstractController
30
{
31
	/** @var array Will hold all available mention types */
32
	protected $_known_mentions = [];
33
34
	/** @var string The type of the mention we are looking at (if empty means all of them) */
35
	protected $_type = '';
36
37
	/** @var string The url of the display mentions button (all, unread, etc) */
38
	protected $_url_param = '';
39
40
	/** @var int Used for pagination, keeps track of the current start point */
41
	protected $_page = 0;
42
43
	/** @var int Number of items per page */
44
	protected $_items_per_page = 20;
45
46
	/** @var string Default sorting column */
47
	protected $_default_sort = 'log_time';
48
49
	/** @var string User chosen sorting column */
50
	protected $_sort = '';
51
52
	/** @var string[] The sorting methods we know */
53
	protected $_known_sorting = [];
54
55
	/** @var bool Determine if we are looking only at unread mentions or any kind of */
56
	protected $_all = false;
57
58
	/**
59
	 * Good old constructor
60
	 *
61
	 * @param EventManager $eventManager
62
	 */
63
	public function __construct($eventManager)
64
	{
65
		$this->_known_sorting = ['id_member_from', 'type', 'log_time'];
66
67
		parent::__construct($eventManager);
68
	}
69
70
	/**
71
	 * Set up the data for the mention based on what was requested
72
	 * This function is called before the flow is redirected to action_index().
73
	 */
74
	public function pre_dispatch()
75
	{
76
		global $modSettings;
77
78
		// I'm not sure if this is needed, though better have it. :P
79
		if (empty($modSettings['mentions_enabled']))
80
		{
81
			throw new Exception('no_access', false);
82
		}
83
84
		require_once(SUBSDIR . '/Mentions.subs.php');
85
86
		$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...
87
	}
88
89
	/**
90
	 * The default action is to show the list of mentions
91
	 * This allows ?action=mention to be forwarded to action_list()
92
	 */
93
	public function action_index()
94
	{
95
		if ($this->_req->getQuery('sa') === 'fetch')
96
		{
97
			$this->action_fetch();
98
		}
99
		else
100
		{
101
			// Default action to execute
102
			$this->action_list();
103
		}
104
	}
105
106
	/**
107
	 * Fetches number of notifications and number of recently added ones for use
108
	 * in favicon and desktop notifications.  Triggered by URL request from
109
	 * ElkNotifications
110
	 *
111
	 * @todo probably should be placed somewhere else.
112
	 */
113
	public function action_fetch(): void
114
	{
115
		global $context, $txt, $modSettings;
116
117
		if (empty($modSettings['usernotif_favicon_enable']) && empty($modSettings['usernotif_desktop_enable']))
118
		{
119
			die();
0 ignored issues
show
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...
120
		}
121
122
		setJsonTemplate();
123
124
		require_once(SUBSDIR . '/Mentions.subs.php');
125
		require_once(SUBSDIR . '/PersonalMessage.subs.php');
126
127
		$lastsentmention = $this->_req->getQuery('lastsentmention', 'intval', 0);
128
		$lastsentpm = $this->_req->getQuery('lastsentpm', 'intval', 0);
129
130
		if (empty($lastsentmention) && !empty($_SESSION['notifications_lastsentmention']))
131
		{
132
			$lastsentmention = (int) $_SESSION['notifications_lastsentmention'];
133
		}
134
		if (empty($lastsentpm) && !empty($_SESSION['notifications_lastsentpm']))
135
		{
136
			$lastsentpm = (int) $_SESSION['notifications_lastsentpm'];
137
		}
138
139
		// We only know AJAX for this particular action
140
		$context['json_data'] = [
141
			'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...
142
			'lasttimepm' => getLastPMSentTime($this->user->id)
143
		];
144
145
		// Data to be supplied to Json template, consumed by favicon-notify.js
146
		if (!empty($modSettings['usernotif_favicon_enable']))
147
		{
148
			$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...
149
			$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...
150
		}
151
152
		// Data to be supplied to Push via desktop-notify.js, used to trigger desktop notifications
153
		// on new mentions (like/quote/etc) and PMs.
154
		if (!empty($modSettings['usernotif_desktop_enable']))
155
		{
156
			$new_mentions = getNewMentions($this->user->id, $lastsentmention);
157
			$new_pms = getNewPMs($this->user->id, $lastsentpm);
158
159
			$context['json_data']['desktop_notifications'] = [
160
				'new_from_last' => $new_mentions + $new_pms,
161
				'title' => sprintf($txt['forum_notification'], strip_tags(un_htmlspecialchars($context['forum_name']))),
162
				'link' => '/index.php?action=mentions',
163
			];
164
			$context['json_data']['desktop_notifications']['message'] = sprintf(
165
				$txt[$new_mentions + $new_pms === 0 ? 'unread_notifications' : 'new_from_last_notifications'],
166
				$context['json_data']['desktop_notifications']['new_from_last']);
167
		}
168
169
		$_SESSION['notifications_lastsentmention'] = $context['json_data']['lasttimemention'];
170
		$_SESSION['notifications_lastsentpm'] = $context['json_data']['lasttimepm'];
171
	}
172
173
	/**
174
	 * Display a list of mentions for the current user.
175
	 *
176
	 *  - Allows them to mark them read or unread
177
	 *  - Can sort the various forms of mentions, such as likes, buddies, quoted, etc.
178
	 */
179
	public function action_list(): void
180
	{
181
		global $context, $txt, $scripturl;
182
183
		// Only registered members can be mentioned
184
		is_not_guest();
185
186
		require_once(SUBSDIR . '/Mentions.subs.php');
187
		Txt::load('Mentions');
188
189
		$this->_buildUrl();
190
191
		$list_options = [
192
			'id' => 'list_mentions',
193
			'title' => empty($this->_all) ? $txt['my_unread_mentions'] : $txt['my_mentions'],
194
			'items_per_page' => $this->_items_per_page,
195
			'base_href' => $scripturl . '?action=mentions;sa=list' . $this->_url_param,
196
			'default_sort_col' => $this->_default_sort,
197
			'default_sort_dir' => 'default',
198
			'no_items_label' => $this->_all ? $txt['no_mentions_yet'] : $txt['no_new_mentions'],
199
			'get_items' => [
200
				'function' => fn(int $start, int $limit, string $sort, bool $all, string $type): array => $this->list_loadMentions($start, $limit, $sort, $all, $type),
201
				'params' => [
202
					$this->_all,
203
					$this->_type,
204
				],
205
			],
206
			'get_count' => [
207
				'function' => fn(bool $all, string $type) => $this->list_getMentionCount($all, $type),
208
				'params' => [
209
					$this->_all,
210
					$this->_type,
211
				],
212
			],
213
			'columns' => [
214
				'id_member_from' => [
215
					'header' => [
216
						'value' => $txt['mentions_from'],
217
					],
218
					'data' => [
219
						'function' => static function ($row) {
220
							global $settings;
221
222
							if (isset($settings['mentions']['mentioner_template']))
223
							{
224
								return str_replace(
225
									[
226
										'{avatar_img}',
227
										'{mem_url}',
228
										'{mem_name}',
229
									],
230
									[
231
										$row['avatar']['image'],
232
										empty($row['id_member_from']) ? '#' : getUrl('action', ['action' => 'profile', 'u' => $row['id_member_from']]),
233
										$row['mentioner'],
234
									],
235
									$settings['mentions']['mentioner_template']);
236
							}
237
238
							return '';
239
						},
240
					],
241
					'sort' => [
242
						'default' => 'mtn.id_member_from',
243
						'reverse' => 'mtn.id_member_from DESC',
244
					],
245
				],
246
				'type' => [
247
					'header' => [
248
						'value' => $txt['mentions_what'],
249
					],
250
					'data' => [
251
						'db' => 'message',
252
					],
253
					'sort' => [
254
						'default' => 'mtn.mention_type',
255
						'reverse' => 'mtn.mention_type DESC',
256
					],
257
				],
258
				'log_time' => [
259
					'header' => [
260
						'value' => $txt['mentions_when'],
261
						'class' => 'mention_log_time',
262
					],
263
					'data' => [
264
						'db' => 'log_time',
265
						'timeformat' => 'html_time',
266
						'class' => 'mention_log_time',
267
					],
268
					'sort' => [
269
						'default' => 'mtn.log_time DESC',
270
						'reverse' => 'mtn.log_time',
271
					],
272
				],
273
				'action' => [
274
					'header' => [
275
						'value' => $txt['mentions_action'],
276
						'class' => 'listaction grid8',
277
					],
278
					'data' => [
279
						'function' => static function ($row) {
280
							global $txt;
281
282
							$mark = empty($row['status']) ? 'read' : 'unread';
283
							$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;';
284
285
							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>';
286
						},
287
						'class' => 'listaction grid8',
288
					],
289
				],
290
			],
291
			'list_menu' => [
292
				'show_on' => 'top',
293
				'links' => [
294
					[
295
						'href' => getUrl('action', ['action' => 'mentions'] + (empty($this->_all) ? [] : ['all'])),
296
						'is_selected' => empty($this->_type),
297
						'label' => $txt['mentions_type_all']
298
					],
299
				],
300
			],
301
			'additional_rows' => [
302
				[
303
					'position' => 'above_column_headers',
304
					'class' => 'flow_flex_right',
305
					'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>',
306
				],
307
				[
308
					'class' => 'submitbutton',
309
					'position' => 'below_table_data',
310
					'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>',
311
				],
312
			],
313
		];
314
315
		// Build the available mention tabs
316
		$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...
317
		foreach ($this->_known_mentions as $mention)
318
		{
319
			$list_options['list_menu']['links'][] = [
320
				'href' => getUrl('action', ['action' => 'mentions', 'type' => $mention] + (empty($this->_all) ? [] : ['all'])),
321
				'is_selected' => $this->_type === $mention,
322
				'label' => $txt['mentions_type_' . $mention]
323
			];
324
		}
325
326
		createList($list_options);
327
328
		$context['page_title'] = $txt['my_mentions'] . (empty($this->_page) ? '' : ' - ' . sprintf($txt['my_mentions_pages'], $this->_page));
329
		$context['breadcrumbs'][] = [
330
			'url' => getUrl('action', ['action' => 'mentions']),
331
			'name' => $txt['my_mentions'],
332
		];
333
334
		if (!empty($this->_type))
335
		{
336
			$context['breadcrumbs'][] = [
337
				'url' => getUrl('action', ['action' => 'mentions', 'type' => $this->_type]),
338
				'name' => $txt['mentions_type_' . $this->_type],
339
			];
340
		}
341
	}
342
343
	/**
344
	 * Builds the link back, so you return to the right list of mentions
345
	 */
346
	protected function _buildUrl(): void
347
	{
348
		$this->_all = $this->_req->getQuery('all') !== null;
349
		$this->_sort = in_array($this->_req->getQuery('sort', 'trim'), $this->_known_sorting, true) ? $this->_req->getQuery('sort', 'trim') : $this->_default_sort;
350
		$this->_type = in_array($this->_req->getQuery('type', 'trim'), $this->_known_mentions, true) ? $this->_req->getQuery('type', 'trim') : '';
351
		$this->_page = $this->_req->getQuery('start', 'trim', '');
352
353
		$this->_url_param = ($this->_all ? ';all' : '') . (empty($this->_type) ? '' : ';type=' . $this->_type) . ($this->_req->getQuery('start') !== null ? ';start=' . $this->_req->getQuery('start') : '');
354
	}
355
356
	/**
357
	 * Callback for createList(),
358
	 * Returns the number of mentions of $type that a member has
359
	 *
360
	 * @param bool $all : if true counts all the mentions, otherwise only the unread
361
	 * @param string $type : the type of mention
362
	 *
363
	 * @return array|int
364
	 */
365
	public function list_getMentionCount($all, $type)
366
	{
367
		return countUserMentions($all, $type);
368
	}
369
370
	/**
371
	 * Did you read the mention? Then let's move it to the graveyard.
372
	 * Used by Events registered to the prepare_context event of the Display controller
373
	 */
374
	public function action_markread(): void
375
	{
376
		global $modSettings;
377
378
		checkSession('request');
379
380
		$this->_buildUrl();
381
382
		$id_mention = $this->_req->getQuery('item', 'intval', 0);
383
		$mentioning = new Mentioning(database(), $this->user, new DataValidator(), $modSettings['enabled_mentions']);
384
		$mentioning->updateStatus($id_mention, 'read');
385
	}
386
387
	/**
388
	 * Updating the status from the listing?
389
	 */
390
	public function action_updatestatus(): void
391
	{
392
		global $modSettings;
393
394
		checkSession('request');
395
396
		$mentioning = new Mentioning(database(), $this->user, new DataValidator(), $modSettings['enabled_mentions']);
397
398
		$id_mention = $this->_req->getQuery('item', 'intval', 0);
399
		$mark = $this->_req->getQuery('mark');
400
401
		$this->_buildUrl();
402
403
		switch ($mark)
404
		{
405
			case 'read':
406
			case 'unread':
407
			case 'delete':
408
				$mentioning->updateStatus($id_mention, $mark);
409
				break;
410
			case 'readall':
411
				Txt::load('Mentions');
412
				$mentions = $this->list_loadMentions((int) $this->_page, $this->_items_per_page, $this->_sort, $this->_all, $this->_type);
413
				$mentioning->markread(array_column($mentions, 'id_mention'));
414
				break;
415
		}
416
417
		redirectexit('action=mentions;sa=list' . $this->_url_param);
418
	}
419
420
	/**
421
	 * Callback for createList(),
422
	 * Returns the mentions of a give type (like/buddy/etc.) & (unread or all)
423
	 *
424
	 * @param int $start start list number
425
	 * @param int $limit how many to show on a page
426
	 * @param string $sort which direction are we showing this
427
	 * @param bool $all : if true load all the mentions or type, otherwise only the unread
428
	 * @param string $type : the type of mention
429
	 *
430
	 * @event view_mentions
431
	 * @return array
432
	 */
433
	public function list_loadMentions($start, $limit, $sort, $all, $type): array
434
	{
435
		$totalMentions = countUserMentions($all, $type);
436
		$mentions = [];
437
		$round = 0;
438
		Txt::load('Mentions');
439
440
		// Register the view_mentions event
441
		$this->_registerEvents($type);
442
443
		while ($round < 2)
444
		{
445
			$possible_mentions = getUserMentions($start, $limit, $sort, $all, $type);
446
			$count_possible = count($possible_mentions);
447
448
			$this->_events->trigger('view_mentions', [$type, &$possible_mentions]);
449
450
			foreach ($possible_mentions as $mention)
451
			{
452
				if (count($mentions) < $limit)
453
				{
454
					$mentions[] = $mention;
455
				}
456
				else
457
				{
458
					break;
459
				}
460
			}
461
462
			$round++;
463
464
			// If nothing has been removed OR there are not enough
465
			if (($totalMentions - $start < $limit) || count($mentions) !== $count_possible || count($mentions) === $limit)
466
			{
467
				break;
468
			}
469
470
			// Let's start a bit further into the list
471
			$start += $limit;
472
		}
473
474
		// Trigger an unread count update when needed
475
		if ($all !== false)
476
		{
477
			countUserMentions();
478
		}
479
480
		return $mentions;
481
	}
482
483
	/**
484
	 * Register the listeners for a mention type or for all the mentions.
485
	 *
486
	 * @param string|null $type Specific mention type
487
	 */
488
	protected function _registerEvents($type): void
489
	{
490
		if (!empty($type))
491
		{
492
			$to_register = ['\\ElkArte\\Mentions\\MentionType\\Event\\' . ucfirst($type)];
493
		}
494
		else
495
		{
496
			$to_register = array_map(static fn($name) => '\\ElkArte\\Mentions\\MentionType\\Event\\' . ucfirst($name), $this->_known_mentions);
497
		}
498
499
		$this->_registerEvent('view_mentions', 'view', $to_register);
500
	}
501
}
502