Issues (1686)

sources/subs/Notification.subs.php (2 issues)

1
<?php
2
3
/**
4
 * Functions to support the sending of notifications (new posts, replys, topics)
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\Helper\TokenHash;
15
use ElkArte\Helper\Util;
16
use ElkArte\Languages\Loader;
17
use ElkArte\Notifications\Notifications;
18
use ElkArte\Notifications\PostNotifications;
19
use ElkArte\User;
20
21
/**
22
 * Sends a notification to members who have elected to receive emails
23
 *
24
 * @param int[]|int $topics - represents the topics the action is happening to.
25
 * @param string $type - can be any of reply, sticky, lock, unlock, remove,
26
 *                       move, merge, and split.  An appropriate message will be sent for each.
27
 * @param int[]|int $exclude = array() - members in exclude array will not be
28
 *                             processed for the topic with the same key.
29
 * @param int[]|int $members_only = array() - are the only ones that will be sent the notification if they have it on.
30
 * @param array $pbe = array() - array containing user_info if this is being run as a result of an email posting
31
 */
32
function sendNotifications($topics, $type, $exclude = [], $members_only = [], $pbe = [])
33
{
34
	// Simply redirects to PostNotifications class sendNotifications method
35 2
	// @todo I could find no use of $exclude in any calls to this function, that is why its missing :/
36
	(new PostNotifications())->sendNotifications($topics, $type, $members_only, $pbe);
37 2
}
38
39
/**
40 2
 * Notifies members who have requested notification for new topics posted on a board of said posts.
41 2
 *
42
 * @param array $topicData
43
 */
44 2
function sendBoardNotifications(&$topicData)
45
{
46
	// Redirects to PostNotifications class sendBoardNotifications method
47
	(new PostNotifications())->sendBoardNotifications($topicData);
48
}
49
50 2
/**
51
 * A special function for handling the hell which is sending approval notifications.
52 2
 *
53
 * @param array $topicData
54
 */
55
function sendApprovalNotifications(&$topicData)
56 2
{
57 2
	(new PostNotifications())->sendApprovalNotifications($topicData);
58
}
59
60
/**
61
 * This simple function gets a list of all administrators and emails them
62
 * to let them know a new member has joined.
63 2
 * Called by registerMember() function in subs/Members.subs.php.
64 2
 * Email is sent to all groups that have the moderate_forum permission.
65
 * The language set by each member is being used (if available).
66
 *
67 2
 * @param string $type types supported are 'approval', 'activation', and 'standard'.
68 2
 * @param int $memberID
69 2
 * @param string|null $member_name = null
70
 * @uses the Login language file.
71
 */
