CreatePost_Notify_Background   F
last analyzed

Complexity

Total Complexity 88

Size/Duplication

Total Lines 495
Duplicated Lines 0 %

Importance

Changes 8
Bugs 0 Features 0
Metric Value
eloc 276
c 8
b 0
f 0
dl 0
loc 495
rs 2
wmc 88

4 Methods

Rating   Name   Duplication   Size   Complexity  
B handleQuoteNotifications() 0 40 9
F execute() 0 320 60
C getQuotedMembers() 0 63 12
B handleMentionedNotifications() 0 39 7

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