ReduceMailQueue()   F
last analyzed

Complexity

Conditions 35
Paths > 20000

Size

Total Lines 205
Code Lines 97

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 35
eloc 97
c 1
b 0
f 0
nc 115411
nop 3
dl 0
loc 205
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
 * This file is automatically called and handles all manner of scheduled things.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2019 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 RC2
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * This function works out what to do!
21
 */
22
function AutoTask()
23
{
24
	global $time_start, $smcFunc;
25
26
	// Special case for doing the mail queue.
27
	if (isset($_GET['scheduled']) && $_GET['scheduled'] == 'mailq')
28
		ReduceMailQueue();
29
	else
30
	{
31
		$task_string = '';
32
33
		// Select the next task to do.
34
		$request = $smcFunc['db_query']('', '
35
			SELECT id_task, task, next_time, time_offset, time_regularity, time_unit, callable
36
			FROM {db_prefix}scheduled_tasks
37
			WHERE disabled = {int:not_disabled}
38
				AND next_time <= {int:current_time}
39
			ORDER BY next_time ASC
40
			LIMIT 1',
41
			array(
42
				'not_disabled' => 0,
43
				'current_time' => time(),
44
			)
45
		);
46
		if ($smcFunc['db_num_rows']($request) != 0)
47
		{
48
			// The two important things really...
49
			$row = $smcFunc['db_fetch_assoc']($request);
50
51
			// When should this next be run?
52
			$next_time = next_time($row['time_regularity'], $row['time_unit'], $row['time_offset']);
53
54
			// How long in seconds it the gap?
55
			$duration = $row['time_regularity'];
56
			if ($row['time_unit'] == 'm')
57
				$duration *= 60;
58
			elseif ($row['time_unit'] == 'h')
59
				$duration *= 3600;
60
			elseif ($row['time_unit'] == 'd')
61
				$duration *= 86400;
62
			elseif ($row['time_unit'] == 'w')
63
				$duration *= 604800;
64
65
			// If we were really late running this task actually skip the next one.
66
			if (time() + ($duration / 2) > $next_time)
67
				$next_time += $duration;
68
69
			// Update it now, so no others run this!
70
			$smcFunc['db_query']('', '
71
				UPDATE {db_prefix}scheduled_tasks
72
				SET next_time = {int:next_time}
73
				WHERE id_task = {int:id_task}
74
					AND next_time = {int:current_next_time}',
75
				array(
76
					'next_time' => $next_time,
77
					'id_task' => $row['id_task'],
78
					'current_next_time' => $row['next_time'],
79
				)
80
			);
81
			$affected_rows = $smcFunc['db_affected_rows']();
82
83
			// What kind of task are we handling?
84
			if (!empty($row['callable']))
85
				$task_string = $row['callable'];
86
87
			// Default SMF task or old mods?
88
			elseif (function_exists('scheduled_' . $row['task']))
89
				$task_string = 'scheduled_' . $row['task'];
90
91
			// One last resource, the task name.
92
			elseif (!empty($row['task']))
93
				$task_string = $row['task'];
94
95
			// The function must exist or we are wasting our time, plus do some timestamp checking, and database check!
96
			if (!empty($task_string) && (!isset($_GET['ts']) || $_GET['ts'] == $row['next_time']) && $affected_rows)
97
			{
98
				ignore_user_abort(true);
99
100
				// Get the callable.
101
				$callable_task = call_helper($task_string, true);
102
103
				// Perform the task.
104
				if (!empty($callable_task))
105
					$completed = call_user_func($callable_task);
0 ignored issues
show
Bug introduced by
It seems like $callable_task can also be of type boolean; however, parameter $function of call_user_func() does only seem to accept callable, 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

105
					$completed = call_user_func(/** @scrutinizer ignore-type */ $callable_task);
Loading history...
106
107
				else
108
					$completed = false;
109
110
				// Log that we did it ;)
111
				if ($completed)
112
				{
113
					$total_time = round(microtime(true) - $time_start, 3);
114
					$smcFunc['db_insert']('',
115
						'{db_prefix}log_scheduled_tasks',
116
						array(
117
							'id_task' => 'int', 'time_run' => 'int', 'time_taken' => 'float',
118
						),
119
						array(
120
							$row['id_task'], time(), (int) $total_time,
121
						),
122
						array()
123
					);
124
				}
125
			}
126
		}
127
		$smcFunc['db_free_result']($request);
128
129
		// Get the next timestamp right.
130
		$request = $smcFunc['db_query']('', '
131
			SELECT next_time
132
			FROM {db_prefix}scheduled_tasks
133
			WHERE disabled = {int:not_disabled}
134
			ORDER BY next_time ASC
135
			LIMIT 1',
136
			array(
137
				'not_disabled' => 0,
138
			)
139
		);
140
		// No new task scheduled yet?
141
		if ($smcFunc['db_num_rows']($request) === 0)
142
			$nextEvent = time() + 86400;
143
		else
144
			list ($nextEvent) = $smcFunc['db_fetch_row']($request);
145
		$smcFunc['db_free_result']($request);
146
147
		updateSettings(array('next_task_time' => $nextEvent));
148
	}
149
150
	// Shall we return?
151
	if (!isset($_GET['scheduled']))
152
		return true;
153
154
	// Finally, send some stuff...
155
	header('expires: Mon, 26 Jul 1997 05:00:00 GMT');
156
	header('last-modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
157
	header('content-type: image/gif');
158
	die("\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x21\xF9\x04\x01\x00\x00\x00\x00\x2C\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3B");
159
}
160
161
/**
162
 * Do some daily cleaning up.
163
 */
164
function scheduled_daily_maintenance()
165
{
166
	global $smcFunc, $modSettings, $sourcedir, $boarddir, $db_type, $image_proxy_enabled;
167
168
	// First clean out the cache.
169
	clean_cache();
170
171
	// If warning decrement is enabled and we have people who have not had a new warning in 24 hours, lower their warning level.
172
	list (, , $modSettings['warning_decrement']) = explode(',', $modSettings['warning_settings']);
173
	if ($modSettings['warning_decrement'])
174
	{
175
		// Find every member who has a warning level...
176
		$request = $smcFunc['db_query']('', '
177
			SELECT id_member, warning
178
			FROM {db_prefix}members
179
			WHERE warning > {int:no_warning}',
180
			array(
181
				'no_warning' => 0,
182
			)
183
		);
184
		$members = array();
185
		while ($row = $smcFunc['db_fetch_assoc']($request))
186
			$members[$row['id_member']] = $row['warning'];
187
		$smcFunc['db_free_result']($request);
188
189
		// Have some members to check?
190
		if (!empty($members))
191
		{
192
			// Find out when they were last warned.
193
			$request = $smcFunc['db_query']('', '
194
				SELECT id_recipient, MAX(log_time) AS last_warning
195
				FROM {db_prefix}log_comments
196
				WHERE id_recipient IN ({array_int:member_list})
197
					AND comment_type = {string:warning}
198
				GROUP BY id_recipient',
199
				array(
200
					'member_list' => array_keys($members),
201
					'warning' => 'warning',
202
				)
203
			);
204
			$member_changes = array();
205
			while ($row = $smcFunc['db_fetch_assoc']($request))
206
			{
207
				// More than 24 hours ago?
208
				if ($row['last_warning'] <= time() - 86400)
209
					$member_changes[] = array(
210
						'id' => $row['id_recipient'],
211
						'warning' => $members[$row['id_recipient']] >= $modSettings['warning_decrement'] ? $members[$row['id_recipient']] - $modSettings['warning_decrement'] : 0,
212
					);
213
			}
214
			$smcFunc['db_free_result']($request);
215
216
			// Have some members to change?
217
			if (!empty($member_changes))
218
				foreach ($member_changes as $change)
219
					$smcFunc['db_query']('', '
220
						UPDATE {db_prefix}members
221
						SET warning = {int:warning}
222
						WHERE id_member = {int:id_member}',
223
						array(
224
							'warning' => $change['warning'],
225
							'id_member' => $change['id'],
226
						)
227
					);
228
		}
229
	}
230
231
	// Do any spider stuff.
232
	if (!empty($modSettings['spider_mode']) && $modSettings['spider_mode'] > 1)
233
	{
234
		require_once($sourcedir . '/ManageSearchEngines.php');
235
		consolidateSpiderStats();
236
	}
237
238
	// Clean up some old login history information.
239
	$smcFunc['db_query']('', '
240
		DELETE FROM {db_prefix}member_logins
241
		WHERE time < {int:oldLogins}',
242
		array(
243
			'oldLogins' => time() - (!empty($modSettings['loginHistoryDays']) ? 60 * 60 * 24 * $modSettings['loginHistoryDays'] : 2592000),
244
		)
245
	);
246
247
	// Run Imageproxy housekeeping
248
	if (!empty($image_proxy_enabled))
249
	{
250
		require_once($boarddir . '/proxy.php');
251
		$proxy = new ProxyServer();
252
		$proxy->housekeeping();
253
	}
254
255
	// Anyone else have something to do?
256
	call_integration_hook('integrate_daily_maintenance');
257
258
	// Log we've done it...
259
	return true;
260
}
261
262
/**
263
 * Send out a daily email of all subscribed topics.
264
 */
265
function scheduled_daily_digest()
266
{
267
	global $is_weekly, $txt, $mbname, $scripturl, $sourcedir, $smcFunc, $context, $modSettings;
268
269
	// We'll want this...
270
	require_once($sourcedir . '/Subs-Post.php');
271
	loadEssentialThemeData();
272
273
	$is_weekly = !empty($is_weekly) ? 1 : 0;
274
275
	// Right - get all the notification data FIRST.
276
	$request = $smcFunc['db_query']('', '
277
		SELECT ln.id_topic, COALESCE(t.id_board, ln.id_board) AS id_board, mem.email_address, mem.member_name,
278
			mem.lngfile, mem.id_member
279
		FROM {db_prefix}log_notify AS ln
280
			JOIN {db_prefix}members AS mem ON (mem.id_member = ln.id_member)
281
			LEFT JOIN {db_prefix}topics AS t ON (ln.id_topic != {int:empty_topic} AND t.id_topic = ln.id_topic)
282
		WHERE mem.is_activated = {int:is_activated}',
283
		array(
284
			'empty_topic' => 0,
285
			'is_activated' => 1,
286
		)
287
	);
288
	$members = array();
289
	$langs = array();
290
	$notify = array();
291
	while ($row = $smcFunc['db_fetch_assoc']($request))
292
	{
293
		if (!isset($members[$row['id_member']]))
294
		{
295
			$members[$row['id_member']] = array(
296
				'email' => $row['email_address'],
297
				'name' => $row['member_name'],
298
				'id' => $row['id_member'],
299
				'lang' => $row['lngfile'],
300
			);
301
			$langs[$row['lngfile']] = $row['lngfile'];
302
		}
303
304
		// Store this useful data!
305
		$boards[$row['id_board']] = $row['id_board'];
306
		if ($row['id_topic'])
307
			$notify['topics'][$row['id_topic']][] = $row['id_member'];
308
		else
309
			$notify['boards'][$row['id_board']][] = $row['id_member'];
310
	}
311
	$smcFunc['db_free_result']($request);
312
313
	if (empty($boards))
314
		return true;
315
316
	// Just get the board names.
317
	$request = $smcFunc['db_query']('', '
318
		SELECT id_board, name
319
		FROM {db_prefix}boards
320
		WHERE id_board IN ({array_int:board_list})',
321
		array(
322
			'board_list' => $boards,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $boards does not seem to be defined for all execution paths leading up to this point.
Loading history...
323
		)
324
	);
325
	$boards = array();
326
	while ($row = $smcFunc['db_fetch_assoc']($request))
327
		$boards[$row['id_board']] = $row['name'];
328
	$smcFunc['db_free_result']($request);
329
330
	if (empty($boards))
331
		return true;
332
333
	// Get the actual topics...
334
	$request = $smcFunc['db_query']('', '
335
		SELECT ld.note_type, t.id_topic, t.id_board, t.id_member_started, m.id_msg, m.subject,
336
			b.name AS board_name
337
		FROM {db_prefix}log_digest AS ld
338
			JOIN {db_prefix}topics AS t ON (t.id_topic = ld.id_topic
339
				AND t.id_board IN ({array_int:board_list}))
340
			JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
341
			JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
342
		WHERE ' . ($is_weekly ? 'ld.daily != {int:daily_value}' : 'ld.daily IN (0, 2)'),
343
		array(
344
			'board_list' => array_keys($boards),
345
			'daily_value' => 2,
346
		)
347
	);
348
	$types = array();
349
	while ($row = $smcFunc['db_fetch_assoc']($request))
350
	{
351
		if (!isset($types[$row['note_type']][$row['id_board']]))
352
			$types[$row['note_type']][$row['id_board']] = array(
353
				'lines' => array(),
354
				'name' => $row['board_name'],
355
				'id' => $row['id_board'],
356
			);
357
358
		if ($row['note_type'] == 'reply')
359
		{
360
			if (isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]))
361
				$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['count']++;
362
			else
363
				$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = array(
364
					'id' => $row['id_topic'],
365
					'subject' => un_htmlspecialchars($row['subject']),
366
					'count' => 1,
367
				);
368
		}
369
		elseif ($row['note_type'] == 'topic')
370
		{
371
			if (!isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]))