72
function sendAdminNotifications($type, $memberID, $member_name = null)
73
{
74
	global $modSettings, $language;
75
76
	$db = database();
77
78
	// If the setting isn't enabled then just exit.
79
	if (empty($modSettings['notify_new_registration']))
80
	{
81 2
		return;
82 2
	}
83
84 2
	// Needed to notify admins, or anyone
85
	require_once(SUBSDIR . '/Mail.subs.php');
86
87 2
	if ($member_name === null)
88
	{
89
		require_once(SUBSDIR . '/Members.subs.php');
90 2
91
		// Get the new user's name....
92
		$member_info = getBasicMemberData($memberID);
93 2
		$member_name = $member_info['real_name'];
94 2
	}
95 2
96 2
	// All membergroups who can approve members.
97 2
	$groups = [];
98 2
	$db->fetchQuery('
99 2
		SELECT 
100 2
			id_group
101 2
		FROM {db_prefix}permissions
102 2
		WHERE permission = {string:moderate_forum}
103
			AND add_deny = {int:add_deny}
104 2
			AND id_group != {int:id_group}',
105
		[
106
			'add_deny' => 1,
107
			'id_group' => 0,
108 2
			'moderate_forum' => 'moderate_forum',
109
		]
110 2
	)->fetch_callback(
111
		function ($row) use (&$groups) {
112 1
			$groups[] = $row['id_group'];
113
		}
114
	);
115
116
	// Add administrators too...
117 2
	$groups[] = 1;
118
	$groups = array_unique($groups);
119
120
	// Get a list of all members who have ability to approve accounts - these are the people who we inform.
121
	$current_language = User::$info->language;
0 ignored issues
show
Bug Best Practice introduced by
The property language does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
122 2
	$db->query('', '
123
		SELECT 
124
			id_member, lngfile, email_address
125 2
		FROM {db_prefix}members
126
		WHERE (id_group IN ({array_int:group_list}) OR FIND_IN_SET({raw:group_array_implode}, additional_groups) != 0)
127
			AND notify_types != {int:notify_types}
128
		ORDER BY lngfile',
129
		[
130
			'group_list' => $groups,
131 2
			'notify_types' => 4,
132 2
			'group_array_implode' => implode(', additional_groups) != 0 OR FIND_IN_SET(', $groups),
133
		]
134 2
	)->fetch_callback(
135
		function ($row) use ($type, $member_name, $memberID, $language) {
136
			global $scripturl, $modSettings;
137 2
138 2
			$replacements = [
139
				'USERNAME' => $member_name,
140 2
				'PROFILELINK' => $scripturl . '?action=profile;u=' . $memberID
141
			];
142 1
			$emailtype = 'admin_notify';
143 2
144
			// If they need to be approved add more info...
145
			if ($type === 'approval')
146
			{
147 2
				$replacements['APPROVALLINK'] = $scripturl . '?action=admin;area=viewmembers;sa=browse;type=approve';
148
				$emailtype .= '_approval';
149
			}
150 2
151
			$emaildata = loadEmailTemplate($emailtype, $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']);
152
153
			// And do the actual sending...
154
			sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, null, false, 0);
155
		}
156
	);
157
158
	if (isset($current_language) && $current_language !== User::$info->language)
159
	{
160
		$lang_loader = new Loader(null, $txt, database());
161
		$lang_loader->load('Login', false);
162
	}
163
}
164
165
/**
166
 * Checks if a user has the correct access to get notifications
167
 * - validates they have proper group access to a board
168
 * - if using the maillist, checks if they should get a reply-able message
169
 *     - not muted
170
 *     - has postby_email permission on the board
171
 *
172
 * Returns false if they do not have the proper group access to a board
173
 * Sets email_perm to false if they should not get a reply-able message
174
 *
175
 * @param array $row
176
 * @param bool $maillist
177
 * @param bool $email_perm
178
 *
179
 * @return bool
180
 */
181
function validateNotificationAccess($row, $maillist, &$email_perm = true)
182
{
183
	global $modSettings;
184
185
	static $board_profile = [];
186
187
	$member_in_groups = array_merge([$row['id_group'], $row['id_post_group']], (empty($row['additional_groups']) ? [] : explode(',', $row['additional_groups'])));
188
	$board_allowed_groups = explode(',', $row['member_groups']);
189
190
	// Standardize the data
191
	$member_in_groups = array_map('intval', $member_in_groups);
192
	$board_allowed_groups = array_map('intval', $board_allowed_groups);
193
194
	// No need to check for you ;)
195
	if (!in_array(1, $member_in_groups, true))
196
	{
197
		$email_perm = true;
198
199
		return true;
200
	}
201
202
	// They do have access to this board?
203
	if (count(array_intersect($member_in_groups, $board_allowed_groups)) === 0)
204
	{
205
		$email_perm = false;
206
207
		return false;
208
	}
209
210
	// If using maillist, see if they should get a reply-able message
211
	if ($email_perm && $maillist)
212
	{
213
		// Perhaps they don't require or deserve a security key in the message
214
		if (!empty($modSettings['postmod_active'])
215
			&& !empty($modSettings['warning_mute']) && $modSettings['warning_mute'] <= $row['warning'])
216
		{
217
			$email_perm = false;
218
219
			return false;
220
		}
221
222
		if (!isset($board_profile[$row['id_board']]))
223
		{
224
			require_once(SUBSDIR . '/Members.subs.php');
225
			$board_profile[$row['id_board']] = groupsAllowedTo('postby_email', $row['id_board']);
226
		}
227
228
		// In a group that has email posting permissions on this board
229
		if (count(array_intersect($board_profile[$row['id_board']]['allowed'], $member_in_groups)) === 0)
230
		{
231
			$email_perm = false;
232
233
			return false;
234
		}
235
236
		// And not specifically denied?
237
		if ($email_perm && !empty($modSettings['permission_enable_deny'])
238
			&& count(array_intersect($member_in_groups, $board_profile[$row['id_board']]['denied'])) !== 0)
239
		{
240
			$email_perm = false;
241
		}
242
	}
243
244
	return $email_perm;
245
}
246
247
/**
248
 * Returns the enabled notifications from the modSettings.
249
 *
250
 * @return array
251
 * @global array $modSettings
252
 *
253
 */
