DailyDigest::runDigest()   F
last analyzed

Complexity

Conditions 62
Paths > 20000

Size

Total Lines 428
Code Lines 189

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 3906

Importance

Changes 0
Metric Value
cc 62
eloc 189
c 0
b 0
f 0
nc 9983848
nop 1
dl 0
loc 428
ccs 0
cts 182
cp 0
crap 3906
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Send out emails of all subscribed topics, to members.
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 Beta 1
14
 *
15
 */
16
17
namespace ElkArte\ScheduledTasks\Tasks;
18
19
use ElkArte\Helper\Util;
20
use ElkArte\Languages\Loader;
21
use ElkArte\Themes\ThemeLoader;
22
23
/**
24
 * Class DailyDigest - Send out a daily email of all subscribed topics, to members.
25
 *
26
 * - It sends notifications about replies or new topics, and moderation actions.
27
 *
28
 * @package ScheduledTasks
29
 */
30
class DailyDigest implements ScheduledTaskInterface
31
{
32
	/**
33
	 * Sends out the daily digest for all the DD subscribers
34
	 *
35
	 * @return bool
36
	 */
37
	public function run()
38
	{
39
		return $this->runDigest();
40
	}
41
42
	/**
43
	 * Send out an email of all subscribed topics, to members.
44
	 *
45
	 * What it does:
46
	 *
47
	 * - Builds email body's of topics and messages per user as defined by their notification settings
48
	 * - If weekly builds the weekly abridged digest
49
	 *
50
	 * @param bool|int $is_weekly
51
	 *
52
	 * @return bool
53
	 */
54
	public function runDigest($is_weekly = false): bool
55
	{
56
		global $mbname, $modSettings, $boardurl;
57
58
		$db = database();
59
60
		// We'll want this...
61
		require_once(SUBSDIR . '/Mail.subs.php');
62
		ThemeLoader::loadEssentialThemeData();
63
64
		// If the maillist function is on then so is the enhanced digest
65
		$maillist = !empty($modSettings['maillist_enabled']) && !empty($modSettings['maillist_digest_enabled']);
66
		if ($maillist)
67
		{
68
			require_once(SUBSDIR . '/Maillist.subs.php');
69
		}
70
71
		$is_weekly = empty($is_weekly) ? 0 : 1;
72
73
		// Right - get all the notification data FIRST.
74
		$request = $db->fetchQuery('
75
			SELECT 
76
				ln.id_topic, COALESCE(t.id_board, ln.id_board) AS id_board, 
77
				mem.email_address, mem.member_name, mem.real_name, mem.notify_types, mem.lngfile, mem.id_member
78
			FROM {db_prefix}log_notify AS ln
79
				INNER JOIN {db_prefix}members AS mem ON (mem.id_member = ln.id_member)
80
				LEFT JOIN {db_prefix}topics AS t ON (ln.id_topic != {int:empty_topic} AND t.id_topic = ln.id_topic)
81
			WHERE mem.notify_regularity = {int:notify_regularity}
82
				AND mem.is_activated = {int:is_activated}',
83
			[
84
				'empty_topic' => 0,
85
				'notify_regularity' => $is_weekly !== 0 ? '3' : '2',
86
				'is_activated' => 1,
87
			]
88
		);
89
		$members = [];
90
		$langs = [];
91
		$notify = [];
92
		$boards = [];
93
		while (($row = $request->fetch_assoc()))
94
		{
95
			if (!isset($members[$row['id_member']]))
96
			{
97
				$members[$row['id_member']] = [
98
					'email' => $row['email_address'],
99
					'name' => ($row['real_name'] === '') ? $row['member_name'] : un_htmlspecialchars($row['real_name']),
100
					'id' => $row['id_member'],
101
					'notifyMod' => $row['notify_types'] < 3,
102
					'lang' => $row['lngfile'],
103
				];
104
				$langs[$row['lngfile']] = $row['lngfile'];
105
			}
106
107
			// Store this useful data!
108
			$boards[$row['id_board']] = $row['id_board'];
109
			if ($row['id_topic'])
110
			{
111
				$notify['topics'][$row['id_topic']][] = $row['id_member'];
112
			}
113
			else
114
			{
115
				$notify['boards'][$row['id_board']][] = $row['id_member'];
116
			}
117
		}
118
119
		$request->free_result();
120
121
		if (empty($boards))
122
		{
123
			return true;
124
		}
125
126
		// Just get the board names.
127
		require_once(SUBSDIR . '/Boards.subs.php');
128
		$boards = fetchBoardsInfo(['boards' => $boards], ['override_permissions' => true]);
129
130
		if (empty($boards))
131
		{
132
			return true;
133
		}
134
135
		// Get the actual topics...
136
		$request = $db->fetchQuery('
137
			SELECT 
138
				ld.note_type, ld.id_msg AS last_reply,
139
				t.id_topic, t.id_board, t.id_member_started, 
140
				m.id_msg, m.subject, m.body, 
141
				b.name AS board_name, 
142
				ml.body as last_body
143
			FROM {db_prefix}log_digest AS ld
144
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = ld.id_topic
145
					AND t.id_board IN ({array_int:board_list}))
146
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
147
				INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = ld.id_msg)
148
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
149
			WHERE ' . ($is_weekly !== 0 ? 'ld.daily != {int:daily_value}' : 'ld.daily IN (0, 2)'),
150
			[
151
				'board_list' => array_keys($boards),
152
				'daily_value' => 2,
153
			]
154
		);