372
				$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = array(
373
					'id' => $row['id_topic'],
374
					'subject' => un_htmlspecialchars($row['subject']),
375
				);
376
		}
377
		else
378
		{
379
			if (!isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]))
380
				$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = array(
381
					'id' => $row['id_topic'],
382
					'subject' => un_htmlspecialchars($row['subject']),
383
					'starter' => $row['id_member_started'],
384
				);
385
		}
386
387
		$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['members'] = array();
388
		if (!empty($notify['topics'][$row['id_topic']]))
389
			$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']]);
390
		if (!empty($notify['boards'][$row['id_board']]))
391
			$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']]);
392
	}
393
	$smcFunc['db_free_result']($request);
394
395
	if (empty($types))
396
		return true;
397
398
	// Let's load all the languages into a cache thingy.
399
	$langtxt = array();
400
	foreach ($langs as $lang)
401
	{
402
		loadLanguage('Post', $lang);
403
		loadLanguage('index', $lang);
404
		loadLanguage('EmailTemplates', $lang);
405
		$langtxt[$lang] = array(
406
			'subject' => $txt['digest_subject_' . ($is_weekly ? 'weekly' : 'daily')],
407
			'char_set' => $txt['lang_character_set'],
408
			'intro' => sprintf($txt['digest_intro_' . ($is_weekly ? 'weekly' : 'daily')], $mbname),
409
			'new_topics' => $txt['digest_new_topics'],
410
			'topic_lines' => $txt['digest_new_topics_line'],
411
			'new_replies' => $txt['digest_new_replies'],
412
			'mod_actions' => $txt['digest_mod_actions'],
413
			'replies_one' => $txt['digest_new_replies_one'],
414
			'replies_many' => $txt['digest_new_replies_many'],
415
			'sticky' => $txt['digest_mod_act_sticky'],
416
			'lock' => $txt['digest_mod_act_lock'],
417
			'unlock' => $txt['digest_mod_act_unlock'],
418
			'remove' => $txt['digest_mod_act_remove'],
419
			'move' => $txt['digest_mod_act_move'],
420
			'merge' => $txt['digest_mod_act_merge'],
421
			'split' => $txt['digest_mod_act_split'],
422
			'bye' => $txt['regards_team'],
423
		);
424
425
		call_integration_hook('integrate_daily_digest_lang', array(&$langtxt, $lang));
426
	}