254
function getEnabledNotifications()
255
{
256
	global $modSettings;
257
258
	if (empty($modSettings['enabled_mentions']))
259
	{
260
		return [];
261
	}
262
263
	$enabled = array_filter(array_unique(explode(',', $modSettings['enabled_mentions'])));
264
	sort($enabled);
265
266
	return $enabled;
267
}
268
269
/**
270
 * Queries the database for notification preferences of a set of members.
271
 *
272
 *
273
 * @param string[]|string $notification_types
274
 * @param int[]|int $members
275
 *
276
 * @return array
277
 */
278
function getUsersNotificationsPreferences($notification_types, $members)
279
{
280
	$db = database();
281
282
	$notification_types = (array) $notification_types;
283
	$query_members = (array) $members;
284
	$defaults = [];
285
	foreach (getConfiguredNotificationMethods('*') as $notification => $methods)
286
	{
287 2
		$return = [];
288
		foreach ($methods as $k => $level)
289
		{
290
			if ($level == Notifications::DEFAULT_LEVEL)
291
			{
292
				$return[] = $k;
293
			}
294
		}
295
		$defaults[$notification] = $return;
296
	}
297
298
	$results = [];
299
	$db->fetchQuery('
300
		SELECT 
301
			id_member, notification_type, mention_type
302 2
		FROM {db_prefix}notifications_pref
303
		WHERE id_member IN ({array_int:members_to})
304
			AND mention_type IN ({array_string:mention_types})',
305 2
		[
306 2
			'members_to' => $query_members,
307 2
			'mention_types' => $notification_types,
308 2
		]
309 2
	)->fetch_callback(
310 2
		function ($row) use (&$results) {
311
			if (!isset($results[$row['id_member']]))
312
			{
313 2
				$results[$row['id_member']] = [];
314
			}
315
316
			$results[$row['id_member']][$row['mention_type']] = json_decode($row['notification_type']);
317
		}
318
	);
319
320
	// Set the defaults
321
	foreach ($query_members as $member)
322
	{
323
		foreach ($notification_types as $type)
324
		{
325
			if (empty($results[$member]) && !empty($defaults[$type]))
326
			{
327
				if (!isset($results[$member]))
328
				{
329
					$results[$member] = [];
330
				}
331
332
				if (!isset($results[$member][$type]))
333
				{
334
					$results[$member][$type] = [];
335
				}
336
337
				$results[$member][$type] = $defaults[$type];
338
			}
339
		}
340
	}
341
342
	return $results;
343
}
344
345
/**
346
 * Saves into the database the notification preferences of a certain member.
347
 *
348
 * @param int $member The member id
349
 * @param array $notification_data The array of notifications ('type' => ['level'])
350
 */