155
		$types = [];
156
		while (($row = $request->fetch_assoc()))
157
		{
158
			if (!isset($types[$row['note_type']][$row['id_board']]))
159
			{
160
				$types[$row['note_type']][$row['id_board']] = [
161
					'lines' => [],
162
					'name' => un_htmlspecialchars($row['board_name']),
163
					'id' => $row['id_board'],
164
				];
165
			}
166
167
			// A reply has been made
168
			if ($row['note_type'] === 'reply')
169
			{
170
				// More than one reply to this topic?
171
				if (isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]))
172
				{
173
					$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['count']++;
174
175
					// keep track of the highest numbered reply and body text for this topic ...
176
					if ($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['body_id'] < $row['last_reply'])
177
					{
178
						$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['body_id'] = $row['last_reply'];
179
						$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['body_text'] = $row['last_body'];
180
					}
181
				}
182
				else
183
				{
184
					// First time we have seen a reply to this topic, so load our array
185
					$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = [
186
						'id' => $row['id_topic'],
187
						'subject' => un_htmlspecialchars($row['subject']),
188
						'link' => getUrl('action', ['topic' => $row['id_topic'] . '.new', 'topicseen', 'hash' => '#new']),
189
						'count' => 1,
190
						'body_id' => $row['last_reply'],
191
						'body_text' => $row['last_body'],
192
					];
193
				}
194
			}
195
			// New topics are good too
196
			elseif ($row['note_type'] === 'topic')
197
			{
198
				if ($maillist)
199
				{
200
					// Convert to Markdown markup e.g. text ;)
201
					pbe_prepare_text($row['body']);
202
					$row['body'] = Util::shorten_text($row['body'], empty($modSettings['digest_preview_length']) ? 375 : $modSettings['digest_preview_length'], true);
203
					$row['body'] = preg_replace("~\n~", "\n  ", $row['body']);
204
				}
205
206
				// Topics are simple since we are only concerned with the first post
207
				if (!isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]))
208
				{
209
					$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = [
210
						'id' => $row['id_topic'],
211
						'link' => getUrl('action', ['topic' => $row['id_topic'] . '.new', 'topicseen', 'hash' => '#new']),
212
						'subject' => un_htmlspecialchars($row['subject']),
213
						'body' => $row['body'],
214
					];
215
				}
216
			}
217
			elseif ($maillist && empty($modSettings['pbe_no_mod_notices']))
218
			{
219
				if (!isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]))
220
				{
221
					$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = [
222
						'id' => $row['id_topic'],
223
						'subject' => un_htmlspecialchars($row['subject']),
224
						'starter' => $row['id_member_started'],
225
					];
226
				}
227
			}
228
229
			$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['members'] = [];
230
231
			if (!empty($notify['topics'][$row['id_topic']]))
232
			{
233
				$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['members'] = array_merge($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['members'], $notify['topics'][$row['id_topic']]);
234
			}
235
236
			if (!empty($notify['boards'][$row['id_board']]))
237
			{
238
				$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['members'] = array_merge($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['members'], $notify['boards'][$row['id_board']]);
239
			}
240
		}
241
242
		$request->free_result();
243
244
		if (empty($types))
245
		{
246
			return true;
247
		}
248
249
		// Fix the last reply message so it's suitable for previewing
250
		if ($maillist && !empty($types['reply']))
