Passed
Push — development ( e156b5...b889cb )
by Spuds
01:09 queued 28s
created

PostNotifications::sendSiteBoardNotifications()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 37
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 17
dl 0
loc 37
rs 9.0777
c 0
b 0
f 0
cc 6
nc 4
nop 3
1
<?php
2
3
/**
4
 * This deals with sending email notifications to members who have elected to receive them
5
 * when things happen to a topic, or board, which they have subscribed.
6
 *
7
 * @package   ElkArte Forum
8
 * @copyright ElkArte Forum contributors
9
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
10
 *
11
 * @version 2.0 dev
12
 *
13
 */
14
15
namespace ElkArte\Notifications;
16
17
use ElkArte\AbstractModel;
18
use ElkArte\Languages\Loader;
19
use ElkArte\Mail\BuildMail;
20
use ElkArte\Mail\PreparseMail;
21
use ElkArte\User;
22
23
class PostNotifications extends AbstractModel
24
{
25
	/** Notification levels, what the user has selected in profile, **for reference** */
26
	private const NOTIFY_TYPE_ALL_MESSAGES = 1;
27
	private const NOTIFY_TYPE_MODERATION_ONLY_IF_STARTED = 2;
28
	private const NOTIFY_TYPE_ONLY_REPLIES = 3;
29
	private const NOTIFY_TYPE_NOTHING_AT_ALL = 4;
30
31
	/** Notification, **for reference** */
32
	private const NOTIFY_REPLY = 'reply';
33
	private const NOTIFY_STICKY = 'sticky';
34
	private const NOTIFY_LOCK = 'lock';
35
	private const NOTIFY_UNLOCK = 'unlock';
36
	private const NOTIFY_REMOVE = 'remove';
37
	private const NOTIFY_MOVE = 'move';
38
	private const NOTIFY_MERGE = 'merge';
39
	private const NOTIFY_SPLIT = 'split';
40
41
	/** Notification Regularity, **for reference** */
42
	private const REGULARITY_NOTHING = 99;
43
	private const REGULARITY_EMAIL_INSTANTLY = 0;
44
	private const REGULARITY_EMAIL_FIRST_UNREAD_MSG = 1;
45
	private const REGULARITY_DAILY_DIGEST = 2;
46
	private const REGULARITY_WEEKLY_DIGEST = 3;
47
	private const REGULARITY_ONSITE_FIRST_UNREAD_MSG = 4;
48
49
	/** @var int how many emails were sent */
50
	protected $sent = 0;
51
52
	/** @var array If the topic was sent via a board notification to prevent double sending */
53
	protected $boards = [];
54
55
	/** @var string Humm could it be the current language? */
56
	protected $current_language = '';
57
58
	/**
59
	 * PostNotifications constructor.
60
	 */
61
	public function __construct()
62
	{
63
		parent::__construct();
64
65
		// Load in dependencies
66
		require_once(SUBSDIR . '/Maillist.subs.php');
67
		require_once(SUBSDIR . '/Notification.subs.php');
68
		require_once(SUBSDIR . '/Mail.subs.php');
69
	}
70
71
	/**
72
	 * The function automatically finds the subject and its board, and then
73
	 * checks permissions for each member who is "signed up" for notifications.
74
	 *
75
	 * It will not send 'reply' notifications more than once in a row.  It will send new replies to topics on
76
	 * subscribed boards and/or subscribed (watched) topics.
77
	 *
78
	 * @param int[]|int $topics - represents the topics the action is happening to.
79
	 * @param string $type - can be any of reply, sticky, lock, unlock, remove,
80
	 *                       move, merge, and split.  An appropriate message will be sent for each.
81
	 * @param int[]|int $members_only = array() - restrict to only send the notification to this list, otherwise all
82
	 * @param array $pbe = array() - PBE user_info if this is being run as a result of an email posting
83
	 */
84
	public function sendNotifications($topics, $type, $members_only = [], $pbe = [])
85
	{
86
		global $txt;
87
88
		// Can't do it if there's no topics.
89
		if (empty($topics))
90
		{
91
			return;
92
		}
93
94
		// It must be an array - it must!
95
		if (!is_array($topics))
96
		{
97
			$topics = [$topics];
98
		}
99
100
		// I hope we are not sending one of those silly moderation notices
101
		if ($type !== self::NOTIFY_REPLY && !empty($this->_modSettings['pbe_no_mod_notices']) && $this->isUsingMailList())
102
		{
103
			return;
104
		}
105
106
		// Who are we?
107
		$user_id = $this->_getUserID($pbe);
108
		$user_language = $this->_getUserLanguage($pbe);
109
110
		// Get the subject, body and basic poster details, number of attachments if any
111
		[$boards_index, $topicData] = getTopicInfos($topics, $type);
112
113
		// Nada?
114
		if (empty($topicData))
115
		{
116
			trigger_error('sendNotifications(): topics not found', E_USER_NOTICE);
117
		}
118
119
		// Just in case they've gone walkies, or trying to get to something they no longer can
120
		$topics = array_keys($topicData);
121
		if (empty($topics))
122
		{
123
			return;
124
		}
125
126
		// Insert all of these items into the digest log for those who want notifications later.
127
		$digest_insert = [];
128
		foreach ($topicData as $data)
129
		{
130
			$digest_insert[] = [$data['topic'], $data['last_id'], $type, (int) $data['exclude']];
131
		}
132
133
		insertLogDigestQueue($digest_insert);
134
135
		// Find the members with onsite watch notifications for this topic.
136
		$this->sendSiteNotifications($user_id, $topicData, $type, $members_only);
0 ignored issues
show
Bug introduced by
It seems like $members_only can also be of type integer; however, parameter $members_only of ElkArte\Notifications\Po...sendSiteNotifications() does only seem to accept integer[], maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

136
		$this->sendSiteNotifications($user_id, $topicData, $type, /** @scrutinizer ignore-type */ $members_only);
Loading history...
137
138
		// Using the posting email function then process posts to subscribed boards
139
		if ($this->isUsingMailList())
140
		{
141
			$this->sendBoardTopicNotifications($topicData, $user_id, $boards_index, $type, $members_only);
142
		}
143
144
		// Find the members with watch notifications set for this topic, it will skip any sent via board notifications
145
		$this->sendTopicNotifications($user_id, $topicData, $type, $members_only);
0 ignored issues
show
Bug introduced by
It seems like $members_only can also be of type integer; however, parameter $members_only of ElkArte\Notifications\Po...endTopicNotifications() does only seem to accept integer[], maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

145
		$this->sendTopicNotifications($user_id, $topicData, $type, /** @scrutinizer ignore-type */ $members_only);
Loading history...
146
147
		if (!empty($this->current_language) && $this->current_language !== $user_language)
148
		{
149
			$lang_loader = new Loader(null, $txt, database());
150
			$lang_loader->load('Post', false);
151
		}
152
153
		// Sent!
154
		if ($type !== self::NOTIFY_REPLY)
155
		{
156
			return;
157
		}
158
159
		if (empty($this->sent))
160
		{
161
			return;
162
		}
163
164
		updateLogNotify($user_id, $topics);
165
	}
166
167
	/**
168
	 * If we are using the mail list functionality
169
	 *
170
	 * @return bool
171
	 */
172
	public function isUsingMailList()
173
	{
174
		return !empty($this->_modSettings['maillist_enabled']) && !empty($this->_modSettings['pbe_post_enabled']);
175
	}
176
177
	/**
178
	 * The current user, which might be the one posting via email
179
	 *
180
	 * @param array $pbe
181
	 * @return int
182
	 */
183
	private function _getUserID($pbe)
184
	{
185
		return (!empty($pbe['user_info']['id']) && !empty($this->_modSettings['maillist_enabled']))
186
			? (int) $pbe['user_info']['id'] : 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...
187
	}
188
189
	/**
190
	 * The language of the poster
191
	 *
192
	 * @param array $pbe
193
	 * @return string
194
	 */
195
	private function _getUserLanguage($pbe)
196
	{
197
		return (!empty($pbe['user_info']['language']) && !empty($this->_modSettings['maillist_enabled']))
198
			? $pbe['user_info']['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...
199
	}
200
201
	/**
202
	 * Sends a new topic notification to members subscribed to a board
203
	 *
204
	 * @param array $topicData the topic in question
205
	 * @param int $user_id the poster
206
	 * @param int[] $boards_index the boards the topic(s) are on
207
	 * @param string $type see Notify Types
208
	 * @param int|int[] $members_only if only sending to a select list of members
209
	 */
210
	public function sendBoardTopicNotifications($topicData, $user_id, $boards_index, $type, $members_only)
211
	{
212
		global $language;
213
214
		// Fetch the members with *board* notifications on.
215
		$boardNotifyData = fetchBoardNotifications($user_id, $boards_index, $type, $members_only);
216
217
		// Check each board notification entry against the topic
218
		foreach ($boardNotifyData as $notifyDatum)
219
		{
220
			// For this member/board, loop through the topics and see if we should send it
221
			foreach ($topicData as $id_topic => $topicDatum)
222
			{
223
				// Don't if it is not from the right board
224
				if ($topicDatum['board'] !== $notifyDatum['id_board'])
225
				{
226
					continue;
227
				}
228
229
				// Don't when they want moderation notifications for their topics, and it is not theirs.
230
				if ($type !== self::NOTIFY_REPLY && $topicDatum['id_member_started'] !== $notifyDatum['id_member']
231
					&& $notifyDatum['notify_types'] === self::NOTIFY_TYPE_MODERATION_ONLY_IF_STARTED)
232
				{
233
					continue;
234
				}
235
236
				// Don't if they don't have notification permissions
237
				$email_perm = true;
238
				if (!validateNotificationAccess($notifyDatum, true, $email_perm))
239
				{
240
					continue;
241
				}
242
243
				$needed_language = empty($notifyDatum['lngfile']) || empty($this->_modSettings['userLanguage']) ? $language : $notifyDatum['lngfile'];
244
				$this->_checkLanguage($needed_language);
245
246
				// Set the mail template
247
				$message_type = $this->setMessageTemplate($type, $notifyDatum);
248
249
				// Set the replacement values for the template
250
				$replacements = $this->setTemplateReplacements($topicDatum, $notifyDatum, $id_topic, $type, 'board');
251
252
				// Give them a way to add in their own replacements
253
				call_integration_hook('integrate_notification_replacements', [&$replacements, $notifyDatum, $type, $this->current_language]);
254
255
				// Send moderation notices, "instantly" notifications, and any that have not been sent.
256
				if ($type !== self::NOTIFY_REPLY || empty($notifyDatum['notify_regularity']) || empty($notifyDatum['sent']))
257
				{
258
					// If they have PBE access, and it is on ... use those templates.
259
					$template = ($email_perm && $type === self::NOTIFY_REPLY && $this->canSendPostBody($notifyDatum) ? 'pbe_' : '') . $message_type;
260
					$emaildata = loadEmailTemplate($template, $replacements, $needed_language, true);
261
262
					$sendMail = new BuildMail();
263
					$sendMail->setEmailReplacements($replacements);
264
265
					// If using the maillist functions, we adjust who this is coming from
266
					if ($email_perm && $type === self::NOTIFY_REPLY && $this->canSendPostBody($notifyDatum))
267
					{
268
						$email_from = $this->_getEmailFrom($topicDatum);
269
						$from_wrapper = $this->_getFromWrapper();
270
271
						$sendMail->buildEmail($notifyDatum['email_address'], $emaildata['subject'], $emaildata['body'], $email_from, 'm' . $topicDatum['last_id'], true, 3, false, $from_wrapper, $id_topic);
272
					}
273
					else
274
					{
275
						$sendMail->buildEmail($notifyDatum['email_address'], $emaildata['subject'], $emaildata['body'], null, 'm' . $topicDatum['last_id'], true);
276
					}
277
278
					// Make a note that this member was sent this topic
279
					$this->sent++;
280
					$this->boards[$notifyDatum['id_member']][$id_topic] = 1;
281
				}
282
			}
283
		}
284
	}
285
286
	/**
287
	 * Checks if the language in use is what the user needs and if not loads the right one
288
	 *
289
	 * @param string $needed_language
290
	 * @uses Post language file
291
	 */
292
	private function _checkLanguage($needed_language)
293
	{
294
		global $txt;
295
296
		if (empty($this->current_language) || $this->current_language !== $needed_language)
297
		{
298
			$lang_loader = new Loader($needed_language, $txt, database());
299
			$lang_loader->load('Post', false);
300
			$this->current_language = $needed_language;
301
		}
302
	}
303
304
	/**
305
	 * Returns the string of the email template to use, like notification_reply_body
306
	 * pbe variants are appended back in the main flow
307
	 *
308
	 * @param string $type
309
	 * @param array $notifyDatum
310
	 * @return string
311
	 */
312
	private function setMessageTemplate($type, $notifyDatum)
313
	{
314
		$message_type = 'notification_' . $type;
315
316
		if ($type === self::NOTIFY_REPLY)
317
		{
318
			if (!empty($notifyDatum['notify_send_body']) && $this->canSendPostBody($notifyDatum))
319
			{
320
				$message_type .= '_body';
321
			}
322
323
			if (!empty($notifyDatum['notify_regularity']))
324
			{
325
				$message_type .= '_once';
326
			}
327
		}
328
329
		return $message_type;
330
	}
331
332
	/**
333
	 * Creates the replacement strings for use in email templates
334
	 *
335
	 * @param array $topicDatum
336
	 * @param array $notifyDatum
337
	 * @param int $id id of the topic/message
338
	 * @param string $type one of the TYPE constants
339
	 * @param string $area topic or board, defines the unsubscribe link
340
	 * @return array
341
	 */
342
	private function setTemplateReplacements($topicDatum, $notifyDatum, $id, $type = 'reply', $area = 'topic')
343
	{
344
		global $scripturl, $txt;
345
346
		$mailPreparse = new PreparseMail();
347
348
		// Set the replacement values for the template
349
		$replacements = [
350
			'TOPICSUBJECT' => $mailPreparse->preparseSubject($topicDatum['subject']),
351
			'POSTERNAME' => un_htmlspecialchars($topicDatum['name']),
352
			'SIGNATURE' => $mailPreparse->preparseSignature($topicDatum['signature']),
353
			'BOARDNAME' => $notifyDatum['board_name'],
354
			'SUBSCRIPTION' => $txt['topic'],
355
		];
356
357
		// Notification due to a board or topic subscription, we provide a proper no más correo no deseado link
358
		if ($area === 'board')
359
		{
360
			$replacements['SUBSCRIPTION'] = $txt['board'];
361
			$replacements['UNSUBSCRIBELINK'] = replaceBasicActionUrl('{script_url}?action=notify;sa=unsubscribe;token=' .
362
				getNotifierToken($notifyDatum['id_member'], $notifyDatum['email_address'], $notifyDatum['password_salt'], 'board', $topicDatum['board']));
363
		}
364
		else
365
		{
366
			$replacements['UNSUBSCRIBELINK'] = replaceBasicActionUrl('{script_url}?action=notify;sa=unsubscribe;token=' .
367
				getNotifierToken($notifyDatum['id_member'], $notifyDatum['email_address'], $notifyDatum['password_salt'], 'topic', $notifyDatum['id_topic']));
368
		}
369
370
		// New topic or a reply
371
		if (!empty($topicDatum['last_id']))
372
		{
373
			$replacements['TOPICLINK'] = $scripturl . '?topic=' . $id . '.msg' . $topicDatum['last_id'] . '#msg' . $topicDatum['last_id'];
374
			$replacements['TOPICLINKNEW'] = $scripturl . '?topic=' . $id . '.new;topicseen#new';
375
		}
376
		else
377
		{
378
			$replacements['TOPICLINK'] = $scripturl . '?topic=' . $id . '.new#new';
379
			$replacements['TOPICLINKNEW'] = $scripturl . '?topic=' . $id . '.new#new';
380
		}
381
382
		// If removed, no sense in sending links that point to nothing
383
		if ($type === 'remove')
384
		{
385
			unset($replacements['TOPICLINK'], $replacements['UNSUBSCRIBELINK']);
386
		}
387
388
		// Do they want the body of the message sent too?
389
		if ($type === self::NOTIFY_REPLY && $this->canSendPostBody($notifyDatum))
390
		{
391
			$body = $topicDatum['body'];
392
393
			// Any attachments? if so lets make a big deal about them!
394
			if (!empty($topicDatum['attachments']))
395
			{
396
				$body .= "\n\n" . sprintf($txt['message_attachments'], $topicDatum['attachments'], $replacements['TOPICLINK']);
397
			}
398
399
			$replacements['MESSAGE'] = $mailPreparse->preparseHtml($body);
400
		}
401
402
		return $replacements;
403
	}
404
405
	/**
406
	 * Returns if we are to send the post text/body in the email
407
	 *
408
	 * @param array $data
409
	 * @return bool
410
	 */
411
	private function canSendPostBody($data)
412
	{
413
		if (empty($data['notify_send_body']))
414
		{
415
			return false;
416
		}
417
418
		if ($this->isUsingMailList())
419
		{
420
			return true;
421
		}
422
423
		return empty($this->_modSettings['disallow_sendBody']);
424
	}
425
426
	/**
427
	 * Who the email "envelope: is from which depends on the mode set
428
	 *
429
	 * @param array $topicDatum
430
	 * @return string
431
	 */
432
	private function _getEmailFrom($topicDatum)
433
	{
434
		global $mbname;
435
436
		// In group mode like google groups or yahoo groups, the mail is from the poster
437
		if (!empty($this->_modSettings['maillist_group_mode']))
438
		{
439
			return un_htmlspecialchars($topicDatum['name'] ?? 'N/A');
440
		}
441
442
		// Otherwise in maillist mode, it is from the site
443
		if (!empty($this->_modSettings['maillist_sitename']))
444
		{
445
			return un_htmlspecialchars($this->_modSettings['maillist_sitename']);
446
		}
447
448
		// Fallback to the forum name
449
		return $mbname;
450
	}
451
452
	/**
453
	 * The actual sender of the email, needs to be correct, or it will bounce
454
	 *
455
	 * @return string
456
	 */
457
	private function _getFromWrapper()
458
	{
459
		global $webmaster_email;
460
461
		// The email address of the sender, irrespective of the envelope name above
462
		if (!empty($this->_modSettings['maillist_mail_from']))
463
		{
464
			return $this->_modSettings['maillist_mail_from'];
465
		}
466
467
		if (!empty($this->_modSettings['maillist_sitename_address']))
468
		{
469
			return $this->_modSettings['maillist_sitename_address'];
470
		}
471
472
		return $webmaster_email;
473
	}
474
475
	/**
476
	 * Sends onsite notifications for watched topics with activity
477
	 *
478
	 * @param int $user_id the poster
479
	 * @param array $topicData new replies topic data
480
	 * @param string $type see Notify Types
481
	 * @param int[] $members_only if only sending to a select list of members
482
	 */
483
	public function sendSiteNotifications($user_id, $topicData, $type, $members_only)
484
	{
485
		// Find the members with watch notifications set for these topics.
486
		$topics = array_keys($topicData);
487
		$topicNotifications = fetchTopicNotifications($user_id, $topics, $type, $members_only, 'onsite');
488
		$notifier = Notifications::instance();
489
		foreach ($topicNotifications as $notifyDatum)
490
		{
491
			// Don't do the ones that are interested in only their own topic moderation events when it is not their topic
492
			if ($type !== self::NOTIFY_REPLY && $notifyDatum['id_member'] !== $notifyDatum['id_member_started']
493
				&& $notifyDatum['notify_types'] === self::NOTIFY_TYPE_MODERATION_ONLY_IF_STARTED)
494
			{
495
				continue;
496
			}
497
498
			// Notify just once
499
			if (empty($notifyDatum['sent']))
500
			{
501
				$status = 'new';
502
				$topicDetails = $topicData[$notifyDatum['id_topic']];
503
504
				$notifier->add(new NotificationsTask(
505
					'watchedtopic',
506
					$topicDetails['last_id'],
507
					$user_id,
508
					['id_members' => $notifyDatum['id_member'], 'notifier_data' => $notifyDatum, 'status' => $status, 'subject' => $topicDetails['subject']]
509
				));
510
511
				$this->sent++;
512
			}
513
		}
514
	}
515
516
	/**
517
	 * Sends reply notifications to topics with new replies on watched topics
518
	 *
519
	 * @param int $user_id the poster
520
	 * @param array $topicData new replies topic data
521
	 * @param string $type see Notify Types
522
	 * @param int[] $members_only if only sending to a select list of members
523
	 */
524
	public function sendTopicNotifications($user_id, $topicData, $type, $members_only)
525
	{
526
		global $language;
527
528
		$maillist = $this->isUsingMailList();
529
530
		// Find the members with watch notifications set for these topics.
531
		$topics = array_keys($topicData);
532
		$topicNotifications = fetchTopicNotifications($user_id, $topics, $type, $members_only);
533
		foreach ($topicNotifications as $notifyDatum)
534
		{
535
			// Don't do the ones that were already sent via board notification, you only get one notice
536
			if (isset($this->boards[$notifyDatum['id_member']][$notifyDatum['id_topic']]))
537
			{
538
				continue;
539
			}
540
541
			// Don't do the ones that are interested in only their own topic moderation events when it is not their topic
542
			if ($type !== self::NOTIFY_REPLY && $notifyDatum['id_member'] !== $notifyDatum['id_member_started']
543
				&& $notifyDatum['notify_types'] === self::NOTIFY_TYPE_MODERATION_ONLY_IF_STARTED)
544
			{
545
				continue;
546
			}
547
548
			// Don't if they don't have notification permissions
549
			$email_perm = true;
550
			if (!validateNotificationAccess($notifyDatum, $maillist, $email_perm))
551
			{
552
				continue;
553
			}
554
555
			$needed_language = empty($notifyDatum['lngfile']) || empty($this->_modSettings['userLanguage']) ? $language : $notifyDatum['lngfile'];
556
			$this->_checkLanguage($needed_language);
557
558
			$message_type = $this->setMessageTemplate($type, $notifyDatum);
559
			$replacements = $this->setTemplateReplacements($topicData[$notifyDatum['id_topic']], $notifyDatum, $notifyDatum['id_topic'], $type);
560
561
			// Send if a moderation notice, or they want everything, or it has not been sent (first new message)
562
			if ($type !== self::NOTIFY_REPLY || empty($notifyDatum['notify_regularity']) || empty($notifyDatum['sent']))
563
			{
564
				// Use the pbe template when appropriate
565
				$template = ($maillist && $email_perm && $type === self::NOTIFY_REPLY && $this->canSendPostBody($notifyDatum) ? 'pbe_' : '') . $message_type;
566
				$emaildata = loadEmailTemplate($template, $replacements, $needed_language, true);
567
568
				$sendMail = new BuildMail();
569
				$sendMail->setEmailReplacements($replacements);
570
571
				// Using the maillist functions? Then adjust the wrapper
572
				if ($maillist && $email_perm && $type === self::NOTIFY_REPLY && !empty($notifyDatum['notify_send_body']))
573
				{
574
					// Set from name based on group or maillist mode
575
					$email_from = $this->_getEmailFrom($topicData[$notifyDatum['id_topic']]);
576
					$from_wrapper = $this->_getFromWrapper();
577
578
					$sendMail->buildEmail($notifyDatum['email_address'], $emaildata['subject'], $emaildata['body'], $email_from, 'm' . $topicNotifications[$notifyDatum['id_topic']]['last_id'], true, 3, false, $from_wrapper, $notifyDatum['id_topic']);
579
				}
580
				else
581
				{
582
					$sendMail->buildEmail($notifyDatum['email_address'], $emaildata['subject'], $emaildata['body'], null, 'm' . $topicNotifications[$notifyDatum['id_topic']]['last_id'], true);
583
				}
584
585
				$this->sent++;
586
			}
587
		}
588
	}
589
590
	/**
591
	 * Notifies members who have requested notification for new topics posted on a board of said posts.
592
	 *
593
	 * What it does:
594
	 * - Receives data on the topics to send out notifications to the passed in array.
595
	 * - Only sends notifications to those who can *currently* see the topic (it doesn't matter if they could when they requested notification.)
596
	 *  -Loads the Post language file multiple times for each language if the userLanguage setting is set.
597
	 *
598
	 * @param array $topicData
599
	 */
600
	public function sendBoardNotifications(&$topicData)
601
	{
602
		global $txt;
603
604
		// Do we have one or lots of topics?
605
		if (isset($topicData['body']))
606
		{
607
			$topicData = [$topicData];
608
		}
609
610
		// Find out what boards we have... and clear out any rubbish!
611
		$boards = [];
612
		foreach ($topicData as $key => $topic)
613
		{
614
			if (!empty($topic['board']))
615
			{
616
				$boards[$topic['board']][] = $key;
617
			}
618
			else
619
			{
620
				unset($topic[$key]);
621
			}
622
		}
623
624
		// Just the board numbers.
625
		$board_index = array_unique(array_keys($boards));
626
		if (empty($board_index))
627
		{
628
			return;
629
		}
630
631
		// Yea, we need to add this to the digest queue.
632
		$digest_insert = [];
633
		foreach ($topicData as $data)
634
		{
635
			$digest_insert[] = [$data['topic'], $data['msg'], 'topic', 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...
636
		}
637
		insertLogDigestQueue($digest_insert);
638
639
		// Find the members with onsite watch notifications for this topic.
640
		$this->sendSiteBoardNotifications($topicData, $board_index, $boards);
641
642
		// Now the ones who want it via Email
643
		$this->sendEmailBoardNotifications($topicData,$board_index, $boards);
644
645
		$lang_loader = new Loader(null, $txt, database());
646
		$lang_loader->load('index', false);
647
648
		// Sent!
649
		updateLogNotify(User::$info->id, $board_index, true);
650
	}
651
652
	/**
653
	 * @param $topicData
654
	 * @param $board_index
655
	 * @param $boards
656
	 * @return void
657
	 */
658
	public function sendSiteBoardNotifications($topicData, $board_index, $boards)
659
	{
660
		// Find the members with onsite notifications set for these boards.
661
		$boardNotifyData = fetchBoardNotifications(User::$info->id, $board_index, 'reply', [], 'onsite');
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...
662
		$notifier = Notifications::instance();
663
664
		foreach ($boardNotifyData as $notifyDatum)
665
		{
666
			$email_perm = true;
667
668
			// Not allowed to see that board?
669
			if (!validateNotificationAccess($notifyDatum, false, $email_perm))
670
			{
671
				continue;
672
			}
673
674
			// Something to do?
675
			if (empty($boards[$notifyDatum['id_board']]))
676
			{
677
				continue;
678
			}
679
680
			// For each message we need to send (from this board to this member)
681
			foreach ($boards[$notifyDatum['id_board']] as $key)
682
			{
683
				// Don't notify the guy who started the topic!
684
				if ($topicData[$key]['poster'] === $notifyDatum['id_member'])
685
				{
686
					continue;
687
				}
688
689
				$status = 'new';
690
				$notifier->add(new NotificationsTask(
691
					'watchedboard',
692
					$topicData[$key]['msg'],
693
					$topicData[$key]['poster'],
694
					['id_members' => $notifyDatum['id_member'], 'notifier_data' => $notifyDatum, 'status' => $status, 'subject' => $topicData[$key]['subject']]
695
				));
696
			}
697
		}
698
	}
699
700
	/**
701
	 * @param $topicData
702
	 * @param $board_index
703
	 * @param $boards
704
	 * @return void
705
	 */
706
	public function sendEmailBoardNotifications($topicData, $board_index, $boards)
707
	{
708
		global $language, $txt;
709
710
		$maillist = $this->isUsingMailList();
711
712
		// Find the members with email notification on for these boards.
713
		$boardNotifyData = fetchBoardNotifications(User::$info->id, $board_index, 'reply', [], 'email');
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...
714
		foreach ($boardNotifyData as $notifyDatum)
715
		{
716
			// No access, no notification, easy
717
			$email_perm = true;
718
			if (!validateNotificationAccess($notifyDatum, $maillist, $email_perm))
719
			{
720
				continue;
721
			}
722
723
			$langloaded = empty($notifyDatum['lngfile']) || empty($this->_modSettings['userLanguage']) ? $language : $notifyDatum['lngfile'];
724
			$lang_loader = new Loader($langloaded, $txt, database());
725
			$lang_loader->load('index', false);
726
727
			// Now loop through all the notifications to send for this board.
728
			if (empty($boards[$notifyDatum['id_board']]))
729
			{
730
				continue;
731
			}
732
733
			$sentOnceAlready = false;
734
735
			// For each message we need to send (from this board to this member)
736
			foreach ($boards[$notifyDatum['id_board']] as $key)
737
			{
738
				// Don't notify the guy who started the topic!
739
				// @todo In this case actually send them a "it's approved hooray" email :P
740
				if ($topicData[$key]['poster'] === $notifyDatum['id_member'])
741
				{
742
					continue;
743
				}
744
745
				// Set the string for adding the body to the message, if a user wants it.
746
				$replacements = $this->setTemplateReplacements($topicData[$key], $notifyDatum, $topicData[$key]['topic'], 'reply', 'board');
747
748
				// Figure out which email to send
749
				$email_type = $this->setBoardTemplate($notifyDatum, $sentOnceAlready);
750
				if (!empty($email_type))
751
				{
752
					// Perhaps PBE as well?
753
					$template = ($email_perm && $this->canSendPostBody($notifyDatum) ? 'pbe_' : '') . $email_type;
754
					$emaildata = loadEmailTemplate($template, $replacements, $langloaded, true);
755
756
					$sendMail = new BuildMail();
757
					$sendMail->setEmailReplacements($replacements);
758
759
					// Maillist style?
760
					if ($email_perm && $this->canSendPostBody($notifyDatum))
761
					{
762
						// Add in the from wrapper and trigger sendmail to add in a security key
763
						$email_from = $this->_getEmailFrom($topicData[$key]);
764
						$from_wrapper = $this->_getFromWrapper();
765
766
						$sendMail->buildEmail($notifyDatum['email_address'], $emaildata['subject'], $emaildata['body'], $email_from, 't' . $topicData[$key]['topic'], true, 3, false, $from_wrapper, $topicData[$key]['topic']);
767
					}
768
					else
769
					{
770
						$sendMail->buildEmail($notifyDatum['email_address'], $emaildata['subject'], $emaildata['body'], null, null, true);
771
					}
772
				}
773
774
				$sentOnceAlready = true;
775
			}
776
		}
777
	}
778
779
	/**
780
	 * Returns the string of the email template to use, similar to setMessageTemplate but for
781
	 * new topics on boards
782
	 *
783
	 * PBE variants are appended back in the main flow
784
	 *
785
	 * @param array $data
786
	 * @param boolean $sentOnceAlready
787
	 * @return string
788
	 */
789
	public function setBoardTemplate($data, $sentOnceAlready)
790
	{
791
		$email_type = '';
792
793
		if (!empty($data['notify_regularity']) && !$sentOnceAlready && empty($data['sent']))
794
		{
795
			$email_type = 'notify_boards_once';
796
		}
797
		elseif (empty($data['notify_regularity']))
798
		{
799
			$email_type = 'notify_boards';
800
		}
801
802
		if (!empty($email_type))
803
		{
804
			$email_type .= $this->canSendPostBody($data) ? '_body' : '';
805
		}
806
807
		return $email_type;
808
	}
809
810
	/**
811
	 * A special function for handling the hell which is sending approval notifications. Called
812
	 * when a post is approved in a topic.
813
	 *
814
	 * @param array $topicData
815
	 */
816
	public function sendApprovalNotifications($topicData)
817
	{
818
		global $language;
819
820
		// Clean up the data...
821
		if (!is_array($topicData) || empty($topicData))
0 ignored issues
show
introduced by
The condition is_array($topicData) is always true.
Loading history...
822
		{
823
			return;
824
		}
825
826
		$topics = array_keys($topicData);
827
		if (empty($topics))
828
		{
829
			return;
830
		}
831
832
		// These need to go into the digest too...
833
		$digest_insert = [];
834
		foreach ($topicData as $topic_data)
835
		{
836
			foreach ($topic_data as $data)
837
			{
838
				$digest_insert[] = [$data['topic'], $data['id'], 'reply', 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...
839
			}
840
		}
841
842
		insertLogDigestQueue($digest_insert);
843
844
		// Find everyone who needs to know about this.
845
		$topicNotifications = fetchApprovalNotifications($topics);
846
847
		$sent = 0;
848
		$this->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...
849
		foreach ($topicNotifications as $notifyDatum)
850
		{
851
			// Don't if they don't have notification permissions, they could be pure evil.
852
			$email_perm = true;
853
			if (!validateNotificationAccess($notifyDatum, $this->isUsingMailList(), $email_perm))
854
			{
855
				continue;
856
			}
857
858
			$needed_language = empty($notifyDatum['lngfile']) || empty($this->_modSettings['userLanguage']) ? $language : $notifyDatum['lngfile'];
859
			$this->_checkLanguage($needed_language);
860
861
			$sent_this_time = false;
862
863
			// Now loop through all the messages to send to this member ($notifyDatum)
864
			foreach ($topicData[$notifyDatum['id_topic']] as $msg)
865
			{
866
				$message_type = $this->setMessageTemplate('reply', $notifyDatum);
867
				$replacements = $this->setTemplateReplacements($msg, $notifyDatum, $notifyDatum['id_topic']);
868
869
				// Send only if once
870
				if (empty($notifyDatum['notify_regularity']) || (empty($notifyDatum['sent']) && !$sent_this_time))
871
				{
872
					$template = ($email_perm && $this->canSendPostBody($notifyDatum) ? 'pbe_' : '') . $message_type;
873
					$emaildata = loadEmailTemplate($template, $replacements, $needed_language, true);
874
875
					$sendMail = new BuildMail();
876
					$sendMail->setEmailReplacements($replacements);
877
878
					// If using the maillist functions, we adjust who this is coming from
879
					if ($email_perm && $this->canSendPostBody($notifyDatum))
880
					{
881
						$email_from = $this->_getEmailFrom($msg);
882
						$from_wrapper = $this->_getFromWrapper();
883
884
						$sendMail->buildEmail($notifyDatum['email_address'], $emaildata['subject'], $emaildata['body'], $email_from, 'm' . $msg['id'], true, 3, false, $from_wrapper, $msg['topic']);
885
					}
886
					else
887
					{
888
						$sendMail->buildEmail($notifyDatum['email_address'], $emaildata['subject'], $emaildata['body'], null, 'm' . $msg['id'], true);
889
					}
890
891
					$sent++;
892
				}
893
894
				$sent_this_time = true;
895
			}
896
		}
897
898
		$this->_checkLanguage(User::$info->language);
899
900
		// Sent!
901
		if (!empty($sent))
902
		{
903
			updateLogNotify(User::$info->id, $topics);
904
		}
905
	}
906
}
907