351
function saveUserNotificationsPreferences($member, $notification_data)
352
{
353
	$db = database();
354
355
	$inserts = [];
356
357
	// First drop the existing settings
358
	$db->query('', '
359
		DELETE FROM {db_prefix}notifications_pref
360
		WHERE id_member = {int:member}
361
			AND mention_type IN ({array_string:mention_types})',
362
		[
363
			'member' => $member,
364
			'mention_types' => array_keys($notification_data),
365
		]
366
	);
367
368
	foreach ($notification_data as $type => $level)
369
	{
370
		// used to skip values that are here only to remove the default
371
		if (empty($level))
372
		{
373
			continue;
374
		}
375
376
		// If they have any site notifications enabled, set a flag to request Push.Permissions
377
		if (in_array('notification', $level))
378
		{
379
			$_SESSION['push_enabled'] = true;
380
		}
381
382
		$inserts[] = [
383
			$member,
384
			$type,
385
			json_encode($level),
386
		];
387
	}
388
389
	if (empty($inserts))
390
	{
391
		return;
392
	}
393
394 2
	$db->insert('',
395
		'{db_prefix}notifications_pref',
396 2
		[
397
			'id_member' => 'int',
398
			'mention_type' => 'string-12',
399
			'notification_type' => 'string',
400
		],
401
		$inserts,
402 2
		['id_member', 'mention_type']
403
	);
404
}
405
406
/**
407
 * From the list of all possible notification methods available, only those
408
 * enabled are returned.
409
 *
410
 * @param string[] $possible_methods The array of notifications ('type' => 'level')
411
 * @param string $type The type of notification (mentionmem, likemsg, etc.)
412
 *
413
 * @return array
414
 */
415
function filterNotificationMethods($possible_methods, $type)
416
{
417
	$unserialized = getConfiguredNotificationMethods($type);
418
419 2
	if (empty($unserialized))
420
	{
421
		return [];
422
	}
423
424
	$allowed = [];
425
	foreach ($possible_methods as $class)
426
	{
427
		$class = strtolower($class);
428
		if (!empty($unserialized[$class]))
429
		{
430
			$allowed[] = $class;
431
		}
432
	}
433
434
	return $allowed;
435
}
436
437
/**
438
 * Returns all the enabled methods of notification for a specific
439
 * type of notification.
440 2
 *
441
 * @param string $type The type of notification (mentionmem, likemsg, etc.)
442
 *
443
 * @return array
444
 */
445
function getConfiguredNotificationMethods($type = '*')
446
{
447
	global $modSettings;
448
449
	$unserialized = Util::unserialize($modSettings['notification_methods']);
450
451
	if (isset($unserialized[$type]))
452
	{
453
		return $unserialized[$type];
454 2
	}
455
456 2
	if ($type === '*')
457
	{
458 2
		return $unserialized;
459 2
	}
460
461
	return [];
462 2
}
463
464 2
/**
465
 * Creates a hash code using the notification details and our secret key
466
 *
467
 * - If no salt (secret key) has been set, creates a random one and saves it
468 2
 * in modSettings for future use
469
 *
470
 * @param string $memID member id
471 2
 * @param string $memEmail member email address
472 2
 * @param string $memSalt member salt
473
 * @param string $area area to unsubscribe
474 2
 * @param string $extra area specific data such as topic id or liked msg
475
 * @return string the token for the unsubscribe link
476 2
 */
477
function getNotifierToken($memID, $memEmail, $memSalt, $area, $extra)
478
{
479
	global $modSettings;
480
481
	// We need a site salt to keep things moving
482
	if (empty($modSettings['unsubscribe_site_salt']))
483
	{
484
		$tokenizer = new TokenHash();
485 2
486
		// Extra digits of salt
487
		$unsubscribe_site_salt = $tokenizer->generate_hash(22);
488
		updateSettings(['unsubscribe_site_salt' => $unsubscribe_site_salt]);
489 2
	}
490 2
491
	// Generate a code suitable for Blowfish crypt.
492
	$blowfish_salt = '$2a$07$' . $memSalt . $modSettings['unsubscribe_site_salt'] . '$';
493
	$now = time();
494
	$hash = crypt($area . $extra . $now . $memEmail . $memSalt, $blowfish_salt);
495
496 2
	// Return just the hash, drop the salt
497 2
	return urlencode(implode('_',
498
		[
499
			$memID,
500 2
			substr($hash, 28),
501 2
			$area,
502
			$extra,
503 2
			$now
504
		]
505 2
	));
506 2
}
507
508 2
/**
509
 * Validates a hash code using the notification details and our secret key
510 1
 *
511 2
 * - If no site salt (secret key) has been set, simply fails
512
 *
513
 * @param string $memEmail member email address
514
 * @param string $memSalt member salt
515 2
 * @param string $area data to validate = area + extra + time from link
516
 * @param string $hash the hash from the link
517
 * @return bool
518
 */