427
428
	// The preferred way...
429
	require_once($sourcedir . '/Subs-Notify.php');
430
	$prefs = getNotifyPrefs(array_keys($members), array('msg_notify_type', 'msg_notify_pref'), true);
431
432
	// Right - send out the silly things - this will take quite some space!
433
	$members_sent = array();
434
	foreach ($members as $mid => $member)
435
	{
436
		$frequency = isset($prefs[$mid]['msg_notify_pref']) ? $prefs[$mid]['msg_notify_pref'] : 0;
437
		$notify_types = !empty($prefs[$mid]['msg_notify_type']) ? $prefs[$mid]['msg_notify_type'] : 1;
438
439
		// Did they not elect to choose this?
440
		if ($frequency < 3 || $frequency == 4 && !$is_weekly || $frequency == 3 && $is_weekly || $notify_types == 4)
441
			continue;
442
443
		// Right character set!
444
		$context['character_set'] = empty($modSettings['global_character_set']) ? $langtxt[$lang]['char_set'] : $modSettings['global_character_set'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $lang seems to be defined by a foreach iteration on line 400. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
445
446
		// Do the start stuff!
447
		$email = array(
448
			'subject' => $mbname . ' - ' . $langtxt[$lang]['subject'],
449
			'body' => $member['name'] . ',' . "\n\n" . $langtxt[$lang]['intro'] . "\n" . $scripturl . '?action=profile;area=notification;u=' . $member['id'] . "\n",
450
			'email' => $member['email'],
451
		);
452
453
		// All new topics?
454
		if (isset($types['topic']))
455
		{
456
			$titled = false;
457
			foreach ($types['topic'] as $id => $board)
458
				foreach ($board['lines'] as $topic)
459
					if (in_array($mid, $topic['members']))
460
					{
461
						if (!$titled)
462
						{
463
							$email['body'] .= "\n" . $langtxt[$lang]['new_topics'] . ':' . "\n" . '-----------------------------------------------';
464
							$titled = true;
465
						}
466
						$email['body'] .= "\n" . sprintf($langtxt[$lang]['topic_lines'], $topic['subject'], $board['name']);
467
					}
468
			if ($titled)
469
				$email['body'] .= "\n";
470
		}
471
472
		// What about replies?
473
		if (isset($types['reply']))
474
		{
475
			$titled = false;
476
			foreach ($types['reply'] as $id => $board)
477
				foreach ($board['lines'] as $topic)
478
					if (in_array($mid, $topic['members']))
479
					{
480
						if (!$titled)
481
						{
482
							$email['body'] .= "\n" . $langtxt[$lang]['new_replies'] . ':' . "\n" . '-----------------------------------------------';
483
							$titled = true;
484
						}
485
						$email['body'] .= "\n" . ($topic['count'] == 1 ? sprintf($langtxt[$lang]['replies_one'], $topic['subject']) : sprintf($langtxt[$lang]['replies_many'], $topic['count'], $topic['subject']));
486
					}
487
488
			if ($titled)
489
				$email['body'] .= "\n";
490
		}
491
492
		// Finally, moderation actions!
493
		if ($notify_types < 3)
494
		{
495
			$titled = false;
496
			foreach ($types as $note_type => $type)
497
			{
498
				if ($note_type == 'topic' || $note_type == 'reply')
499
					continue;
500
501
				foreach ($type as $id => $board)
502
					foreach ($board['lines'] as $topic)
503
						if (in_array($mid, $topic['members']))
504
						{
505
							if (!$titled)
506
							{
507
								$email['body'] .= "\n" . $langtxt[$lang]['mod_actions'] . ':' . "\n" . '-----------------------------------------------';
508
								$titled = true;
509
							}
510
							$email['body'] .= "\n" . sprintf($langtxt[$lang][$note_type], $topic['subject']);
511
						}
512
			}
513
		}
514
515
		call_integration_hook('integrate_daily_digest_email', array(&$email, $types, $notify_types, $langtxt));
516
517
		if ($titled)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $titled does not seem to be defined for all execution paths leading up to this point.
Loading history...
518
			$email['body'] .= "\n";
519
520
		// Then just say our goodbyes!
521
		$email['body'] .= "\n\n" . $txt['regards_team'];
522
523
		// Send it - low priority!
524
		sendmail($email['email'], $email['subject'], $email['body'], null, 'digest', false, 4);
525
526
		$members_sent[] = $mid;
527
	}
528
529
	// Clean up...
530
	if ($is_weekly)
531
	{
532
		$smcFunc['db_query']('', '
533
			DELETE FROM {db_prefix}log_digest
534
			WHERE daily != {int:not_daily}',
535
			array(
536
				'not_daily' => 0,
537
			)
538
		);
539
		$smcFunc['db_query']('', '
540
			UPDATE {db_prefix}log_digest
541
			SET daily = {int:daily_value}
542
			WHERE daily = {int:not_daily}',
543
			array(
544
				'daily_value' => 2,
545
				'not_daily' => 0,
546
			)
547
		);
548
	}
549
	else
550
	{
551
		// Clear any only weekly ones, and stop us from sending daily again.
552
		$smcFunc['db_query']('', '
553
			DELETE FROM {db_prefix}log_digest
554
			WHERE daily = {int:daily_value}',
555
			array(
556
				'daily_value' => 2,
557
			)
558
		);
559
		$smcFunc['db_query']('', '
560
			UPDATE {db_prefix}log_digest
561
			SET daily = {int:both_value}
562
			WHERE daily = {int:no_value}',
563
			array(
564
				'both_value' => 1,
565
				'no_value' => 0,
566
			)
567
		);
568
	}
569
570
	// Just in case the member changes their settings mark this as sent.
571
	if (!empty($members_sent))
572
	{
573
		$smcFunc['db_query']('', '
574
			UPDATE {db_prefix}log_notify
575
			SET sent = {int:is_sent}
576
			WHERE id_member IN ({array_int:member_list})',
577
			array(
578
				'member_list' => $members_sent,
579
				'is_sent' => 1,
580
			)
581
		);
582
	}
583
584
	// Log we've done it...
585
	return true;
586
}
587
588
/**
589
 * Like the daily stuff - just seven times less regular ;)
590
 */
591
function scheduled_weekly_digest()
592
{
593
	global $is_weekly;
594
595
	// We just pass through to the daily function - avoid duplication!
596
	$is_weekly = true;
597
	return scheduled_daily_digest();
598
}
599
600
/**
601
 * Send a group of emails from the mail queue.
602
 *
603
 * @param bool|int $number The number to send each loop through or false to use the standard limits
604
 * @param bool $override_limit Whether to bypass the limit
605
 * @param bool $force_send Whether to forcibly send the messages now (useful when using cron jobs)
606
 * @return bool Whether things were sent
607
 */