251
		{
252
			require_once(SUBSDIR . '/MaillistPost.subs.php');
253
			foreach ($types['reply'] as $id => $board)
254
			{
255
				foreach ($board['lines'] as $topic)
256
				{
257
					// Replace the body array with the appropriate preview message
258
					$body = $types['reply'][$id]['lines'][$topic['id']]['body_text'];
259
					pbe_prepare_text($body);
260
					$body = Util::shorten_text($body, empty($modSettings['digest_preview_length']) ? 375 : $modSettings['digest_preview_length'], true);
261
					$body = preg_replace("~\n~", "\n  ", $body);
262
					$types['reply'][$id]['lines'][$topic['id']]['body'] = $body;
263
264
					unset($types['reply'][$id]['lines'][$topic['id']]['body_text'], $body);
265
				}
266
			}
267
		}
268
269
		// Let's load all the languages into a cache thingy.
270
		$langtxt = [];
271
		foreach ($langs as $lang)
272
		{
273
			$mtxt = [];
274
			$lang_loader = new Loader($lang, $mtxt, database());
275
			$lang_loader->load('index+Post+Maillist+EmailTemplates');
276
277
			$langtxt[$lang] = [
278
				'subject' => $mtxt['digest_subject_' . ($is_weekly !== 0 ? 'weekly' : 'daily')],
279
				'char_set' => 'UTF-8',
280
				'intro' => sprintf($mtxt['digest_intro_' . ($is_weekly !== 0 ? 'weekly' : 'daily')], $mbname),
281
				'new_topics' => $mtxt['digest_new_topics'],
282
				'topic_lines' => $mtxt['digest_new_topics_line'],
283
				'new_replies' => $mtxt['digest_new_replies'],
284
				'mod_actions' => $mtxt['digest_mod_actions'],
285
				'replies_one' => $mtxt['digest_new_replies_one'],
286
				'replies_many' => $mtxt['digest_new_replies_many'],
287
				'sticky' => $mtxt['digest_mod_act_sticky'],
288
				'lock' => $mtxt['digest_mod_act_lock'],
289
				'unlock' => $mtxt['digest_mod_act_unlock'],
290
				'remove' => $mtxt['digest_mod_act_remove'],
291
				'move' => $mtxt['digest_mod_act_move'],
292
				'merge' => $mtxt['digest_mod_act_merge'],
293
				'split' => $mtxt['digest_mod_act_split'],
294
				'bye' => (empty($modSettings['maillist_sitename_regards']) ? '' : $modSettings['maillist_sitename_regards']) . "\n" . $boardurl,
295
				'preview' => $mtxt['digest_preview'],
296
				'see_full' => $mtxt['digest_see_full'],
297
				'reply_preview' => $mtxt['digest_reply_preview'],
298
				'unread_reply_link' => $mtxt['digest_unread_reply_link'],
299
			];
300
		}
301
302
		// Right - send out the silly things - this will take quite some space!
303
		foreach ($members as $mid => $member)