519
function validateNotifierToken($memEmail, $memSalt, $area, $hash)
520
{
521
	global $modSettings;
522
523
	if (empty($modSettings['unsubscribe_site_salt']))
524
	{
525
		return false;
526
	}
527
528
	$blowfish_salt = '$2a$07$' . $memSalt . $modSettings['unsubscribe_site_salt']. '$';
529
	$expected = substr($blowfish_salt, 0, 28) . $hash;
530 2
	$check = crypt($area . $memEmail . $memSalt, $blowfish_salt);
531 2
532 2
	// Basic safe compare
533 2
	return hash_equals($expected, $check);
534 2
}
535
536
/**
537
 * Fetches a set of data for a topic that will then be used in creating/building notification emails.
538 2
 *
539
 * @param int[] $topics
540
 * @param string $type
541
 * @return array[] A board array and the topic info array.  Board array used to search for board subscriptions.
542
 */
543
function getTopicInfos($topics, $type)
544
{
545
	$db = database();
546
547
	$topicData = [];
548
	$boards_index = [];
549
550
	$db->fetchQuery('
551
		SELECT 
552
			mf.subject, ml.body, ml.id_member, t.id_last_msg, t.id_topic, t.id_board, t.id_member_started,
553
			mem.signature, COALESCE(mem.real_name, ml.poster_name) AS poster_name, COUNT(a.id_attach) as num_attach
554
		FROM {db_prefix}topics AS t
555
			INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
556
			INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)
557
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = ml.id_member)
558
			LEFT JOIN {db_prefix}attachments AS a ON(a.attachment_type = {int:attachment_type} AND a.id_msg = t.id_last_msg)
559
		WHERE t.id_topic IN ({array_int:topic_list})
560
		GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9',
561
		[
562
			'topic_list' => $topics,
563
			'attachment_type' => 0,
564
		]
565
	)->fetch_callback(
566
		function ($row) use (&$topicData, &$boards_index, $type) {
567
			// all the boards for these topics, used to find all the members to be notified
568
			$boards_index[] = $row['id_board'];
569
570
			// And the information we are going to tell them about
571
			$topicData[$row['id_topic']] = [
572
				'subject' => $row['subject'],
573
				'body' => $row['body'],
574
				'last_id' => (int) $row['id_last_msg'],
575
				'topic' => (int) $row['id_topic'],
576
				'board' => (int) $row['id_board'],
577
				'id_member_started' => (int) $row['id_member_started'],
578
				'name' => $type === 'reply' ? $row['poster_name'] : User::$info->name,
0 ignored issues
show
Bug Best Practice introduced by
The property name does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
579
				'exclude' => '',
580
				'signature' => $row['signature'],
581
				'attachments' => (int) $row['num_attach'],
582
			];
583
		}
584
	);
585
586
	return [$boards_index, $topicData];
587
}
588
589
/**
590
 * Keeps the log_digest up to date for members who want weekly/daily updates
591
 *
592
 * @param array $digest_insert
593
 * @return void
594
 */
595
function insertLogDigestQueue($digest_insert)
596
{
597
	$db = database();
598
599
	$db->insert('',
600
		'{db_prefix}log_digest',
601
		[
602
			'id_topic' => 'int', 'id_msg' => 'int', 'note_type' => 'string', 'exclude' => 'int',
603
		],
604
		$digest_insert,
605
		[]
606
	);
607
}
608
609
/**
610
 * Find the members with *board* notifications on.
611
 *
612
 * What it does:
613
 * Finds board notifications that meet:
614
 * 	- Member has watch notifications on for the board
615 2
 *  - The notification type of reply and/or moderation notices
616
 *  - Notification regularity of instantly or first unread
617 2
 *  - Member is activated
618
 *  - Member has access to the board where the topic resides
619
 *
620 2
 * @param int $user_id the id of the poster as they do not get a notification of their own post
621
 * @param int[] $boards_index list of boards to check
622
 * @param string $type type of activity, like reply or lock
623
 * @param int|int[] $members_only returns data only for a list of members
624
 * @param string $regularity type of notification 'email' or 'onsite'
625
 * @return array
626
 */