608
function ReduceMailQueue($number = false, $override_limit = false, $force_send = false)
609
{
610
	global $modSettings, $smcFunc, $sourcedir, $txt, $language;
611
612
	// Are we intending another script to be sending out the queue?
613
	if (!empty($modSettings['mail_queue_use_cron']) && empty($force_send))
614
		return false;
615
616
	// Just in case we run into a problem.
617
	if (!isset($txt))
618
	{
619
		loadEssentialThemeData();
620
		loadLanguage('Errors', $language, false);
621
		loadLanguage('index', $language, false);
622
	}
623
624
	// By default send 5 at once.
625
	if (!$number)
626
		$number = empty($modSettings['mail_quantity']) ? 5 : $modSettings['mail_quantity'];
627
628
	// If we came with a timestamp, and that doesn't match the next event, then someone else has beaten us.
629
	if (isset($_GET['ts']) && $_GET['ts'] != $modSettings['mail_next_send'] && empty($force_send))
630
		return false;
631
632
	// By default move the next sending on by 10 seconds, and require an affected row.
633
	if (!$override_limit)
634
	{
635
		$delay = !empty($modSettings['mail_queue_delay']) ? $modSettings['mail_queue_delay'] : (!empty($modSettings['mail_limit']) && $modSettings['mail_limit'] < 5 ? 10 : 5);
636
637
		$smcFunc['db_query']('', '
638
			UPDATE {db_prefix}settings
639
			SET value = {string:next_mail_send}
640
			WHERE variable = {literal:mail_next_send}
641
				AND value = {string:last_send}',
642
			array(
643
				'next_mail_send' => time() + $delay,
644
				'last_send' => $modSettings['mail_next_send'],
645
			)
646
		);
647
		if ($smcFunc['db_affected_rows']() == 0)
648
			return false;
649
		$modSettings['mail_next_send'] = time() + $delay;
650
	}
651
652
	// If we're not overriding how many are we allow to send?
653
	if (!$override_limit && !empty($modSettings['mail_limit']))
654
	{
655
		list ($mt, $mn) = @explode('|', $modSettings['mail_recent']);
656
657
		// Nothing worth noting...
658
		if (empty($mn) || $mt < time() - 60)
659
		{
660
			$mt = time();
661
			$mn = $number;
662
		}
663
		// Otherwise we have a few more we can spend?
664
		elseif ($mn < $modSettings['mail_limit'])
665
		{
666
			$mn += $number;
667
		}
668
		// No more I'm afraid, return!
669
		else
670
			return false;
671
672
		// Reflect that we're about to send some, do it now to be safe.
673
		updateSettings(array('mail_recent' => $mt . '|' . $mn));
674
	}
675
676
	// Now we know how many we're sending, let's send them.
677
	$request = $smcFunc['db_query']('', '
678
		SELECT id_mail, recipient, body, subject, headers, send_html, time_sent, private
679
		FROM {db_prefix}mail_queue
680
		ORDER BY priority ASC, id_mail ASC
681
		LIMIT {int:limit}',
682
		array(
683
			'limit' => $number,
684
		)
685
	);
686
	$ids = array();
687
	$emails = array();
688
	while ($row = $smcFunc['db_fetch_assoc']($request))
689
	{
690
		// We want to delete these from the database ASAP, so just get the data and go.
691
		$ids[] = $row['id_mail'];
692
		$emails[] = array(
693
			'to' => $row['recipient'],
694
			'body' => $row['body'],
695
			'subject' => $row['subject'],
696
			'headers' => $row['headers'],
697
			'send_html' => $row['send_html'],
698
			'time_sent' => $row['time_sent'],
699
			'private' => $row['private'],
700
		);
701
	}
702
	$smcFunc['db_free_result']($request);
703
704
	// Delete, delete, delete!!!
705
	if (!empty($ids))
706
		$smcFunc['db_query']('', '
707
			DELETE FROM {db_prefix}mail_queue
708
			WHERE id_mail IN ({array_int:mail_list})',
709
			array(
710
				'mail_list' => $ids,
711
			)
712
		);
713
714
	// Don't believe we have any left?
715
	if (count($ids) < $number)
716
	{
717
		// Only update the setting if no-one else has beaten us to it.
718
		$smcFunc['db_query']('', '
719
			UPDATE {db_prefix}settings
720
			SET value = {string:no_send}
721
			WHERE variable = {literal:mail_next_send}
722
				AND value = {string:last_mail_send}',
723
			array(
724
				'no_send' => '0',
725
				'last_mail_send' => $modSettings['mail_next_send'],
726
			)
727
		);
728
	}
729
730
	if (empty($ids))
731
		return false;
732
733
	if (!empty($modSettings['mail_type']) && $modSettings['smtp_host'] != '')
734
		require_once($sourcedir . '/Subs-Post.php');
735
736
	// Send each email, yea!
737
	$failed_emails = array();
738
	foreach ($emails as $email)
739
	{
740
		if (empty($modSettings['mail_type']) || $modSettings['smtp_host'] == '')
741
		{
742
			$email['subject'] = strtr($email['subject'], array("\r" => '', "\n" => ''));
743
			if (!empty($modSettings['mail_strip_carriage']))
744
			{
745
				$email['body'] = strtr($email['body'], array("\r" => ''));
746
				$email['headers'] = strtr($email['headers'], array("\r" => ''));
747
			}
748
749
			// No point logging a specific error here, as we have no language. PHP error is helpful anyway...
750
			$result = mail(strtr($email['to'], array("\r" => '', "\n" => '')), $email['subject'], $email['body'], $email['headers']);
751
752
			// Try to stop a timeout, this would be bad...
753
			@set_time_limit(300);
754
			if (function_exists('apache_reset_timeout'))
755
				@apache_reset_timeout();
756
		}
757
		else
758
			$result = smtp_mail(array($email['to']), $email['subject'], $email['body'], $email['headers']);
759
760
		// Hopefully it sent?
761
		if (!$result)
762
			$failed_emails[] = array($email['to'], $email['body'], $email['subject'], $email['headers'], $email['send_html'], $email['time_sent'], $email['private']);
763
	}
764
765
	// Any emails that didn't send?
766
	if (!empty($failed_emails))
767
	{
768
		// Update the failed attempts check.
769
		$smcFunc['db_insert']('replace',
770
			'{db_prefix}settings',
771
			array('variable' => 'string', 'value' => 'string'),
772
			array('mail_failed_attempts', empty($modSettings['mail_failed_attempts']) ? 1 : ++$modSettings['mail_failed_attempts']),
773
			array('variable')
774
		);
775
776
		// If we have failed to many times, tell mail to wait a bit and try again.
777
		if ($modSettings['mail_failed_attempts'] > 5)
778
			$smcFunc['db_query']('', '
779
				UPDATE {db_prefix}settings
780
				SET value = {string:next_mail_send}
781
				WHERE variable = {literal:mail_next_send}
782
					AND value = {string:last_send}',
783
				array(
784
					'next_mail_send' => time() + 60,
785
					'last_send' => $modSettings['mail_next_send'],
786
				)
787
			);
788
789
		// Add our email back to the queue, manually.
790
		$smcFunc['db_insert']('insert',
791
			'{db_prefix}mail_queue',
792
			array('recipient' => 'string', 'body' => 'string', 'subject' => 'string', 'headers' => 'string', 'send_html' => 'string', 'time_sent' => 'string', 'private' => 'int'),
793
			$failed_emails,
794
			array('id_mail')
795
		);
796
797
		return false;
798
	}
799
	// We where unable to send the email, clear our failed attempts.
800
	elseif (!empty($modSettings['mail_failed_attempts']))
801
		$smcFunc['db_query']('', '
802
			UPDATE {db_prefix}settings
803
			SET value = {string:zero}
804
			WHERE variable = {string:mail_failed_attempts}',
805
			array(
806
				'zero' => '0',
807
				'mail_failed_attempts' => 'mail_failed_attempts',
808
			)
809
		);
810
811
	// Had something to send...
812
	return true;
813
}
814
815
/**
816
 * Calculate the next time the passed tasks should be triggered.
817
 *
818
 * @param string|array $tasks The ID of a single task or an array of tasks
819
 * @param bool $forceUpdate Whether to force the tasks to run now
820
 */
