Passed
Push — release-2.1 ( 0c2197...207d2d )
by Jeremy
05:47
created

handleQuoteNotifications()   B

Complexity

Conditions 9
Paths 6

Size

Total Lines 44
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 27
c 0
b 0
f 0
nop 6
dl 0
loc 44
rs 8.0555
nc 6
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 2018 Simple Machines and individual contributors
10
 * @license http://www.simplemachines.org/about/smf/license.php BSD
11
 *
12
 * @version 2.1 Beta 4
13
 */
14
15
/**
16
 * Class CreatePost_Notify_Background
17
 */
18
class CreatePost_Notify_Background extends SMF_BackgroundTask
19
{
20
	/**
21
	 * Constants for receiving email notfications.
22
	*/
23
	const RECEIVE_NOTIFY_EMAIL = 0x02;
24
	const RECEIVE_NOTIFY_ALERT = 0x01;
25
26
	/**
27
	 * Constants for reply types.
28
	*/
29
	const NOTIFY_TYPE_REPLY_AND_MODIFY = 1;
30
	const NOTIFY_TYPE_REPLY_AND_TOPIC_START_FOLLOWING = 2;
31
	const NOTIFY_TYPE_ONLY_REPLIES = 3;
32
	const NOTIFY_TYPE_NOTHING = 4;
33
34
	/**
35
	 * Constants for frequencies.
36
	*/
37
	const FREQUENCY_NOTHING = 0;
38
	const FREQUENCY_EVERYTHING = 1;
39
	const FREQUENCY_FIRST_UNREAD_MSG = 2;
40
	const FREQUENCY_DAILY_DIGEST = 3;
41
	const FREQUENCY_WEEKLY_DIGEST = 4;
42
43
	/**
44
     * This handles notifications when a new post is created - new topic, reply, quotes and mentions.
45
	 * @return bool Always returns true
46
	 */
47
	public function execute()
48
	{
49
		global $smcFunc, $sourcedir, $scripturl, $language, $modSettings;
50
51
		require_once($sourcedir . '/Subs-Post.php');
52
		require_once($sourcedir . '/Mentions.php');
53
		require_once($sourcedir . '/Subs-Notify.php');
54
55
		$msgOptions = $this->_details['msgOptions'];
56
		$topicOptions = $this->_details['topicOptions'];
57
		$posterOptions = $this->_details['posterOptions'];
58
		$type = $this->_details['type'];
59
60
		$members = array();
61
		$quotedMembers = array();
62
		$done_members = array();
63
		$alert_rows = array();
64
65
		if ($type == 'reply' || $type == 'topic')
66
		{
67
			$quotedMembers = self::getQuotedMembers($msgOptions, $posterOptions);
68
			$members = array_keys($quotedMembers);
69
		}
70
71
		// Insert the post mentions
72
		if (!empty($msgOptions['mentioned_members']))
73
		{
74
			Mentions::insertMentions('msg', $msgOptions['id'], $msgOptions['mentioned_members'], $posterOptions['id']);
75
			$members = array_merge($members, array_keys($msgOptions['mentioned_members']));
76
		}
77
78
		// Find the people interested in receiving notifications for this topic
79
		$request = $smcFunc['db_query']('', '
80
			SELECT mem.id_member, ln.id_topic, ln.id_board, ln.sent, mem.email_address, mem.lngfile, b.member_groups,
81
				mem.id_group, mem.id_post_group, mem.additional_groups, t.id_member_started, mem.pm_ignore_list,
82
				t.id_member_updated
83
			FROM {db_prefix}log_notify AS ln
84
				INNER JOIN {db_prefix}members AS mem ON (ln.id_member = mem.id_member)
85
				LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = ln.id_topic)
86
				LEFT JOIN {db_prefix}boards AS b ON (b.id_board = ln.id_board OR b.id_board = t.id_board)
87
			WHERE ln.id_topic = {int:topic}
88
				OR ln.id_board = {int:board}',
89
			array(
90
				'topic' => $topicOptions['id'],
91
				'board' => $topicOptions['board'],
92
			)
93
		);
94
95
		$watched = array();
96
		while ($row = $smcFunc['db_fetch_assoc']($request))
97
		{
98
			$groups = array_merge(array($row['id_group'], $row['id_post_group']), (empty($row['additional_groups']) ? array() : explode(',', $row['additional_groups'])));
99
			if (!in_array(1, $groups) && count(array_intersect($groups, explode(',', $row['member_groups']))) == 0)
100
				continue;
101
102
			$members[] = $row['id_member'];
103
			$watched[$row['id_member']] = $row;
104
		}
105
106
		$smcFunc['db_free_result']($request);
107
108
		if (empty($members))
109
			return true;
110
111
		$members = array_unique($members);
112
		$prefs = getNotifyPrefs($members, '', true);
113
114
		// Do we have anyone to notify via mention? Handle them first and cross them off the list
115
		if (!empty($msgOptions['mentioned_members']))
116
		{
117
			$mentioned_members = Mentions::getMentionsByContent('msg', $msgOptions['id'], array_keys($msgOptions['mentioned_members']));
118
			self::handleMentionedNotifications($msgOptions, $mentioned_members, $prefs, $done_members, $alert_rows);
119
		}
120
121
		// Notify members which might've been quoted
122
		self::handleQuoteNotifications($msgOptions, $posterOptions, $quotedMembers, $prefs, $done_members, $alert_rows);
123
124
		// Save ourselves a bit of work in the big loop below
125
		foreach ($done_members as $done_member)
126
			unset($watched[$done_member]);
127
128
		// Handle rest of the notifications for watched topics and boards
129
		foreach ($watched as $member => $data)
130
		{
131
			$frequency = isset($prefs[$member]['msg_notify_pref']) ? $prefs[$member]['msg_notify_pref'] : self::FREQUENCY_NOTHING;
132
			$notify_types = !empty($prefs[$member]['msg_notify_type']) ? $prefs[$member]['msg_notify_type'] : self::NOTIFY_TYPE_REPLY_AND_MODIFY;
133
134
			// Don't send a notification if the watching member ignored the member who made the action.
135
			if (!empty($data['pm_ignore_list']) && in_array($data['id_member_updated'], explode(',', $data['pm_ignore_list'])))
136
				continue;
137
			if (!in_array($type, array('reply', 'topic')) && $notify_types == self::NOTIFY_TYPE_REPLY_AND_TOPIC_START_FOLLOWING && $member != $data['id_member_started'])
138
				continue;
139
			elseif (in_array($type, array('reply', 'topic')) && $member == $posterOptions['id'])
140
				continue;
141
			elseif (!in_array($type, array('reply', 'topic')) && $notify_types == self::NOTIFY_TYPE_ONLY_REPLIES)
142
				continue;
143
			elseif ($notify_types == self::NOTIFY_TYPE_NOTHING)
144
				continue;
145
146
			// Don't send a notification if they don't want any...
147
			if (in_array($frequency, array(self::FREQUENCY_NOTHING, self::FREQUENCY_DAILY_DIGEST, self::FREQUENCY_WEEKLY_DIGEST)))
148
				continue;
149
			// ... or if we already sent one and they don't want more...
150
			elseif ($frequency === self::FREQUENCY_FIRST_UNREAD_MSG && $data['sent'])
151
				continue;
152
			// ... or if they aren't on the bouncer's list.
153
			elseif (!empty($this->_details['members_only']) && !in_array($member, $this->_details['members_only']))
154
				continue;
155
156
			// Watched topic?
157
			if (!empty($data['id_topic']) && $type != 'topic' && !empty($prefs[$member]))
158
			{
159
				$pref = !empty($prefs[$member]['topic_notify_' . $topicOptions['id']]) ? $prefs[$member]['topic_notify_' . $topicOptions['id']] : (!empty($prefs[$member]['topic_notify']) ? $prefs[$member]['topic_notify'] : 0);
160
				$message_type = 'notification_' . $type;
161
162
				if ($type == 'reply')
163
				{
164
					if (!empty($prefs[$member]['msg_receive_body']))
165
						$message_type .= '_body';
166
					if (!empty($frequency))
167
						$message_type .= '_once';
168
				}
169
170
				$content_type = 'topic';
171
			}
172
			// A new topic in a watched board then?
173
			elseif ($type == 'topic')
174
			{
175
				$pref = !empty($prefs[$member]['board_notify_' . $topicOptions['board']]) ? $prefs[$member]['board_notify_' . $topicOptions['board']] : (!empty($prefs[$member]['board_notify']) ? $prefs[$member]['board_notify'] : 0);
176
177
				$content_type = 'board';
178
179
				$message_type = !empty($frequency) ? 'notify_boards_once' : 'notify_boards';
180
				if (!empty($prefs[$member]['msg_receive_body']))
181
					$message_type .= '_body';
182
			}
183
			// If neither of the above, this might be a redundent row due to the OR clause in our SQL query, skip
184
			else
185
				continue;
186
187
			// Bitwise check: Receiving a email notification?
188
			if ($pref & self::RECEIVE_NOTIFY_EMAIL)
189
			{
190
				$replacements = array(
191
					'TOPICSUBJECT' => $msgOptions['subject'],
192
					'POSTERNAME' => un_htmlspecialchars($posterOptions['name']),
193
					'TOPICLINK' => $scripturl . '?topic=' . $topicOptions['id'] . '.new#new',
194
					'MESSAGE' => trim(un_htmlspecialchars(strip_tags(strtr(parse_bbc(un_preparsecode($msgOptions['body']), false), array('<br>' => "\n", '</div>' => "\n", '</li>' => "\n", '&#91;' => '[', '&#93;' => ']', '&#39;' => '\''))))),
195
					'UNSUBSCRIBELINK' => $scripturl . '?action=notifyboard;board=' . $topicOptions['board'] . '.0',
196
				);
197
198
				$emaildata = loadEmailTemplate($message_type, $replacements, empty($data['lngfile']) || empty($modSettings['userLanguage']) ? $language : $data['lngfile']);
199
				$mail_result = sendmail($data['email_address'], $emaildata['subject'], $emaildata['body'], null, 'm' . $topicOptions['id'], $emaildata['is_html']);
200
201
				// We failed, don't trigger a alert as we don't have a way to attempt to resend just the email currently.
202
				if ($mail_result === false)
203
					continue;
204
			}
205
206
			// Bitwise check: Receiving a alert?
207
			if ($pref & self::RECEIVE_NOTIFY_ALERT)
208
			{
209
				$alert_rows[] = array(
210
					'alert_time' => time(),
211
					'id_member' => $member,
212
					// Only tell sender's information for new topics and replies
213
					'id_member_started' => in_array($type, array('topic', 'reply')) ? $posterOptions['id'] : 0,
214
					'member_name' => in_array($type, array('topic', 'reply')) ? $posterOptions['name'] : '',
215
					'content_type' => $content_type,
216
					'content_id' => $topicOptions['id'],
217
					'content_action' => $type,
218
					'is_read' => 0,
219
					'extra' => $smcFunc['json_encode'](array(
220
						'topic' => $topicOptions['id'],
221
						'board' => $topicOptions['board'],
222
						'content_subject' => $msgOptions['subject'],
223
						'content_link' => $scripturl . '?topic=' . $topicOptions['id'] . '.new;topicseen#new',
224
					)),
225
				);
226
				updateMemberData($member, array('alerts' => '+'));
227
			}
228
229
			$smcFunc['db_query']('', '
230
				UPDATE {db_prefix}log_notify
231
				SET sent = {int:is_sent}
232
				WHERE (id_topic = {int:topic} OR id_board = {int:board})
233
					AND id_member = {int:member}',
234
				array(
235
					'topic' => $topicOptions['id'],
236
					'board' => $topicOptions['board'],
237
					'member' => $member,
238
					'is_sent' => 1,
239
				)
240
			);
241
		}
242
243
		// Insert it into the digest for daily/weekly notifications
244
		$smcFunc['db_insert']('',
245
			'{db_prefix}log_digest',
246
			array(
247
				'id_topic' => 'int', 'id_msg' => 'int', 'note_type' => 'string', 'exclude' => 'int',
248
			),
249
			array($topicOptions['id'], $msgOptions['id'], $type, $posterOptions['id']),
250
			array()
251
		);
252
253
		// Insert the alerts if any
254
		if (!empty($alert_rows))
255
			$smcFunc['db_insert']('',
256
				'{db_prefix}user_alerts',
257
				array('alert_time' => 'int', 'id_member' => 'int', 'id_member_started' => 'int', 'member_name' => 'string',
258
					'content_type' => 'string', 'content_id' => 'int', 'content_action' => 'string', 'is_read' => 'int', 'extra' => 'string'),
259
				$alert_rows,
260
				array()
261
			);
262
263
		return true;
264
	}