627 2
function fetchBoardNotifications($user_id, $boards_index, $type, $members_only, $regularity = 'email')
628 2
{
629 2
	$db = database();
630
631
	$boardNotifyData = [];
632 2
633
	$notify_types = $type === 'reply' ? 'mem.notify_types < 4' : 'mem.notify_types < 3';
634
	$notify_regularity = $regularity === 'email' ? 'mem.notify_regularity < 2' : 'mem.notify_regularity = 4';
635
636
	// Find the members (excluding the poster) that have board notification enabled.
637
	$db->fetchQuery('
638
		SELECT
639
			mem.id_member, mem.email_address, mem.notify_regularity, mem.notify_types, mem.notify_send_body, 
640
			mem.lngfile, mem.warning, mem.id_group, mem.additional_groups, mem.id_post_group, mem.password_salt,
641
			b.member_groups, b.name, b.id_profile,
642
			ln.id_board, ln.sent
643
		FROM {db_prefix}log_notify AS ln
644
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = ln.id_board)
645
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = ln.id_member)
646
		WHERE ln.id_board IN ({array_int:board_list})
647
			AND {raw:notify_types}
648
			AND {raw:notify_regularity}
649
			AND mem.is_activated = {int:is_activated}
650
			AND ln.id_member != {int:current_member}' .
651
		(empty($members_only) ? '' : ' AND ln.id_member IN ({array_int:members_only})') . '
652
		ORDER BY mem.lngfile',
653
		[
654
			'current_member' => $user_id,
655
			'board_list' => $boards_index,
656
			'notify_types' => $notify_types,
657
			'notify_regularity' => $notify_regularity,
658
			'is_activated' => 1,
659
			'members_only' => is_array($members_only) ? $members_only : [$members_only],
660
		]
661
	)->fetch_callback(
662
		function ($row) use (&$boardNotifyData) {
663
			// The board subscription information for the member
664
			$clean = [
665
				'id_member' => (int) $row['id_member'],
666
				'email_address' => $row['email_address'],
667
				'notify_regularity' => (int) $row['notify_regularity'],
668
				'notify_types' => (int) $row['notify_types'],
669
				'notify_send_body' => (int) $row['notify_send_body'],
670
				'lngfile' => $row['lngfile'],
671
				'warning' => $row['warning'],
672
				'id_group' => (int) $row['id_group'],
673
				'additional_groups' => $row['additional_groups'],
674
				'id_post_group' => (int) $row['id_post_group'],
675
				'password_salt' => $row['password_salt'],
676
				'member_groups' => $row['member_groups'],
677
				'board_name' => $row['name'],
678
				'id_profile_board' => (int) $row['id_profile'],
679
				'id_board' => (int) $row['id_board'],
680
				'sent' => (int) $row['sent'],
681
			];
682
683
			if (validateNotificationAccess($clean, false))
684
			{
685
				$boardNotifyData[] = $clean;
686
			}
687
		}
688
	);
689
690
	return $boardNotifyData;