821
function CalculateNextTrigger($tasks = array(), $forceUpdate = false)
822
{
823
	global $modSettings, $smcFunc;
824
825
	$task_query = '';
826
	if (!is_array($tasks))
827
		$tasks = array($tasks);
828
829
	// Actually have something passed?
830
	if (!empty($tasks))
831
	{
832
		if (!isset($tasks[0]) || is_numeric($tasks[0]))
833
			$task_query = ' AND id_task IN ({array_int:tasks})';
834
		else
835
			$task_query = ' AND task IN ({array_string:tasks})';
836
	}
837
	$nextTaskTime = empty($tasks) ? time() + 86400 : $modSettings['next_task_time'];
838
839
	// Get the critical info for the tasks.
840
	$request = $smcFunc['db_query']('', '
841
		SELECT id_task, next_time, time_offset, time_regularity, time_unit
842
		FROM {db_prefix}scheduled_tasks
843
		WHERE disabled = {int:no_disabled}
844
			' . $task_query,
845
		array(
846
			'no_disabled' => 0,
847
			'tasks' => $tasks,
848
		)
849
	);
850
	$tasks = array();
851
	while ($row = $smcFunc['db_fetch_assoc']($request))
852
	{
853
		$next_time = next_time($row['time_regularity'], $row['time_unit'], $row['time_offset']);
854
855
		// Only bother moving the task if it's out of place or we're forcing it!
856
		if ($forceUpdate || $next_time < $row['next_time'] || $row['next_time'] < time())
857
			$tasks[$row['id_task']] = $next_time;
858
		else
859
			$next_time = $row['next_time'];
860
861
		// If this is sooner than the current next task, make this the next task.
862
		if ($next_time < $nextTaskTime)
863
			$nextTaskTime = $next_time;
864
	}
865
	$smcFunc['db_free_result']($request);
866
867
	// Now make the changes!
868
	foreach ($tasks as $id => $time)
869
		$smcFunc['db_query']('', '
870
			UPDATE {db_prefix}scheduled_tasks
871
			SET next_time = {int:next_time}
872
			WHERE id_task = {int:id_task}',
873
			array(
874
				'next_time' => $time,
875
				'id_task' => $id,
876
			)
877
		);
878
879
	// If the next task is now different update.
880
	if ($modSettings['next_task_time'] != $nextTaskTime)
881
		updateSettings(array('next_task_time' => $nextTaskTime));
882
}
883
884
/**
885
 * Simply returns a time stamp of the next instance of these time parameters.
886
 *
887
 * @param int $regularity The regularity
888
 * @param string $unit What unit are we using - 'm' for minutes, 'd' for days, 'w' for weeks or anything else for seconds
889
 * @param int $offset The offset
890
 * @return int The timestamp for the specified time
891
 */
892
function next_time($regularity, $unit, $offset)
893
{
894
	// Just in case!
895
	if ($regularity == 0)
896
		$regularity = 2;
897
898
	$curMin = date('i', time());
899
900
	// If the unit is minutes only check regularity in minutes.
901
	if ($unit == 'm')
902
	{
903
		$off = date('i', $offset);
904
905
		// If it's now just pretend it ain't,
906
		if ($off == $curMin)
907
			$next_time = time() + $regularity;
908
		else
909
		{
910
			// Make sure that the offset is always in the past.
911
			$off = $off > $curMin ? $off - 60 : $off;
912
913
			while ($off <= $curMin)
914
				$off += $regularity;
915
916
			// Now we know when the time should be!
917
			$next_time = time() + 60 * ($off - $curMin);
918
		}
919
	}
920
	// Otherwise, work out what the offset would be with today's date.
921
	else
922
	{
923
		$next_time = mktime(date('H', $offset), date('i', $offset), 0, date('m'), date('d'), date('Y'));
0 ignored issues
show
Bug introduced by
date('Y') of type string is incompatible with the type integer expected by parameter $year of mktime(). ( Ignorable by Annotation )

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

923
		$next_time = mktime(date('H', $offset), date('i', $offset), 0, date('m'), date('d'), /** @scrutinizer ignore-type */ date('Y'));
Loading history...
Bug introduced by
date('d') of type string is incompatible with the type integer expected by parameter $day of mktime(). ( Ignorable by Annotation )

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

923
		$next_time = mktime(date('H', $offset), date('i', $offset), 0, date('m'), /** @scrutinizer ignore-type */ date('d'), date('Y'));
Loading history...
Bug introduced by
date('m') of type string is incompatible with the type integer expected by parameter $month of mktime(). ( Ignorable by Annotation )

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

923
		$next_time = mktime(date('H', $offset), date('i', $offset), 0, /** @scrutinizer ignore-type */ date('m'), date('d'), date('Y'));
Loading history...
Bug introduced by
date('H', $offset) of type string is incompatible with the type integer expected by parameter $hour of mktime(). ( Ignorable by Annotation )

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

923
		$next_time = mktime(/** @scrutinizer ignore-type */ date('H', $offset), date('i', $offset), 0, date('m'), date('d'), date('Y'));
Loading history...
Bug introduced by
date('i', $offset) of type string is incompatible with the type integer expected by parameter $minute of mktime(). ( Ignorable by Annotation )

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

923
		$next_time = mktime(date('H', $offset), /** @scrutinizer ignore-type */ date('i', $offset), 0, date('m'), date('d'), date('Y'));
Loading history...
924
925
		// Make the time offset in the past!
926
		if ($next_time > time())
927
		{
928
			$next_time -= 86400;
929
		}
930
931
		// Default we'll jump in hours.
932
		$applyOffset = 3600;
933
		// 24 hours = 1 day.
934
		if ($unit == 'd')
935
			$applyOffset = 86400;
936
		// Otherwise a week.
937
		if ($unit == 'w')
938
			$applyOffset = 604800;
939
940
		$applyOffset *= $regularity;
941
942
		// Just add on the offset.
943
		while ($next_time <= time())
944
		{
945
			$next_time += $applyOffset;
946
		}
947
	}
948
949
	return $next_time;
950
}
951
952
/**
953
 * This loads the bare minimum data to allow us to load language files!
954
 */