265
266
	protected static function handleQuoteNotifications($msgOptions, $posterOptions, $quotedMembers, $prefs, &$done_members, &$alert_rows)
267
	{
268
		global $smcFunc, $modSettings, $language, $scripturl;
269
270
		foreach ($quotedMembers as $id => $member)
271
		{
272
			if (!isset($prefs[$id]) || $id == $posterOptions['id'] || empty($prefs[$id]['msg_quote']))
273
				continue;
274
275
			$done_members[] = $id;
276
277
			// Bitwise check: Receiving a email notification?
278
			if ($prefs[$id]['msg_quote'] & self::RECEIVE_NOTIFY_EMAIL)
279
			{
280
				$replacements = array(
281
					'CONTENTSUBJECT' => $msgOptions['subject'],
282
					'QUOTENAME' => $posterOptions['name'],
283
					'MEMBERNAME' => $member['real_name'],
284
					'CONTENTLINK' => $scripturl . '?msg=' . $msgOptions['id'],
285
				);
286
287
				$emaildata = loadEmailTemplate('msg_quote', $replacements, empty($member['lngfile']) || empty($modSettings['userLanguage']) ? $language : $member['lngfile']);
288
				sendmail($member['email_address'], $emaildata['subject'], $emaildata['body'], null, 'msg_quote_' . $msgOptions['id'], $emaildata['is_html'], 2);
289
			}
290
291
			// Bitwise check: Receiving a alert?
292
			if ($prefs[$id]['msg_quote'] & self::RECEIVE_NOTIFY_ALERT)
293
			{
294
				$alert_rows[] = array(
295
					'alert_time' => time(),
296
					'id_member' => $member['id_member'],
297
					'id_member_started' => $posterOptions['id'],
298
					'member_name' => $posterOptions['name'],
299
					'content_type' => 'msg',
300
					'content_id' => $msgOptions['id'],
301
					'content_action' => 'quote',
302
					'is_read' => 0,
303
					'extra' => $smcFunc['json_encode'](array(
304
						'content_subject' => $msgOptions['subject'],
305
						'content_link' => $scripturl . '?msg=' . $msgOptions['id'],
306
					)),
307
				);
308
309
				updateMemberData($member['id_member'], array('alerts' => '+'));
310
			}
311
		}
312
	}