691
}
692
693
/**
694
 * Finds members who have topic notification on for a topic where the type is one that they want to be
695
 * kept in the loop.
696
 *
697
 * What it does:
698
 * Finds notifications that meet:
699
 * 	- Member has watch notifications on for these topics
700
 *  - Member has set notification type of reply and/or moderation notices
701
 *      - ALL_MESSAGES = 1;
702
 *      - MODERATION_ONLY_IF_STARTED = 2;
703
 *      - ONLY_REPLIES = 3;
704
 *      - NOTHING_AT_ALL = 4;
705
 *  - Member has set notification regularity to 'email' (instantly or first unread) or 'onsite' for onsite
706
 *      - NOTHING = 99;
707
 *      - EMAIL INSTANTLY = 0;
708
 *      - EMAIL FIRST_UNREAD_MSG = 1;
709
 *      - DAILY_DIGEST = 2;
710
 *      - WEEKLY_DIGEST = 3;
711
 *      - ONSITE FIRST_UNREAD_MSG = 4;
712
 *  - Member is activated
713
 *  - Member has access to the board where the topic resides
714
 *
715
 * @param int $user_id some goon, e.g. the originator of the action who **WILL NOT** get a notification
716
 * @param int[] $topics array of topic id's that have updates
717
 * @param string $type type of notification like reply, lock, sticky
718
 * @param int|int[] $members_only if not empty, only send notices to these members
719
 * @param string $regularity type of notification 'email' or 'onsite'
720
 * @return array
721
 */
722
function fetchTopicNotifications($user_id, $topics, $type, $members_only, $regularity = 'email')
723
{
724
	$db = database();
725
726
	$topicNotifyData = [];
727
728
	$notify_types = $type === 'reply' ? 'mem.notify_types < 4' : 'mem.notify_types < 3';
729
	$notify_regularity = $regularity === 'email' ? 'mem.notify_regularity < 2' : 'mem.notify_regularity = 4';
730
731
	// Find the members with notification on for this topic.
732
	$db->fetchQuery('
733
		SELECT
734
			mem.id_member, mem.email_address, mem.notify_regularity, mem.notify_types, mem.notify_send_body, mem.notify_from,
735
			mem.lngfile, mem.warning, mem.id_group, mem.additional_groups, mem.id_post_group, mem.password_salt,
736
			t.id_member_started, t.id_last_msg,
737
			b.member_groups, b.name, b.id_profile, b.id_board,
738
			ln.id_topic, ln.sent
739
		FROM {db_prefix}log_notify AS ln
740
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = ln.id_member)
741
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = ln.id_topic)
742
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
743
		WHERE ln.id_topic IN ({array_int:topic_list})
744
			AND {raw:notify_types}
745
			AND {raw:notify_regularity}
746
			AND mem.is_activated = {int:is_activated}
747
			AND ln.id_member != {int:current_member}' .
748
		(empty($members_only) ? '' : ' AND ln.id_member IN ({array_int:members_only})') . '
749
		ORDER BY mem.lngfile',
750
		[
751
			'current_member' => $user_id,
752
			'topic_list' => $topics,
753
			'notify_types' => $notify_types,
754
			'notify_regularity' => $notify_regularity,
755
			'is_activated' => 1,
756
			'members_only' => is_array($members_only) ? $members_only : [$members_only],
757
		]
758
	)->fetch_callback(
759
		function ($row) use (&$topicNotifyData) {
760
			// The topic subscription information for the member
761
			$clean = [
762
				'id_member' => (int) $row['id_member'],
763
				'email_address' => $row['email_address'],
764
				'notify_regularity' => (int) $row['notify_regularity'],
765
				'notify_types' => (int) $row['notify_types'],
766
				'notify_send_body' => (int) $row['notify_send_body'],
767
				'notify_from' => (int) $row['notify_from'],
768
				'lngfile' => $row['lngfile'],
769
				'warning' => $row['warning'],
770
				'id_group' => (int) $row['id_group'],
771
				'additional_groups' => $row['additional_groups'],
772
				'id_post_group' => (int) $row['id_post_group'],
773
				'password_salt' => $row['password_salt'],
774
				'id_member_started' => (int) $row['id_member_started'],
775
				'member_groups' => $row['member_groups'],
776
				'board_name' => $row['name'],
777
				'id_profile_board' => (int) $row['id_profile'],
778
				'id_board' =>  (int) $row['id_board'],
779
				'id_topic' => (int) $row['id_topic'],
780
				'last_id' => (int) $row['id_last_msg'],
781
				'sent' => (int) $row['sent'],
782
			];
783
784
			if (validateNotificationAccess($clean, false))
785
			{
786
				$topicNotifyData[$clean['id_topic']] = $clean;
787
			}
788
		}
789
	);
