Passed
Pull Request — development (#3796)
by Spuds
08:08
created

changeStatus()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 42
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 18
nc 8
nop 4
dl 0
loc 42
ccs 0
cts 0
cp 0
crap 72
rs 8.4444
c 1
b 0
f 0
1
<?php
2
3
/**
4
 * Functions that deal with the database work involved with mentions
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
use ElkArte\User;
15
16
/**
17
 * Counts the number of mentions for a user.
18
 *
19
 * @param bool $all Specifies whether to count all mentions or only unread.
20
 * @param string $type Specifies the type of mention to count. Empty string to count all types.
21
 * @param int|null $id_member Specifies the ID of the member. If null, the current user's ID will be used.
22
 *
23
 * @return int|array The total count of mentions if $type is empty, otherwise an array with counts for each type.
24
 */
25
function countUserMentions($all = false, $type = '', $id_member = null)
26
{
27
	static $counts;
28
29
	$db = database();
30
	$id_member = $id_member === null ? User::$info->id : (int) $id_member;
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...
31 4
32
	if (isset($counts[$id_member][$type]))
33 4
	{
34 4
		return $counts[$id_member][$type];
35
	}
36 4
37
	$allTypes = getMentionTypes($id_member, $all === true ? 'system' : 'user');
38 2
	foreach ($allTypes as $thisType)
39
	{
40
		$counts[$id_member][$thisType] = 0;
41 2
	}
42
	$counts[$id_member]['total'] = 0;
43
44
	$db->fetchQuery('
45
		SELECT 
46
			mention_type, COUNT(*) AS cnt
47 2
		FROM {db_prefix}log_mentions as mtn
48 2
		WHERE mtn.id_member = {int:current_user}
49
			AND mtn.is_accessible = {int:is_accessible}
50
			AND mtn.status IN ({array_int:status})
51 2
			AND mtn.mention_type IN ({array_string:all_type})
52 2
		GROUP BY mtn.mention_type',
53 2
		[
54 2
			'current_user' => $id_member,
55
			'status' => $all ? [0, 1] : [0],
56
			'is_accessible' => 1,
57 2
			'all_type' => empty($allTypes) ? [$type] : $allTypes,
58 2
		]
59
	)->fetch_callback(function ($row) use (&$counts, $id_member) {
60
		$counts[$id_member][$row['mention_type']] = (int) $row['cnt'];
61 2
		$counts[$id_member]['total'] += $row['cnt'];
62
	});
63
64
	// Counts as maintenance! :P
65
	if ($all === false)
66
	{
67 2
		require_once(SUBSDIR . '/Members.subs.php');
68
		updateMemberData($id_member, ['mentions' => $counts[$id_member]['total']]);
69
	}
70
71
	return empty($type) ? $counts[$id_member]['total'] : $counts[$id_member][$type] ?? 0;
72
}
73
74
/**
75
 * Retrieve all the info to render the mentions page for the current user
76
 * callback for createList in action_list of \ElkArte\Controller\Mentions
77
 *
78
 * @param int $start Query starts sending results from here
79
 * @param int $limit Number of mentions returned
80
 * @param string $sort Sorting
81
 * @param bool $all if show all mentions or only unread ones
82
 * @param string[]|string $type : the type of the mention can be a string or an array of strings.
83
 *
84
 * @return array
85
 * @package Mentions
86
 *
87 2
 */
88
function getUserMentions($start, $limit, $sort, $all = false, $type = '')
89 2
{
90
	global $txt;
91 2
92
	$db = database();
93
94
	return $db->fetchQuery('
95
		SELECT
96
			mtn.id_mention, mtn.id_target, mtn.id_member_from, mtn.log_time, mtn.mention_type, mtn.status,
97
			m.subject, m.id_topic, m.id_board,
98
			COALESCE(mem.real_name, {string:guest_text}) as mentioner, mem.avatar, mem.email_address,
99
			COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type
100
		FROM {db_prefix}log_mentions AS mtn
101
			LEFT JOIN {db_prefix}messages AS m ON (mtn.id_target = m.id_msg)
102
			LEFT JOIN {db_prefix}members AS mem ON (mtn.id_member_from = mem.id_member)
103 2
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member)
104 2
		WHERE mtn.id_member = {int:current_user}
105 2
			AND mtn.is_accessible = {int:is_accessible}
106
			AND mtn.status IN ({array_int:status})' . (empty($type) ? '' : (is_array($type) ? '
107
			AND mtn.mention_type IN ({array_string:current_type})' : '
108
			AND mtn.mention_type = {string:current_type}')) . '
109 2
		ORDER BY {raw:sort}
110 2
		LIMIT {int:limit} OFFSET {int:start} ',
111 2
		array(
112 2
			'current_user' => 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...
113 2
			'current_type' => $type,
114 2
			'status' => $all ? array(0, 1) : array(0),
115 2
			'guest_text' => $txt['guest'],
116 2
			'is_accessible' => 1,
117
			'start' => $start,
118 2
			'limit' => $limit,
119
			'sort' => $sort,
120 2
		)
121
	)->fetch_callback(
122 2
		function ($row) {
123 2
			$row['avatar'] = determineAvatar($row);
124
125
			return $row;
126
		}
127
	);
128
}
129
130
/**
131
 * Completely remove from the database a set of mentions.
132
 *
133
 * Doesn't check permissions, access, anything. It just deletes everything.
134
 *
135
 * @param int[] $id_mentions the mention ids
136
 *
137
 * @return bool
138
 * @package Mentions
139
 *
140
 */