955
function loadEssentialThemeData()
956
{
957
	global $settings, $modSettings, $smcFunc, $mbname, $context, $sourcedir, $txt;
958
959
	// Get all the default theme variables.
960
	$result = $smcFunc['db_query']('', '
961
		SELECT id_theme, variable, value
962
		FROM {db_prefix}themes
963
		WHERE id_member = {int:no_member}
964
			AND id_theme IN (1, {int:theme_guests})',
965
		array(
966
			'no_member' => 0,
967
			'theme_guests' => !empty($modSettings['theme_guests']) ? $modSettings['theme_guests'] : 1,
968
		)
969
	);
970
	while ($row = $smcFunc['db_fetch_assoc']($result))
971
	{
972
		$settings[$row['variable']] = $row['value'];
973
974
		// Is this the default theme?
975
		if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1')
976
			$settings['default_' . $row['variable']] = $row['value'];
977
	}
978
	$smcFunc['db_free_result']($result);
979
980
	// Check we have some directories setup.
981
	if (empty($settings['template_dirs']))
982
	{
983
		$settings['template_dirs'] = array($settings['theme_dir']);
984
985
		// Based on theme (if there is one).
986
		if (!empty($settings['base_theme_dir']))
987
			$settings['template_dirs'][] = $settings['base_theme_dir'];
988
989
		// Lastly the default theme.
990
		if ($settings['theme_dir'] != $settings['default_theme_dir'])
991
			$settings['template_dirs'][] = $settings['default_theme_dir'];
992
	}
993
994
	// Assume we want this.
995
	$context['forum_name'] = $mbname;
996
997
	// Check loadLanguage actually exists!
998
	if (!function_exists('loadLanguage'))
999
	{
1000
		require_once($sourcedir . '/Load.php');
1001
		require_once($sourcedir . '/Subs.php');
1002
	}
1003
1004
	loadLanguage('index+Modifications');
1005
1006
	// Just in case it wasn't already set elsewhere.
1007
	$context['character_set'] = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
1008
	$context['utf8'] = $context['character_set'] === 'UTF-8';
1009
	$context['right_to_left'] = !empty($txt['lang_rtl']);
1010
1011
	// Tell fatal_lang_error() to not reload the theme.
1012
	$context['theme_loaded'] = true;
1013
}
1014
1015
/**
1016
 * This retieves data (e.g. last version of SMF) from sm.org
1017
 */
1018
function scheduled_fetchSMfiles()
1019
{
1020
	global $sourcedir, $txt, $language, $modSettings, $smcFunc, $context;
1021
1022
	// What files do we want to get
1023
	$request = $smcFunc['db_query']('', '
1024
		SELECT id_file, filename, path, parameters
1025
		FROM {db_prefix}admin_info_files',
1026
		array(
1027
		)
1028
	);
1029
1030
	$js_files = array();
1031
1032
	while ($row = $smcFunc['db_fetch_assoc']($request))
1033
	{
1034
		$js_files[$row['id_file']] = array(
1035
			'filename' => $row['filename'],
1036
			'path' => $row['path'],
1037
			'parameters' => sprintf($row['parameters'], $language, urlencode($modSettings['time_format']), urlencode(SMF_FULL_VERSION)),
1038
		);
1039
	}
1040
1041
	$smcFunc['db_free_result']($request);
1042
1043
	// Just in case we run into a problem.
1044
	loadEssentialThemeData();
1045
	loadLanguage('Errors', $language, false);
1046
1047
	foreach ($js_files as $ID_FILE => $file)
1048
	{
1049
		// Create the url
1050
		$server = empty($file['path']) || (substr($file['path'], 0, 7) != 'http://' && substr($file['path'], 0, 8) != 'https://') ? 'https://www.simplemachines.org' : '';
1051
		$url = $server . (!empty($file['path']) ? $file['path'] : $file['path']) . $file['filename'] . (!empty($file['parameters']) ? '?' . $file['parameters'] : '');
1052
1053
		// Get the file
1054
		$file_data = fetch_web_data($url);
1055
1056
		// If we got an error - give up - the site might be down. And if we should happen to be coming from elsewhere, let's also make a note of it.
1057
		if ($file_data === false)
1058
		{
1059
			$context['scheduled_errors']['fetchSMfiles'][] = sprintf($txt['st_cannot_retrieve_file'], $url);
1060
			log_error(sprintf($txt['st_cannot_retrieve_file'], $url));
1061
			return false;
1062
		}
1063
1064
		// Save the file to the database.
1065
		$smcFunc['db_query']('substring', '
1066
			UPDATE {db_prefix}admin_info_files
1067
			SET data = SUBSTRING({string:file_data}, 1, 65534)
1068
			WHERE id_file = {int:id_file}',
1069
			array(
1070
				'id_file' => $ID_FILE,
1071
				'file_data' => $file_data,
1072
			)
1073
		);
1074
	}
1075
	return true;
1076
}
1077
1078
/**
1079
 * Happy birthday!!
1080
 */
1081
function scheduled_birthdayemails()
1082
{
1083
	global $smcFunc;
1084
1085
	$smcFunc['db_insert']('insert', '{db_prefix}background_tasks',
1086
		array('task_file' => 'string-255', 'task_class' => 'string-255', 'task_data' => 'string', 'claimed_time' => 'int'),
1087
		array('$sourcedir/tasks/Birthday-Notify.php', 'Birthday_Notify_Background', '', 0),
1088
		array()
1089
	);
1090
1091
	return true;
1092
}
1093
1094
/**
1095
 * Weekly maintenance
1096
 */