313
314
	protected static function getQuotedMembers($msgOptions, $posterOptions)
315
	{
316
		global $smcFunc;
317
318
		$blocks = preg_split('/(\[quote.*?\]|\[\/quote\])/i', $msgOptions['body'], -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
319
320
		$quote_level = 0;
321
		$message = '';
322
323
		foreach ($blocks as $block)
324
		{
325
			if (preg_match('/\[quote(.*)?\]/i', $block, $matches))
326
			{
327
				if ($quote_level == 0)
328
					$message .= '[quote' . $matches[1] . ']';
329
				$quote_level++;
330
			}
331
			elseif (preg_match('/\[\/quote\]/i', $block))
332
			{
333
				if ($quote_level <= 1)
334
					$message .= '[/quote]';
335
				if ($quote_level >= 1)
336
				{
337
					$quote_level--;
338
					$message .= "\n";
339
				}
340
			}
341
			elseif ($quote_level <= 1)
342
				$message .= $block;
343
		}
344
345
		preg_match_all('/\[quote.*?link=msg=([0-9]+).*?\]/i', $message, $matches);
346
347
		$id_msgs = $matches[1];
348
		foreach ($id_msgs as $k => $id_msg)
349
			$id_msgs[$k] = (int) $id_msg;
350
351
		if (empty($id_msgs))
352
			return array();
353
354
		// Get the messages
355
		$request = $smcFunc['db_query']('', '
356
			SELECT m.id_member, mem.email_address, mem.lngfile, mem.real_name
357
			FROM {db_prefix}messages AS m
358
				INNER JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
359
			WHERE id_msg IN ({array_int:msgs})
360
			LIMIT {int:count}',
361
			array(
362
				'msgs' => array_unique($id_msgs),
363
				'count' => count(array_unique($id_msgs)),
364
			)
365
		);
366
367
		$members = array();
368
		while ($row = $smcFunc['db_fetch_assoc']($request))
369
		{
370
			if ($posterOptions['id'] == $row['id_member'])
371
				continue;
372
373
			$members[$row['id_member']] = $row;
374
		}
375
376
		return $members;
377
	}
378
379
	protected static function handleMentionedNotifications($msgOptions, $members, $prefs, &$done_members, &$alert_rows)
380
	{
381
		global $smcFunc, $scripturl, $language, $modSettings;
382
383
		foreach ($members as $id => $member)
384
		{
385
			if (!empty($prefs[$id]['msg_mention']))
386
				$done_members[] = $id;
387
			else
388
				continue;
389
390
			// Alerts' emails are always instant
391
			if ($prefs[$id]['msg_mention'] & self::RECEIVE_NOTIFY_EMAIL)
392
			{
393
				$replacements = array(
394
					'CONTENTSUBJECT' => $msgOptions['subject'],
395
					'MENTIONNAME' => $member['mentioned_by']['name'],
396
					'MEMBERNAME' => $member['real_name'],
397
					'CONTENTLINK' => $scripturl . '?msg=' . $msgOptions['id'],
398
				);
399
400
				$emaildata = loadEmailTemplate('msg_mention', $replacements, empty($member['lngfile']) || empty($modSettings['userLanguage']) ? $language : $member['lngfile']);
401
				sendmail($member['email_address'], $emaildata['subject'], $emaildata['body'], null, 'msg_mention_' . $msgOptions['id'], $emaildata['is_html'], 2);
402
			}
403
404
			if ($prefs[$id]['msg_mention'] & self::RECEIVE_NOTIFY_ALERT)
405
			{
406
				$alert_rows[] = array(
407
					'alert_time' => time(),
408
					'id_member' => $member['id'],
409
					'id_member_started' => $member['mentioned_by']['id'],
410
					'member_name' => $member['mentioned_by']['name'],
411
					'content_type' => 'msg',
412
					'content_id' => $msgOptions['id'],
413
					'content_action' => 'mention',
414
					'is_read' => 0,
415
					'extra' => $smcFunc['json_encode'](array(
416
						'content_subject' => $msgOptions['subject'],
417
						'content_link' => $scripturl . '?msg=' . $msgOptions['id'],
418
					)),
419
				);
420
421
				updateMemberData($member['id'], array('alerts' => '+'));
422
			}
423
		}
424
	}
425
}
426
427
?>