141
function removeMentions($id_mentions)
142
{
143
	$db = database();
144
145
	$request = $db->query('', '
146
		DELETE FROM {db_prefix}log_mentions
147
		WHERE id_mention IN ({array_int:id_mentions})',
148
		array(
149
			'id_mentions' => $id_mentions,
150
		)
151
	);
152
	$success = $request->affected_rows() !== 0;
153
154
	// Update the top level mentions count
155
	if ($success)
156
	{
157
		updateMentionMenuCount(null, 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...
158
	}
159
160
	return $success;
161
}
162
163
/**
164
 * Toggles a mention on/off
165
 *
166
 * - This is used to turn mentions on when a message is approved
167
 *
168
 * @param int[] $msgs array of messages that you want to toggle
169
 * @param bool $approved direction of the toggle read / unread
170
 * @package Mentions
171
 */
172
function toggleMentionsApproval($msgs, $approved)
173
{
174
	$db = database();
175
176
	$db->query('', '
177
		UPDATE {db_prefix}log_mentions
178
		SET 
179
			status = {int:status}
180
		WHERE id_target IN ({array_int:messages})',
181
		array(
182
			'messages' => $msgs,
183
			'status' => $approved ? 0 : 3,
184
		)
185
	);
186
187
	// Update the mentions menu count for the members that have this message
188
	$status = $approved ? 0 : 3;
189
	$db->fetchQuery('
190
		SELECT 
191
			id_member, status
192
		FROM {db_prefix}log_mentions
193
		WHERE id_target IN ({array_int:messages})',
194
		array(
195
			'messages' => $msgs,
196
		)
197
	)->fetch_callback(
198
		function ($row) use ($status) {
199
			updateMentionMenuCount($status, $row['id_member']);
200
		}
201
	);
202
}
203
204
/**
205
 * Toggles a mention visibility on/off
206
 *
207
 * - if off is restored to visible,
208
 * - if on is switched to invisible for all the users
209
 *
210
 * @param string $type type of the mention that you want to toggle
211
 * @param bool $enable if true enables the mentions, otherwise disables them
212
 * @package Mentions
213
 */
214
function toggleMentionsVisibility($type, $enable)
215
{
216
	$db = database();
217
218
	$db->query('', '
219
		UPDATE {db_prefix}log_mentions
220
		SET
221
			status = status ' . ($enable ? '-' : '+') . ' {int:toggle}
222
		WHERE mention_type = {string:type}
223
			AND status ' . ($enable ? '>=' : '<') . ' {int:toggle}
224
			AND is_accessible = 1',
225
		array(
226
			'type' => $type,
227
			'toggle' => 10,
228
		)
229
	);
230
231
	$db->query('', '
232
		UPDATE {db_prefix}log_mentions
233
		SET
234
			status = status ' . ($enable ? '+' : '-') . ' {int:toggle}
235
		WHERE mention_type = {string:type}
236
			AND status ' . ($enable ? '<' : '>=') . ' 0
237
			AND is_accessible = 0',
238
		array(
239
			'type' => $type,
240
			'toggle' => 10,
241
		)
242
	);
243
}
244
245
/**
246
 * Toggles a bunch of mentions accessibility on/off
247
 *
248
 * @param int[] $mentions an array of mention id
249
 * @param bool $access if true make the mentions accessible (if visible and other things), otherwise marks them as inaccessible
250
 * @package Mentions
251
 */
252
function toggleMentionsAccessibility($mentions, $access)
253
{
254
	$db = database();
255
256
	$db->query('', '
257
		UPDATE {db_prefix}log_mentions
258
		SET
259
			is_accessible = CASE WHEN is_accessible = 1 THEN 0 ELSE 1 END
260
		WHERE id_mention IN ({array_int:mentions})
261
			AND is_accessible ' . ($access ? '=' : '!=') . ' 0',
262
		array(
263
			'mentions' => $mentions,
264
		)
265
	);
266
}
267
268
/**
269
 * To validate access to read/unread/delete mentions
270
 *
271
 * - Called from the validation class via Mentioning.php
272
 *
273
 * @param string $field
274
 * @param array $input
275
 * @param string|null $validation_parameters
276
 *
277
 * @return array|void
278
 * @package Mentions
279
 *
280
 */
281
function validate_own_mention($field, $input, $validation_parameters = null)
282
{
283 2
	if (!isset($input[$field]))
284
	{
285 2
		return;
286
	}
287
288
	if (!findMemberMention($input[$field], 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...
289
	{
290
		return array(
291
			'field' => $field,
292
			'input' => $input[$field],
293
			'function' => __FUNCTION__,
294
			'param' => $validation_parameters
295
		);
296
	}
297
}
298
299
/**
300
 * Provided a mentions id and a member id, checks if the mentions belongs to that user
301
 *
302
 * @param int $id_mention the id of an existing mention
303
 * @param int $id_member id of a member
304
 * @return bool true if the mention belongs to the member, false otherwise
305
 * @package Mentions
306
 */
307
function findMemberMention($id_mention, $id_member)
308
{
309
	$db = database();
310 2
311
	$request = $db->query('', '
312 2
		SELECT 
313
			id_mention
314
		FROM {db_prefix}log_mentions
315
		WHERE id_mention = {int:id_mention}
316
			AND id_member = {int:id_member}
317
		LIMIT 1',
318
		array(
319
			'id_mention' => $id_mention,
320 2
			'id_member' => $id_member,
321 2
		)
322
	);
323
	$return = $request->num_rows();
324 2
	$request->free_result();
325 2
326
	return !empty($return);
327 2
}
328
329
/**
330
 * Updates the mention count as a result of an action, read, new, delete, etc
331
 *
332
 * @param int|null $status
333
 * @param int $member_id
334
 * @package Mentions
335
 */
336
function updateMentionMenuCount($status, $member_id)
337
{
338
	require_once(SUBSDIR . '/Members.subs.php');
339
340
	// If its new add to our menu count
341
	if ($status === 0)
342
	{
343
		updateMemberData($member_id, array('mentions' => '+'));
344
	}
345
	// Mark as read we decrease the count
346
	elseif ($status === 1)
347
	{
348
		updateMemberData($member_id, array('mentions' => '-'));
349
	}
350
	// Deleting or un-approving may have been read or not, so a count is required
351
	else
352
	{
353
		countUserMentions(false, '', $member_id);
354
	}
355
}
356
357
/**
358
 * Retrieves the time the last notification of a certain member was added.
359
 *
360
 * @param int $id_member
361
 * @return int A timestamp (log_time)
362
 * @package Mentions
363
 */
364
function getTimeLastMention($id_member)
365
{
366
	$db = database();
367
368
	$request = $db->fetchQuery('
369
		SELECT 
370
			log_time
371
		FROM {db_prefix}log_mentions
372
		WHERE status = {int:status}
373
			AND id_member = {int:member}
374
		ORDER BY id_mention DESC
375
		LIMIT 1',
376
		array(
377
			'status' => 0,
378
			'member' => $id_member
379
		)
380
	);
381
	list ($log_time) = $request->fetch_row();
382
	$request->free_result();
383
384
	return empty($log_time) ? 0 : $log_time;
385
}
386
387
/**
388
 * Counts all the notifications received by a certain member after a certain time.
389
 *
390
 * @param int $id_member
391
 * @param int $timestamp
392
 * @return int Number of new mentions
393
 * @package Mentions
394
 */
395
function getNewMentions($id_member, $timestamp)
396
{
397
	$db = database();
398
399
	if (empty($timestamp))
400
	{
401
		$result = $db->fetchQuery('
402
			SELECT 
403
				COUNT(*) AS c
404
			FROM {db_prefix}log_mentions
405
			WHERE status = {int:status}
406
				AND id_member = {int:member}
407
				AND is_accessible = {int:has_access}',
408
			array(
409
				'status' => 0,
410
				'has_access' => 1,
411
				'member' => $id_member
412
			)
413
		)->fetch_assoc();
414
	}
415
	else
416
	{
417
		$result = $db->fetchQuery('
418
			SELECT 
419
				COUNT(*) AS c
420
			FROM {db_prefix}log_mentions
421
			WHERE status = {int:status}
422
				AND log_time > {int:last_seen}
423
				AND id_member = {int:member}
424
				AND is_accessible = {int:has_access}',
425
			array(
426
				'status' => 0,
427
				'has_access' => 1,
428
				'last_seen' => $timestamp,
429
				'member' => $id_member
430
			)
431
		)->fetch_assoc();
432
	}
433
434
	return $result['c'];
435
}
436
437
/**
438
 * Get the available mention types for a user.
439
 *
440
 * @param int|null $user The user ID. If null, User::$info->id will be used.
441
 * @param string $type The type of mentions.  "user" will return only those that the user has enabled and set
442
 * as on site notification.
443
 *
444
 * By default, will filter out notification types with a method set to none, e.g. the user has disabled that
445
 * type of mention.  Use type "system" to return everything, or type "user" to return only those
446
 * that they want on-site notifications.
447
 *
448
 * @return array The available mention types.
449
 */
450
function getMentionTypes($user, $type = 'user')
451
{
452
	require_once(SUBSDIR . '/Notification.subs.php');
453
454
	$user = $user ?? 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...
455
456
	$enabled = getEnabledNotifications();
457
458
	if ($type !== 'user')
459
	{
460
		sort($enabled);
461
		return $enabled;
462
	}
463
464
	$userAllEnabled = getUsersNotificationsPreferences($enabled, $user);
465
466
	// Drop ones they do not have enabled (primarily used to drop watchedtopic / watched board)
467
	foreach ($enabled as $key => $notificationType)
468
	{
469
		if (!isset($userAllEnabled[$user][$notificationType]))
470
		{
471
			unset($enabled[$key]);
472
		}
473
	}
474
475
	// Filter the remaining as requested
476
	foreach ($userAllEnabled[$user] as $notificationType => $allowedMethods)
477
	{
478
		if (!in_array('notification', $allowedMethods, true))
479
		{
480
			$key = array_search($notificationType, $enabled, true);
481
			if ($key !== false)
482
			{
483
				unset($enabled[$key]);
484
			}
485
		}
486
	}
487
488
	sort($enabled);
489
	return $enabled;
490
}
491
492
/**
493
 * Marks a set of notifications as read.
494
 *
495
 * Intended to be called when viewing a topic page.
496
 *
497
 * @param array $messages
498
 */
499
function markNotificationsRead($messages)
500
{
501
	// Guests can't mark notifications
502
	if (User::$info->is_guest || empty($messages))
0 ignored issues
show
Bug Best Practice introduced by
The property is_guest does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
503
	{
504
		return;
505
	}
506
507
	// These are the types associated with messages (where the id_target is a msg_id)
508
	$mentionTypes = ['mentionmem', 'likemsg', 'rlikemsg', 'quotedmem', 'watchedtopic', 'watchedboard'];
509
	$messages = is_array($messages) ? $messages : [$messages];
0 ignored issues
show
introduced by
The condition is_array($messages) is always true.
Loading history...
510
	$changes = [];
511
512
	// Find unread notifications for this group of messages for this member
513
	$db = database();
514
	$db->fetchQuery('
515
		SELECT 
516
			id_mention
517
		FROM {db_prefix}log_mentions
518
		WHERE status = {int:status}
519
			AND id_member = {int:member}
520
			AND id_target IN ({array_int:targets})
521
			AND mention_type IN ({array_string:mention_types})',
522
		[
523
			'status' => 0,
524
			'member' => 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...
525
			'targets' => is_array($messages) ? $messages : [$messages],
0 ignored issues
show
introduced by
The condition is_array($messages) is always true.
Loading history...
526
			'mention_types' => $mentionTypes,
527
		]
528
	)->fetch_callback(
529
	function ($row) use (&$changes) {
530
		$changes[] = (int) $row['id_mention'];
531
	});
532
533
	if (!empty($changes))
534
	{
535
		changeStatus(array_unique($changes), User::$info->id);
536
	}
537
}
538
539
/**
540
 * Change the status of mentions
541
 *
542
 * Updates the status of mentions in the database. Also updates the mentions count for the member.
543
 *
544
 *  - Can be used to mark as read, new, deleted, etc a group of mention id's
545
 *  - Note that delete is a "soft-delete" because otherwise anyway we have to remember
546
 *  - When a user was already mentioned for a certain message (e.g. in case of editing)
547
 *
548
 * @param int|array $id_mentions The id(s) of the mentions to update
549
 * @param int $member_id The id of the member
550
 * @param int $status The new status for the mentions (default: 1)
551
 * @param bool $update Whether to update the mentions count (default: true)
552
 *
553
 * @return bool Returns true if the update was successful, false otherwise
554
 */
555
function changeStatus($id_mentions, $member_id, $status = 1, $update = true)
556
{
557
	$db = database();
558
559
	$id_mentions = is_array($id_mentions) ? $id_mentions : [$id_mentions];
560
	$status = $status ?? 1;
561
562
	$success = $db->query('', '
563
		UPDATE {db_prefix}log_mentions
564
		SET status = {int:status}
565
		WHERE id_mention IN ({array_int:id_mentions})',
566
		[
567
			'id_mentions' => $id_mentions,
568
			'status' => $status,
569
		]
570
	)->affected_rows() !== 0;
571
572
	// Update the mentions count
573
	if ($success && $update)
574
	{
575
		$number = count($id_mentions);
576
		require_once(SUBSDIR . '/Members.subs.php');
577
578
		// Increase the count by 1
579
		if ($number === 1 && $status === 0)
580
		{
581
			updateMemberData($member_id, ['mentions' => '+']);
582
			return true;
583
		}
584
585
		// Mark as read we decrease the count by 1
586
		if ($number === 1 && $status === 1)
587
		{
588
			updateMemberData($member_id, ['mentions' => '-']);
589
			return true;
590
		}
591
592
		// A full recount is required
593
		countUserMentions(false, '', $member_id);
594
	}
595
596
	return $success;
597
}
598