Passed
Pull Request — release-2.1 (#6107)
by John
04:02
created

CreatePost_Notify_Background   F

Complexity

Total Complexity 83

Size/Duplication

Total Lines 497
Duplicated Lines 0 %

Importance

Changes 4
Bugs 2 Features 0
Metric Value
eloc 270
c 4
b 2
f 0
dl 0
loc 497
rs 2
wmc 83

How to fix   Complexity   

Complex Class

Complex classes like CreatePost_Notify_Background often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CreatePost_Notify_Background, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This file contains background notification code for any create post action
4
 *
5
 * Simple Machines Forum (SMF)
6
 *
7
 * @package SMF
8
 * @author Simple Machines https://www.simplemachines.org
9
 * @copyright 2020 Simple Machines and individual contributors
10
 * @license https://www.simplemachines.org/about/smf/license.php BSD
11
 *
12
 * @version 2.1 RC2
13
 */
14
15
/**
16
 * Class CreatePost_Notify_Background
17
 */
18
class CreatePost_Notify_Background extends SMF_BackgroundTask
19
{
20
	/**
21
	 * Constants for reply types.
22
	*/
23
	const NOTIFY_TYPE_REPLY_AND_MODIFY = 1;
24
	const NOTIFY_TYPE_REPLY_AND_TOPIC_START_FOLLOWING = 2;
25
	const NOTIFY_TYPE_ONLY_REPLIES = 3;
26
	const NOTIFY_TYPE_NOTHING = 4;
27
28
	/**
29
	 * Constants for frequencies.
30
	*/
31
	const FREQUENCY_NOTHING = 0;
32
	const FREQUENCY_EVERYTHING = 1;
33
	const FREQUENCY_FIRST_UNREAD_MSG = 2;
34
	const FREQUENCY_DAILY_DIGEST = 3;
35
	const FREQUENCY_WEEKLY_DIGEST = 4;
36
37
	/**
38
	 * This handles notifications when a new post is created - new topic, reply, quotes and mentions.
39
	 * @return bool Always returns true
40
	 * @throws Exception
41
	 */
42
	public function execute()
43
	{
44
		global $smcFunc, $sourcedir, $scripturl, $language, $modSettings, $user_info;
45
46
		require_once($sourcedir . '/Subs-Post.php');
47
		require_once($sourcedir . '/Mentions.php');
48
		require_once($sourcedir . '/Subs-Notify.php');
49
		require_once($sourcedir . '/Subs.php');
50
		require_once($sourcedir . '/ScheduledTasks.php');
51
		loadEssentialThemeData();
52
53
		$msgOptions = $this->_details['msgOptions'];
54
		$topicOptions = $this->_details['topicOptions'];
55
		$posterOptions = $this->_details['posterOptions'];
56
		$type = $this->_details['type'];
57
58
		$members = array();
59
		$quotedMembers = array();
60
		$done_members = array();
61
		$alert_rows = array();
62
		$receiving_members = array();
63
64
		if ($type == 'reply' || $type == 'topic')
65
		{
66
			$quotedMembers = self::getQuotedMembers($msgOptions, $posterOptions);
67
			$members = array_keys($quotedMembers);
68
		}
69
70
		// Insert the post mentions
71
		if (!empty($msgOptions['mentioned_members']))
72
		{
73
			Mentions::insertMentions('msg', $msgOptions['id'], $msgOptions['mentioned_members'], $posterOptions['id']);
74
			$members = array_merge($members, array_keys($msgOptions['mentioned_members']));
75
		}
76
77
		// Find the people interested in receiving notifications for this topic
78
		$request = $smcFunc['db_query']('', '
79
			SELECT
80
				ln.id_member, ln.id_board, ln.id_topic, ln.sent,
81
				mem.email_address, mem.lngfile, mem.pm_ignore_list,
82
				mem.id_group, mem.id_post_group, mem.additional_groups,
83
				mem.time_format, mem.time_offset, mem.timezone,
84
				b.member_groups, t.id_member_started, t.id_member_updated
85
			FROM {db_prefix}log_notify AS ln
86
				INNER JOIN {db_prefix}members AS mem ON (ln.id_member = mem.id_member)
87
				LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = ln.id_topic)
88
				LEFT JOIN {db_prefix}boards AS b ON (b.id_board = ln.id_board OR b.id_board = t.id_board)
89
			WHERE ln.id_member != {int:member}
90
				AND (ln.id_topic = {int:topic} OR ln.id_board = {int:board})',
91
			array(
92
				'member' => $posterOptions['id'],
93
				'topic' => $topicOptions['id'],
94
				'board' => $topicOptions['board'],
95
			)
96
		);
97
98
		$watched = array();
99
		while ($row = $smcFunc['db_fetch_assoc']($request))
100
		{
101
			$groups = array_merge(array($row['id_group'], $row['id_post_group']), (empty($row['additional_groups']) ? array() : explode(',', $row['additional_groups'])));
102
103
			if (!in_array(1, $groups) && count(array_intersect($groups, explode(',', $row['member_groups']))) == 0)
104
				continue;
105
			else
106
			{
107
				$row['groups'] = $groups;
108
				unset($row['id_group'], $row['id_post_group'], $row['additional_groups']);
109
			}
110
111
			$members[] = $row['id_member'];
112
			$watched[$row['id_member']] = $row;
113
		}
114
115
		$smcFunc['db_free_result']($request);
116
117
		// Modified post
118
		if ($type == 'edit')
119
		{
120
			// Filter out members who have already been notified about this post's topic
121
			$unnotified = array_filter($watched, function ($member)
122
			{
123
				return empty($member['sent']);
124
			});
125
			$members = array_intersect($members, array_keys($unnotified));
126
			$quotedMembers = array_intersect_key($quotedMembers, $unnotified);
127
			$msgOptions['mentioned_members'] = array_intersect_key($msgOptions['mentioned_members'], $unnotified);
128
129
			// Notifications about modified posts only go to members who were mentioned or quoted
130
			$watched = array();
131
		}
132
133
		if (empty($members))
134
			return true;
135
136
		$members = array_unique($members);
137
		$members_info = $this->getMinUserInfo($members);
138
139
		$prefs = getNotifyPrefs($members, '', true);
140
141
		// May as well disable these, since they'll be stripped out anyway.
142
		$disable = array('attach', 'img', 'iurl', 'url', 'youtube');
143
		if (!empty($modSettings['disabledBBC']))
144
		{
145
			$disabledBBC = $modSettings['disabledBBC'];
146
			$disable = array_unique(array_merge($disable, explode(',', $modSettings['disabledBBC'])));
147
		}
148
		$modSettings['disabledBBC'] = implode(',', $disable);
149
150
		// Do we have anyone to notify via mention? Handle them first and cross them off the list
151
		if (!empty($msgOptions['mentioned_members']))
152
		{
153
			$mentioned_members = Mentions::getMentionsByContent('msg', $msgOptions['id'], array_keys($msgOptions['mentioned_members']));
154
			self::handleMentionedNotifications($msgOptions, $mentioned_members, $prefs, $done_members, $alert_rows);
155
		}
156
157
		// Notify members which might've been quoted
158
		self::handleQuoteNotifications($msgOptions, $posterOptions, $quotedMembers, $prefs, $done_members, $alert_rows);
159
160
		// Save ourselves a bit of work in the big loop below
161
		foreach ($done_members as $done_member)
162
		{
163
			$receiving_members[] = $done_member;
164
			unset($watched[$done_member]);
165
		}
166
167
		$parsed_message = array();
168
169
		// Handle rest of the notifications for watched topics and boards
170
		foreach ($watched as $member_id => $member_data)
171
		{
172
			$frequency = isset($prefs[$member_id]['msg_notify_pref']) ? $prefs[$member_id]['msg_notify_pref'] : self::FREQUENCY_NOTHING;
173
			$notify_types = !empty($prefs[$member_id]['msg_notify_type']) ? $prefs[$member_id]['msg_notify_type'] : self::NOTIFY_TYPE_REPLY_AND_MODIFY;
174
175
			// Don't send a notification if the watching member ignored the member who made the action.
176
			if (!empty($member_data['pm_ignore_list']) && in_array($member_data['id_member_updated'], explode(',', $member_data['pm_ignore_list'])))
177
				continue;
178
179
			if (!in_array($type, array('reply', 'topic')) && $notify_types == self::NOTIFY_TYPE_REPLY_AND_TOPIC_START_FOLLOWING && $member_id != $member_data['id_member_started'])
180
				continue;
181
182
			elseif (!in_array($type, array('reply', 'topic')) && $notify_types == self::NOTIFY_TYPE_ONLY_REPLIES)
183
				continue;
184
185
			elseif ($notify_types == self::NOTIFY_TYPE_NOTHING)
186
				continue;
187
188
			// Don't send a notification if they don't want any...
189
			if (in_array($frequency, array(
190
				self::FREQUENCY_NOTHING,
191
				self::FREQUENCY_DAILY_DIGEST,
192
				self::FREQUENCY_WEEKLY_DIGEST)))
193
				continue;
194
195
			// ... or if we already sent one and they don't want more...
196
			elseif ($frequency == self::FREQUENCY_FIRST_UNREAD_MSG && $member_data['sent'])
197
				continue;
198
199
			// ... or if they aren't on the bouncer's list.
200
			elseif (!empty($this->_details['members_only']) && !in_array($member_id, $this->_details['members_only']))
201
				continue;
202
203
			// Watched topic?
204
			if (!empty($member_data['id_topic']) && $type != 'topic' && !empty($prefs[$member_id]))
205
			{
206
				$pref = !empty($prefs[$member_id]['topic_notify_' . $topicOptions['id']]) ?
207
					$prefs[$member_id]['topic_notify_' . $topicOptions['id']] :
208
					(!empty($prefs[$member_id]['topic_notify']) ? $prefs[$member_id]['topic_notify'] : 0);
209
210
				$message_type = 'notification_' . $type;
211
212
				if ($type == 'reply')
213
				{
214
					if (!empty($prefs[$member_id]['msg_receive_body']))
215
						$message_type .= '_body';
216
217
					if (!empty($frequency))
218
						$message_type .= '_once';
219
				}
220
221
				$content_type = 'topic';
222
			}
223
			// A new topic in a watched board then?
224
			elseif ($type == 'topic')
225
			{
226
				$pref = !empty($prefs[$member_id]['board_notify_' . $topicOptions['board']]) ?
227
					$prefs[$member_id]['board_notify_' . $topicOptions['board']] :
228
					(!empty($prefs[$member_id]['board_notify']) ? $prefs[$member_id]['board_notify'] : 0);
229
230
				$content_type = 'board';
231
232
				$message_type = !empty($frequency) ? 'notify_boards_once' : 'notify_boards';
233
234
				if (!empty($prefs[$member_id]['msg_receive_body']))
235
					$message_type .= '_body';
236
			}
237
238
			// If neither of the above, this might be a redundant row due to the OR clause in our SQL query, skip
239
			else
240
				continue;
241
242
			// We need to fake some of $user_info to make BBC parsing work correctly.
243
			if (isset($user_info))
244
				$real_user_info = $user_info;
245
246
			$user_info = $members_info[$member_id];
247
248
			// Censor and parse BBC in the receiver's localization. Don't repeat unnecessarily.
249
			loadLanguage('index+Modifications', $receiver_lang, false);
250
251
			censorText($msgOptions['subject']);
252
			censorText($msgOptions['body']);
253
254
			$msgOptions['subject'] = un_htmlspecialchars($msgOptions['subject']);
255
			$msgOptions['body'] = trim(
256
				un_htmlspecialchars(
257
					strip_tags(
258
						strtr(
259
							parse_bbc($msgOptions['body'], false),
260
							array(
261
								'<br>' => "\n",
262
								'</div>' => "\n",
263
								'</li>' => "\n",
264
								'&#91;' => '[',
265
								'&#93;' => ']',
266
								'&#39;' => '\'',
267
								'</tr>' => "\n",
268
								'</td>' => "\t",
269
								'<hr>' => "\n---------------------------------------------------------------\n",
270
							)
271
						)
272
					)
273
				)
274
			);
275
276
			// Put $user_info back the way we found it.
277
			if (isset($real_user_info))
278
			{
279
				$user_info = $real_user_info;
280
				unset($real_user_info);
281
			}
282
283
			else
284
				$user_info = null;
285
286
			// Bitwise check: Receiving a email notification?
287
			if ($pref & self::RECEIVE_NOTIFY_EMAIL)
288
			{
289
				$itemID = $content_type == 'board' ? $topicOptions['board'] : $topicOptions['id'];
290
291
				$token = createUnsubscribeToken($member_data['id_member'], $member_data['email_address'], $content_type, $itemID);
292
293
				$replacements = array(
294
					'TOPICSUBJECT' => $msgOptions['subject'],
295
					'POSTERNAME' => un_htmlspecialchars($posterOptions['name']),
296
					'TOPICLINK' => $scripturl . '?topic=' . $topicOptions['id'] . '.new#new',
297
					'MESSAGE' => $msgOptions['body'],
298
					'UNSUBSCRIBELINK' => $scripturl . '?action=notify' . $content_type . ';' . $content_type . '=' . $itemID . ';sa=off;u=' . $data['id_member'] . ';token=' . $token,
299
				);
300
301
				$emaildata = loadEmailTemplate($message_type, $replacements, $member_data['lngfile']);
302
				$mail_result = sendmail($member_data['email_address'], $emaildata['subject'], $emaildata['body'], null, 'm' . $topicOptions['id'], $emaildata['is_html']);
303
304
				// We failed, don't trigger a alert as we don't have a way to attempt to resend just the email currently.
305
				if ($mail_result === false)
306
					continue;
307
			}
308
309
			// Bitwise check: Receiving a alert?
310
			if ($pref & self::RECEIVE_NOTIFY_ALERT)
311
			{
312
				$alert_rows[] = array(
313
					'alert_time' => time(),
314
					'id_member' => $member_id,
315
					// Only tell sender's information for new topics and replies
316
					'id_member_started' => in_array($type, array('topic', 'reply')) ? $posterOptions['id'] : 0,
317
					'member_name' => in_array($type, array('topic', 'reply')) ? $posterOptions['name'] : '',
318
					'content_type' => $content_type,
319
					'content_id' => $topicOptions['id'],
320
					'content_action' => $type,
321
					'is_read' => 0,
322
					'extra' => $smcFunc['json_encode'](
323
						array(
324
							'topic' => $topicOptions['id'],
325
							'board' => $topicOptions['board'],
326
							'content_subject' => $msgOptions['subject'],
327
							'content_link' => sprintf(
328
								'%s?topic=%d%s',
329
								$scripturl,
330
								$topicOptions['id'],
331
								in_array($type, array('reply', 'topic')) ? '.new;topicseen#new' : '.0'
332
							),
333
						),
334
				);
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected ';', expecting ',' or ')' on line 334 at column 5
Loading history...
335
336
				$receiving_members[] = $member_id;
337
			}
338
339
			$smcFunc['db_query']('', '
340
				UPDATE {db_prefix}log_notify
341
				SET sent = {int:is_sent}
342
				WHERE (id_topic = {int:topic} OR id_board = {int:board})
343
					AND id_member = {int:member}',
344
				array(
345
					'topic' => $topicOptions['id'],
346
					'board' => $topicOptions['board'],
347
					'member' => $member_id,
348
					'is_sent' => 1,
349
				)
350
			);
351
		}
352
353
		// Put this back the way we found it.
354
		if (!empty($disabledBBC))
355
			$modSettings['disabledBBC'] = $disabledBBC;
356
357
		// Insert it into the digest for daily/weekly notifications
358
		$smcFunc['db_insert']('',
359
			'{db_prefix}log_digest',
360
			array(
361
				'id_topic' => 'int', 'id_msg' => 'int', 'note_type' => 'string', 'exclude' => 'int',
362
			),
363
			array($topicOptions['id'], $msgOptions['id'], $type, $posterOptions['id']),
364
			array()
365
		);
366
367
		// Insert the alerts if any
368
		if (!empty($alert_rows))
369
			$smcFunc['db_insert']('',
370
				'{db_prefix}user_alerts',
371
				array('alert_time' => 'int', 'id_member' => 'int', 'id_member_started' => 'int', 'member_name' => 'string',
372
					'content_type' => 'string', 'content_id' => 'int', 'content_action' => 'string', 'is_read' => 'int', 'extra' => 'string'),
373
				$alert_rows,
374
				array()
375
			);
376
377
		if (!empty($receiving_members))
378
			updateMemberData($receiving_members, array('alerts' => '+'));
379
380
		return true;
381
	}
382
383
	protected static function handleQuoteNotifications($msgOptions, $posterOptions, $quotedMembers, $prefs, &$done_members, &$alert_rows)
384
	{
385
		global $smcFunc, $modSettings, $language, $scripturl;
386
387
		foreach ($quotedMembers as $id => $member)
388
		{
389
			if (!isset($prefs[$id]) || $id == $posterOptions['id'] || empty($prefs[$id]['msg_quote']))
390
				continue;
391
392
			$done_members[] = $id;
393
394
			// Bitwise check: Receiving a email notification?
395
			if ($prefs[$id]['msg_quote'] & self::RECEIVE_NOTIFY_EMAIL)
396
			{
397
				$replacements = array(
398
					'CONTENTSUBJECT' => $msgOptions['subject'],
399
					'QUOTENAME' => $posterOptions['name'],
400
					'MEMBERNAME' => $member['real_name'],
401
					'CONTENTLINK' => $scripturl . '?msg=' . $msgOptions['id'],
402
				);
403
404
				$emaildata = loadEmailTemplate('msg_quote', $replacements, empty($member['lngfile']) || empty($modSettings['userLanguage']) ? $language : $member['lngfile']);
405
				sendmail($member['email_address'], $emaildata['subject'], $emaildata['body'], null, 'msg_quote_' . $msgOptions['id'], $emaildata['is_html'], 2);
406
			}
407
408
			// Bitwise check: Receiving a alert?
409
			if ($prefs[$id]['msg_quote'] & self::RECEIVE_NOTIFY_ALERT)
410
			{
411
				$alert_rows[] = array(
412
					'alert_time' => time(),
413
					'id_member' => $member['id_member'],
414
					'id_member_started' => $posterOptions['id'],
415
					'member_name' => $posterOptions['name'],
416
					'content_type' => 'msg',
417
					'content_id' => $msgOptions['id'],
418
					'content_action' => 'quote',
419
					'is_read' => 0,
420
					'extra' => $smcFunc['json_encode'](array(
421
						'content_subject' => $msgOptions['subject'],
422
						'content_link' => $scripturl . '?msg=' . $msgOptions['id'],
423
					)),
424
				);
425
			}
426
		}
427
	}
428
429
	protected static function getQuotedMembers($msgOptions, $posterOptions)
430
	{
431
		global $smcFunc;
432
433
		$blocks = preg_split('/(\[quote.*?\]|\[\/quote\])/i', $msgOptions['body'], -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
434
435
		$quote_level = 0;
436
		$message = '';
437
438
		foreach ($blocks as $block)
439
		{
440
			if (preg_match('/\[quote(.*)?\]/i', $block, $matches))
441
			{
442
				if ($quote_level == 0)
443
					$message .= '[quote' . $matches[1] . ']';
444
				$quote_level++;
445
			}
446
			elseif (preg_match('/\[\/quote\]/i', $block))
447
			{
448
				if ($quote_level <= 1)
449
					$message .= '[/quote]';
450
				if ($quote_level >= 1)
451
				{
452
					$quote_level--;
453
					$message .= "\n";
454
				}
455
			}
456
			elseif ($quote_level <= 1)
457
				$message .= $block;
458
		}
459
460
		preg_match_all('/\[quote.*?link=msg=([0-9]+).*?\]/i', $message, $matches);
461
462
		$id_msgs = $matches[1];
463
		foreach ($id_msgs as $k => $id_msg)
464
			$id_msgs[$k] = (int) $id_msg;
465
466
		if (empty($id_msgs))
467
			return array();
468
469
		// Get the messages
470
		$request = $smcFunc['db_query']('', '
471
			SELECT m.id_member, mem.email_address, mem.lngfile, mem.real_name
472
			FROM {db_prefix}messages AS m
473
				INNER JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
474
			WHERE id_msg IN ({array_int:msgs})
475
			LIMIT {int:count}',
476
			array(
477
				'msgs' => array_unique($id_msgs),
478
				'count' => count(array_unique($id_msgs)),
479
			)
480
		);
481
482
		$members = array();
483
		while ($row = $smcFunc['db_fetch_assoc']($request))
484
		{
485
			if ($posterOptions['id'] == $row['id_member'])
486
				continue;
487
488
			$members[$row['id_member']] = $row;
489
		}
490
491
		return $members;
492
	}
493
494
	protected static function handleMentionedNotifications($msgOptions, $members, $prefs, &$done_members, &$alert_rows)
495
	{
496
		global $smcFunc, $scripturl, $language, $modSettings;
497
498
		foreach ($members as $id => $member)
499
		{
500
			if (!empty($prefs[$id]['msg_mention']))
501
				$done_members[] = $id;
502
			else
503
				continue;
504
505
			// Alerts' emails are always instant
506
			if ($prefs[$id]['msg_mention'] & self::RECEIVE_NOTIFY_EMAIL)
507
			{
508
				$replacements = array(
509
					'CONTENTSUBJECT' => $msgOptions['subject'],
510
					'MENTIONNAME' => $member['mentioned_by']['name'],
511
					'MEMBERNAME' => $member['real_name'],
512
					'CONTENTLINK' => $scripturl . '?msg=' . $msgOptions['id'],
513
				);
514
515
				$emaildata = loadEmailTemplate('msg_mention', $replacements, empty($member['lngfile']) || empty($modSettings['userLanguage']) ? $language : $member['lngfile']);
516
				sendmail($member['email_address'], $emaildata['subject'], $emaildata['body'], null, 'msg_mention_' . $msgOptions['id'], $emaildata['is_html'], 2);
517
			}
518
519
			if ($prefs[$id]['msg_mention'] & self::RECEIVE_NOTIFY_ALERT)
520
			{
521
				$alert_rows[] = array(
522
					'alert_time' => time(),
523
					'id_member' => $member['id'],
524
					'id_member_started' => $member['mentioned_by']['id'],
525
					'member_name' => $member['mentioned_by']['name'],
526
					'content_type' => 'msg',
527
					'content_id' => $msgOptions['id'],
528
					'content_action' => 'mention',
529
					'is_read' => 0,
530
					'extra' => $smcFunc['json_encode'](array(
531
						'content_subject' => $msgOptions['subject'],
532
						'content_link' => $scripturl . '?msg=' . $msgOptions['id'],
533
					)),
534
				);
535
			}
536
		}
537
	}
538
}
539
540
?>