1097
function scheduled_weekly_maintenance()
1098
{
1099
	global $modSettings, $smcFunc, $cache_enable, $cacheAPI;
1100
1101
	// Delete some settings that needn't be set if they are otherwise empty.
1102
	$emptySettings = array(
1103
		'warning_mute', 'warning_moderate', 'warning_watch', 'warning_show', 'disableCustomPerPage', 'spider_mode', 'spider_group',
1104
		'paid_currency_code', 'paid_currency_symbol', 'paid_email_to', 'paid_email', 'paid_enabled', 'paypal_email',
1105
		'search_enable_captcha', 'search_floodcontrol_time', 'show_spider_online',
1106
	);
1107
1108
	$smcFunc['db_query']('', '
1109
		DELETE FROM {db_prefix}settings
1110
		WHERE variable IN ({array_string:setting_list})
1111
			AND (value = {string:zero_value} OR value = {string:blank_value})',
1112
		array(
1113
			'zero_value' => '0',
1114
			'blank_value' => '',
1115
			'setting_list' => $emptySettings,
1116
		)
1117
	);
1118
1119
	// Some settings we never want to keep - they are just there for temporary purposes.
1120
	$deleteAnywaySettings = array(
1121
		'attachment_full_notified',
1122
	);
1123
1124
	$smcFunc['db_query']('', '
1125
		DELETE FROM {db_prefix}settings
1126
		WHERE variable IN ({array_string:setting_list})',
1127
		array(
1128
			'setting_list' => $deleteAnywaySettings,
1129
		)
1130
	);
1131
1132
	// Ok should we prune the logs?
1133
	if (!empty($modSettings['pruningOptions']))
1134
	{
1135
		if (!empty($modSettings['pruningOptions']) && strpos($modSettings['pruningOptions'], ',') !== false)
1136
			list ($modSettings['pruneErrorLog'], $modSettings['pruneModLog'], $modSettings['pruneBanLog'], $modSettings['pruneReportLog'], $modSettings['pruneScheduledTaskLog'], $modSettings['pruneSpiderHitLog']) = explode(',', $modSettings['pruningOptions']);
1137
1138
		if (!empty($modSettings['pruneErrorLog']))
1139
		{
1140
			// Figure out when our cutoff time is.  1 day = 86400 seconds.
1141
			$t = time() - $modSettings['pruneErrorLog'] * 86400;
1142
1143
			$smcFunc['db_query']('', '
1144
				DELETE FROM {db_prefix}log_errors
1145
				WHERE log_time < {int:log_time}',
1146
				array(
1147
					'log_time' => $t,
1148
				)
1149
			);
1150
		}
1151
1152
		if (!empty($modSettings['pruneModLog']))
1153
		{
1154
			// Figure out when our cutoff time is.  1 day = 86400 seconds.
1155
			$t = time() - $modSettings['pruneModLog'] * 86400;
1156
1157
			$smcFunc['db_query']('', '
1158
				DELETE FROM {db_prefix}log_actions
1159
				WHERE log_time < {int:log_time}
1160
					AND id_log = {int:moderation_log}',
1161
				array(
1162
					'log_time' => $t,
1163
					'moderation_log' => 1,
1164
				)
1165
			);
1166
		}
1167
1168
		if (!empty($modSettings['pruneBanLog']))
1169
		{
1170
			// Figure out when our cutoff time is.  1 day = 86400 seconds.
1171
			$t = time() - $modSettings['pruneBanLog'] * 86400;
1172
1173
			$smcFunc['db_query']('', '
1174
				DELETE FROM {db_prefix}log_banned
1175
				WHERE log_time < {int:log_time}',
1176
				array(
1177
					'log_time' => $t,
1178
				)
1179
			);
1180
		}
1181
1182
		if (!empty($modSettings['pruneReportLog']))
1183
		{
1184
			// Figure out when our cutoff time is.  1 day = 86400 seconds.
1185
			$t = time() - $modSettings['pruneReportLog'] * 86400;
1186
1187
			// This one is more complex then the other logs.  First we need to figure out which reports are too old.
1188
			$reports = array();
1189
			$result = $smcFunc['db_query']('', '
1190
				SELECT id_report
1191
				FROM {db_prefix}log_reported
1192
				WHERE time_started < {int:time_started}
1193
					AND closed = {int:closed}
1194
					AND ignore_all = {int:not_ignored}',
1195
				array(
1196
					'time_started' => $t,
1197
					'closed' => 1,
1198
					'not_ignored' => 0,
1199
				)
1200
			);
1201
1202
			while ($row = $smcFunc['db_fetch_row']($result))
1203
				$reports[] = $row[0];
1204
1205
			$smcFunc['db_free_result']($result);
1206
1207
			if (!empty($reports))
1208
			{
1209
				// Now delete the reports...
1210
				$smcFunc['db_query']('', '
1211
					DELETE FROM {db_prefix}log_reported
1212
					WHERE id_report IN ({array_int:report_list})',
1213
					array(
1214
						'report_list' => $reports,
1215
					)
1216
				);
1217
				// And delete the comments for those reports...
1218
				$smcFunc['db_query']('', '
1219
					DELETE FROM {db_prefix}log_reported_comments
1220
					WHERE id_report IN ({array_int:report_list})',
1221
					array(
1222
						'report_list' => $reports,
1223
					)
1224
				);
1225
			}
1226
		}
1227
1228
		if (!empty($modSettings['pruneScheduledTaskLog']))
1229
		{
1230
			// Figure out when our cutoff time is.  1 day = 86400 seconds.
1231
			$t = time() - $modSettings['pruneScheduledTaskLog'] * 86400;
1232
1233
			$smcFunc['db_query']('', '
1234
				DELETE FROM {db_prefix}log_scheduled_tasks
1235
				WHERE time_run < {int:time_run}',
1236
				array(
1237
					'time_run' => $t,
1238
				)
1239
			);
1240
		}
1241
1242
		if (!empty($modSettings['pruneSpiderHitLog']))
1243
		{
1244
			// Figure out when our cutoff time is.  1 day = 86400 seconds.
1245
			$t = time() - $modSettings['pruneSpiderHitLog'] * 86400;
1246
1247
			$smcFunc['db_query']('', '
1248
				DELETE FROM {db_prefix}log_spider_hits
1249
				WHERE log_time < {int:log_time}',
1250
				array(
1251
					'log_time' => $t,
1252
				)
1253
			);
1254
		}
1255
	}
1256
1257
	// Get rid of any paid subscriptions that were never actioned.
1258
	$smcFunc['db_query']('', '
1259
		DELETE FROM {db_prefix}log_subscribed
1260
		WHERE end_time = {int:no_end_time}
1261
			AND status = {int:not_active}
1262
			AND start_time < {int:start_time}
1263
			AND payments_pending < {int:payments_pending}',
1264
		array(
1265
			'no_end_time' => 0,
1266
			'not_active' => 0,
1267
			'start_time' => time() - 60,
1268
			'payments_pending' => 1,
1269
		)
1270
	);
1271
1272
	// Some OS's don't seem to clean out their sessions.
1273
	$smcFunc['db_query']('', '
1274
		DELETE FROM {db_prefix}sessions
1275
		WHERE last_update < {int:last_update}',
1276
		array(
1277
			'last_update' => time() - 86400,
1278
		)
1279
	);
1280
1281
	// Update the regex of top level domains with the IANA's latest official list
1282
	$smcFunc['db_insert']('insert', '{db_prefix}background_tasks',
1283
		array('task_file' => 'string-255', 'task_class' => 'string-255', 'task_data' => 'string', 'claimed_time' => 'int'),
1284
		array('$sourcedir/tasks/UpdateTldRegex.php', 'Update_TLD_Regex', '', 0), array()
1285
	);
1286
1287
	// Run Cache housekeeping
1288
	if (!empty($cache_enable) && !empty($cacheAPI))
1289
		$cacheAPI->housekeeping();
1290
1291
	// Prevent stale minimized CSS and JavaScript from cluttering up the theme directories
1292
	deleteAllMinified();
1293
1294
	// Maybe there's more to do.
1295
	call_integration_hook('integrate_weekly_maintenance');
1296
1297
	return true;
1298
}
1299
1300
/**
1301
 * Perform the standard checks on expiring/near expiring subscriptions.
1302
 */