304
		{
305
			// Do the start stuff!
306
			$email = [
307
				'subject' => $mbname . ' - ' . $langtxt[$lang]['subject'],
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $lang seems to be defined by a foreach iteration on line 271. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
308
				'body' => $member['name'] . ',' . "\n\n" . $langtxt[$lang]['intro'] . "\n" . getUrl('profile', ['action' => 'profile', 'area' => 'notification', 'u' => $member['id'], 'name' => $member['name']]) . "\n",
309
				'email' => $member['email'],
310
			];
311
312
			// All the new topics
313
			if (isset($types['topic']))
314
			{
315
				$titled = false;
316
317
				// Each type contains a board ID and then a topic number
318
				foreach ($types['topic'] as $board)
319
				{
320
					foreach ($board['lines'] as $topic)
321
					{
322
						// They have requested notification for new topics in this board
323
						if (in_array($mid, $topic['members']))
324
						{
325
							// Start of the new topics with a heading bar
326
							if (!$titled)
327
							{
328
								$email['body'] .= "\n" . $langtxt[$lang]['new_topics'] . ':' . "\n" . str_repeat('-', 78);
329
								$titled = true;
330
							}
331
332
							$email['body'] .= "\n" . sprintf($langtxt[$lang]['topic_lines'], $topic['subject'], $board['name']);
333
							if ($maillist)
334
							{
335
								$email['body'] .= $langtxt[$lang]['preview'] . $topic['body'] . $langtxt[$lang]['see_full'] . $topic['link'] . "\n";
336
							}
337
						}
338
					}
339
				}
340
341
				if ($titled)
342
				{
343
					$email['body'] .= "\n";
344
				}
345
			}
346
347
			// What about replies?
348
			if (isset($types['reply']))
349
			{
350
				$titled = false;
351
352
				// Each reply will have a board id and then a topic ID
353
				foreach ($types['reply'] as $board)
354
				{
355
					foreach ($board['lines'] as $topic)
356
					{
357
						// This member wants notices on reply's to this topic
358
						if (in_array($mid, $topic['members']))
359
						{
360
							// First one in the section gets a nice heading
361
							if (!$titled)
362
							{
363
								$email['body'] .= "\n" . $langtxt[$lang]['new_replies'] . ':' . "\n" . str_repeat('-', 78);
364
								$titled = true;
365
							}
366
367
							$email['body'] .= "\n" . ($topic['count'] === 1 ? sprintf($langtxt[$lang]['replies_one'], $topic['subject']) : sprintf($langtxt[$lang]['replies_many'], $topic['count'], $topic['subject']));
368
							if ($maillist)
369
							{
370
								$email['body'] .= $langtxt[$lang]['reply_preview'] . $topic['body'] . $langtxt[$lang]['unread_reply_link'] . $topic['link'] . "\n";
371
							}
372
						}
373
					}
374
				}
375
376
				if ($titled)
377
				{
378
					$email['body'] .= "\n";
379
				}
380
			}
381
382
			// Finally, moderation actions!
383
			$titled = false;
384
			foreach ($types as $note_type => $type)
385
			{
386
				if ($note_type === 'topic' || $note_type === 'reply')
387
				{
388
					continue;
389
				}
390
391
				foreach ($type as $board)
392
				{
393
					foreach ($board['lines'] as $topic)
394
					{
395
						if (in_array($mid, $topic['members']))
396
						{
397
							if (!$titled)
398
							{
399
								$email['body'] .= "\n" . $langtxt[$lang]['mod_actions'] . ':' . "\n" . str_repeat('-', 47);
400
								$titled = true;
401
							}
402
403
							$email['body'] .= "\n" . sprintf($langtxt[$lang][$note_type], $topic['subject']);
404
						}
405
					}
406
				}
407
			}
408
409
			if ($titled)
410
			{
411
				$email['body'] .= "\n";
412
			}
413
414
			// Then just say our goodbyes!
415
			$email['body'] .= "\n\n" . $langtxt[$lang]['bye'];
416
417
			// Send it - low priority!
418
			sendmail($email['email'], $email['subject'], $email['body'], null, null, false, 4);
419
		}
420
421
		// Using the queue, do a final flush before we say that's all folks
422
		if (!empty($modSettings['mail_queue']))
423
		{
424
			AddMailQueue(true);
425
		}
426
427
		// Clean up...
428
		if ($is_weekly !== 0)
429
		{
430
			$db->query('', '
431
				DELETE FROM {db_prefix}log_digest
432
				WHERE daily != {int:not_daily}',
433
				[
434
					'not_daily' => 0,
435
				]
436
			);
437
			$db->query('', '
438
				UPDATE {db_prefix}log_digest
439
				SET daily = {int:daily_value}
440
				WHERE daily = {int:not_daily}',
441
				[
442
					'daily_value' => 2,
443
					'not_daily' => 0,
444
				]
445
			);
446
		}
447
		else
448
		{
449
			// Clear any only weekly ones, and stop us from sending daily again.
450
			$db->query('', '
451
				DELETE FROM {db_prefix}log_digest
452
				WHERE daily = {int:daily_value}',
453
				[
454
					'daily_value' => 2,
455
				]
456
			);
457
			$db->query('', '
458
				UPDATE {db_prefix}log_digest
459
				SET daily = {int:both_value}
460
				WHERE daily = {int:no_value}',
461
				[
462
					'both_value' => 1,
463
					'no_value' => 0,
464
				]
465
			);
466
		}
467
468
		// Just in case the member changes their settings mark this as sent.
469
		$members = array_keys($members);
470
		$db->query('', '
471
			UPDATE {db_prefix}log_notify
472
			SET sent = {int:is_sent}
473
			WHERE id_member IN ({array_int:member_list})',
474
			[
475
				'member_list' => $members,
476
				'is_sent' => 1,
477
			]
478
		);
479
480
		// Log we've done it...
481
		return true;
482
	}
483
}
484