790
791
	return $topicNotifyData;
792
}
793
794
/**
795
 * Updates the log_notify table for all members that have received notifications
796
 *
797
 * @param int $user_id
798
 * @param array $data
799
 * @param boolean $board if true updates a boards log notify, else topic
800
 * @return void
801
 */
802
function updateLogNotify($user_id, $data, $board = false)
803
{
804
	$db = database();
805 2
806
	$db->query('', '
807 2
		UPDATE {db_prefix}log_notify
808
		SET 
809
			sent = {int:is_sent}
810 2
		WHERE ' . ($board ? 'id_board' : 'id_topic') . ' IN ({array_int:data_list})
811
			AND id_member != {int:current_member}',
812 2
		[
813
			'current_member' => $user_id,
814
			'data_list' => $data,
815
			'is_sent' => 1,
816
		]
817
	);
818
}
819
820
/**
821
 * Finds members who have topic notification on for a topic where the type is one that they want to be
822
 * kept in the loop.
823
 *
824
 * What it does:
825
 * Finds notifications that meet:
826
 * 	- Members has watch notifications enabled for these topics
827
 *  - The notification type is not none/off (all, replies+moderation, moderation only)
828
 *  - Notification regularity of instantly or first unread
829
 *  - Member is activated
830
 *  - Member has access to the board where the topic resides
831
 *
832
 * @param int[] $topics array of topic id's that have updates
833
 * @return array
834
 */
835
function fetchApprovalNotifications($topics)
836
{
837
	$db = database();
838
839
	$approvalNotifyData = [];
840
841
	// Find everyone who needs to know about this.
842
	$db->fetchQuery('
843
		SELECT
844
			DISTINCT mem.id_member, mem.email_address, mem.notify_regularity, mem.notify_types, mem.notify_send_body, mem.notify_from,
845
			mem.lngfile, mem.warning, mem.id_group, mem.additional_groups, mem.id_post_group, mem.password_salt,
846
			t.id_member_started,
847
			b.member_groups, b.name, b.id_profile, b.id_board,
848
			ln.id_topic, ln.sent
849
		FROM {db_prefix}log_notify AS ln
850
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = ln.id_member)
851
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = ln.id_topic)
852
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
853
		WHERE ln.id_topic IN ({array_int:topic_list})
854
			AND mem.is_activated = {int:is_activated}
855
			AND mem.notify_types < {int:notify_types}
856
			AND mem.notify_regularity < {int:notify_regularity}
857
		ORDER BY mem.lngfile',
858
		[
859
			'topic_list' => $topics,
860
			'is_activated' => 1,
861
			'notify_types' => 4,
862
			'notify_regularity' => 2,
863
		]
864
	)->fetch_callback(
865
		function ($row) use (&$approvalNotifyData) {
866
			$clean = [
867
				'id_member' => (int) $row['id_member'],
868
				'email_address' => $row['email_address'],
869
				'notify_regularity' => (int) $row['notify_regularity'],
870
				'notify_types' => (int) $row['notify_types'],
871
				'notify_send_body' => (int) $row['notify_send_body'],
872
				'notify_from' => (int) $row['notify_from'],
873
				'lngfile' => $row['lngfile'],
874
				'warning' => $row['warning'],
875
				'id_group' => (int) $row['id_group'],
876
				'additional_groups' => $row['additional_groups'],
877
				'id_post_group' => (int) $row['id_post_group'],
878
				'password_salt' => $row['password_salt'],
879
				'id_member_started' => (int) $row['id_member_started'],
880
				'member_groups' => $row['member_groups'],
881
				'board_name' => $row['name'],
882
				'id_profile_board' => (int) $row['id_profile'],
883
				'id_board' =>  (int) $row['id_board'],
884
				'id_topic' => (int) $row['id_topic'],
885
				'sent' => (int) $row['sent'],
886
			];
887
888
			if (validateNotificationAccess($clean, false))
889
			{
890
				$approvalNotifyData[] = $clean;
891
			}
892
		}
893
	);
894
895
	return $approvalNotifyData;
896
}
897