1303
function scheduled_paid_subscriptions()
1304
{
1305
	global $sourcedir, $scripturl, $smcFunc, $modSettings, $language;
1306
1307
	// Start off by checking for removed subscriptions.
1308
	$request = $smcFunc['db_query']('', '
1309
		SELECT id_subscribe, id_member
1310
		FROM {db_prefix}log_subscribed
1311
		WHERE status = {int:is_active}
1312
			AND end_time < {int:time_now}',
1313
		array(
1314
			'is_active' => 1,
1315
			'time_now' => time(),
1316
		)
1317
	);
1318
	while ($row = $smcFunc['db_fetch_assoc']($request))
1319
	{
1320
		require_once($sourcedir . '/ManagePaid.php');
1321
		removeSubscription($row['id_subscribe'], $row['id_member']);
1322
	}
1323
	$smcFunc['db_free_result']($request);
1324
1325
	// Get all those about to expire that have not had a reminder sent.
1326
	$request = $smcFunc['db_query']('', '
1327
		SELECT ls.id_sublog, m.id_member, m.member_name, m.email_address, m.lngfile, s.name, ls.end_time
1328
		FROM {db_prefix}log_subscribed AS ls
1329
			JOIN {db_prefix}subscriptions AS s ON (s.id_subscribe = ls.id_subscribe)
1330
			JOIN {db_prefix}members AS m ON (m.id_member = ls.id_member)
1331
		WHERE ls.status = {int:is_active}
1332
			AND ls.reminder_sent = {int:reminder_sent}
1333
			AND s.reminder > {int:reminder_wanted}
1334
			AND ls.end_time < ({int:time_now} + s.reminder * 86400)',
1335
		array(
1336
			'is_active' => 1,
1337
			'reminder_sent' => 0,
1338
			'reminder_wanted' => 0,
1339
			'time_now' => time(),
1340
		)
1341
	);
1342
	$subs_reminded = array();
1343
	$members = array();
1344
	while ($row = $smcFunc['db_fetch_assoc']($request))
1345
	{
1346
		// If this is the first one load the important bits.
1347
		if (empty($subs_reminded))
1348
		{
1349
			require_once($sourcedir . '/Subs-Post.php');
1350
			// Need the below for loadLanguage to work!
1351
			loadEssentialThemeData();
1352
		}
1353
1354
		$subs_reminded[] = $row['id_sublog'];
1355
		$members[$row['id_member']] = $row;
1356
	}
1357
	$smcFunc['db_free_result']($request);
1358
1359
	// Load alert preferences
1360
	require_once($sourcedir . '/Subs-Notify.php');
1361
	$notifyPrefs = getNotifyPrefs(array_keys($members), 'paidsubs_expiring', true);
1362
	$alert_rows = array();
1363
	foreach ($members as $row)
1364
	{
1365
		$replacements = array(
1366
			'PROFILE_LINK' => $scripturl . '?action=profile;area=subscriptions;u=' . $row['id_member'],
1367
			'REALNAME' => $row['member_name'],
1368
			'SUBSCRIPTION' => $row['name'],
1369
			'END_DATE' => strip_tags(timeformat($row['end_time'])),
1370
		);
1371
1372
		$emaildata = loadEmailTemplate('paid_subscription_reminder', $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']);
1373
1374
		// Send the actual email.
1375
		if ($notifyPrefs[$row['id_member']] & 0x02)
1376
			sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, 'paid_sub_remind', $emaildata['is_html'], 2);
1377
1378
		if ($notifyPrefs[$row['id_member']] & 0x01)
1379
		{
1380
			$alert_rows[] = array(
1381
				'alert_time' => time(),
1382
				'id_member' => $row['id_member'],
1383
				'id_member_started' => $row['id_member'],
1384
				'member_name' => $row['member_name'],
1385
				'content_type' => 'paidsubs',
1386
				'content_id' => $row['id_sublog'],
1387
				'content_action' => 'expiring',
1388
				'is_read' => 0,
1389
				'extra' => $smcFunc['json_encode'](array(
1390
					'subscription_name' => $row['name'],
1391
					'end_time' => $row['end_time'],
1392
				)),
1393
			);
1394
			updateMemberData($row['id_member'], array('alerts' => '+'));
1395
		}
1396
	}
1397
1398
	// Insert the alerts if any
1399
	if (!empty($alert_rows))
1400
		$smcFunc['db_insert']('',
1401
			'{db_prefix}user_alerts',
1402
			array('alert_time' => 'int', 'id_member' => 'int', 'id_member_started' => 'int', 'member_name' => 'string',
1403
				'content_type' => 'string', 'content_id' => 'int', 'content_action' => 'string', 'is_read' => 'int', 'extra' => 'string'),
1404
			$alert_rows,
1405
			array()
1406
		);
1407
1408
	// Mark the reminder as sent.
1409
	if (!empty($subs_reminded))
1410
		$smcFunc['db_query']('', '
1411
			UPDATE {db_prefix}log_subscribed
1412
			SET reminder_sent = {int:reminder_sent}
1413
			WHERE id_sublog IN ({array_int:subscription_list})',
1414
			array(
1415
				'subscription_list' => $subs_reminded,
1416
				'reminder_sent' => 1,
1417
			)
1418
		);
1419
1420
	return true;
1421
}
1422
1423
/**
1424
 * Check for un-posted attachments is something we can do once in a while :P
1425
 * This function uses opendir cycling through all the attachments
1426
 */
1427
function scheduled_remove_temp_attachments()
1428
{
1429
	global $smcFunc, $modSettings, $context, $txt;
1430
1431
	// We need to know where this thing is going.
1432
	if (!empty($modSettings['currentAttachmentUploadDir']))
1433
	{
1434
		if (!is_array($modSettings['attachmentUploadDir']))
1435
			$modSettings['attachmentUploadDir'] = $smcFunc['json_decode']($modSettings['attachmentUploadDir'], true);
1436
1437
		// Just use the current path for temp files.
1438
		$attach_dirs = $modSettings['attachmentUploadDir'];
1439
	}
1440
	else
1441
	{
1442
		$attach_dirs = array($modSettings['attachmentUploadDir']);
1443
	}
1444
1445
	foreach ($attach_dirs as $attach_dir)
1446
	{
1447
		$dir = @opendir($attach_dir);
1448
		if (!$dir)
1449
		{
1450
			loadEssentialThemeData();
1451
			loadLanguage('Post');
1452
			$context['scheduled_errors']['remove_temp_attachments'][] = $txt['cant_access_upload_path'] . ' (' . $attach_dir . ')';
1453
			log_error($txt['cant_access_upload_path'] . ' (' . $attach_dir . ')', 'critical');
1454
			return false;
1455
		}
1456
1457
		while ($file = readdir($dir))
1458
		{
1459
			if ($file == '.' || $file == '..')
1460
				continue;
1461
1462
			if (strpos($file, 'post_tmp_') !== false)
1463
			{
1464
				// Temp file is more than 5 hours old!
1465
				if (filemtime($attach_dir . '/' . $file) < time() - 18000)
1466
					@unlink($attach_dir . '/' . $file);
1467
			}
1468
		}
1469
		closedir($dir);
1470
	}
1471
1472
	return true;
1473
}
1474
1475
/**
1476
 * Check for move topic notices that have past their best by date
1477
 */
1478
function scheduled_remove_topic_redirect()
1479
{
1480
	global $smcFunc, $sourcedir;
1481
1482
	// init
1483
	$topics = array();
1484
1485
	// We will need this for language files
1486
	loadEssentialThemeData();
1487
1488
	// Find all of the old MOVE topic notices that were set to expire
1489
	$request = $smcFunc['db_query']('', '
1490
		SELECT id_topic
1491
		FROM {db_prefix}topics
1492
		WHERE redirect_expires <= {int:redirect_expires}
1493
			AND redirect_expires <> 0',
1494
		array(
1495
			'redirect_expires' => time(),
1496
		)
1497
	);
1498
1499
	while ($row = $smcFunc['db_fetch_row']($request))
1500
		$topics[] = $row[0];
1501
	$smcFunc['db_free_result']($request);
1502
1503
	// Zap, your gone
1504
	if (count($topics) > 0)
1505
	{
1506
		require_once($sourcedir . '/RemoveTopic.php');
1507
		removeTopics($topics, false, true);
1508
	}
1509
1510
	return true;
1511
}
1512
1513
/**
1514
 * Check for old drafts and remove them
1515
 */
1516
function scheduled_remove_old_drafts()
1517
{
1518
	global $smcFunc, $sourcedir, $modSettings;
1519
1520
	if (empty($modSettings['drafts_keep_days']))
1521
		return true;
1522
1523
	// init
1524
	$drafts = array();
1525
1526
	// We need this for language items
1527
	loadEssentialThemeData();
1528
1529
	// Find all of the old drafts
1530
	$request = $smcFunc['db_query']('', '
1531
		SELECT id_draft
1532
		FROM {db_prefix}user_drafts
1533
		WHERE poster_time <= {int:poster_time_old}',
1534
		array(
1535
			'poster_time_old' => time() - (86400 * $modSettings['drafts_keep_days']),
1536
		)
1537
	);
1538
1539
	while ($row = $smcFunc['db_fetch_row']($request))
1540
		$drafts[] = (int) $row[0];
1541
	$smcFunc['db_free_result']($request);
1542
1543
	// If we have old one, remove them
1544
	if (count($drafts) > 0)
1545
	{
1546
		require_once($sourcedir . '/Drafts.php');
1547
		DeleteDraft($drafts, false);
0 ignored issues
show
Bug introduced by
$drafts of type array|integer[] is incompatible with the type integer expected by parameter $id_draft of DeleteDraft(). ( Ignorable by Annotation )

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

1547
		DeleteDraft(/** @scrutinizer ignore-type */ $drafts, false);
Loading history...
1548
	}
1549
1550
	return true;
1551
}
1552
1553
?>