Passed
Push — release-2.1 ( f28109...493a4c )
by Mathias
06:40
created

scheduled_prune_log_topics()   F

Complexity

Conditions 17
Paths 401

Size

Total Lines 169
Code Lines 71

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 17
eloc 71
c 6
b 0
f 0
nc 401
nop 0
dl 0
loc 169
rs 1.8819

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 https://www.simplemachines.org
10
 * @copyright 2020 Simple Machines and individual contributors
11
 * @license https://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 RC3
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 $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
	// Delete old profile exports
256
	if (!empty($modSettings['export_expiry']) && file_exists($modSettings['export_dir']) && is_dir($modSettings['export_dir']))
257
	{
258
		$expiry_date = round(TIME_START - $modSettings['export_expiry'] * 86400);
259
		$export_files = glob(rtrim($modSettings['export_dir'], '/\\') . DIRECTORY_SEPARATOR . '*');
260
261
		foreach ($export_files as $export_file)
262
		{
263
			if (!in_array(basename($export_file), array('index.php', '.htaccess')) && filemtime($export_file) <= $expiry_date)
264
				@unlink($export_file);
265
		}
266
	}
267
268
	// Delete old alerts.
269
	if (!empty($modSettings['alerts_auto_purge']))
270
	{
271
		$smcFunc['db_query']('', '
272
			DELETE FROM {db_prefix}user_alerts
273
			WHERE is_read > 0
274
				AND is_read < {int:purge_before}',
275
			array(
276
				'purge_before' => time() - 86400 * $modSettings['alerts_auto_purge'],
277
			)
278
		);
279
	}
280
281
	// Anyone else have something to do?
282
	call_integration_hook('integrate_daily_maintenance');
283
284
	// Log we've done it...
285
	return true;
286
}
287
288
/**
289
 * Send out a daily email of all subscribed topics.
290
 */
291
function scheduled_daily_digest()
292
{
293
	global $is_weekly, $txt, $mbname, $scripturl, $sourcedir, $smcFunc, $context, $modSettings;
294
295
	// We'll want this...
296
	require_once($sourcedir . '/Subs-Post.php');
297
	loadEssentialThemeData();
298
299
	$is_weekly = !empty($is_weekly) ? 1 : 0;
300
301
	// Right - get all the notification data FIRST.
302
	$request = $smcFunc['db_query']('', '
303
		SELECT ln.id_topic, COALESCE(t.id_board, ln.id_board) AS id_board, mem.email_address, mem.member_name,
304
			mem.lngfile, mem.id_member
305
		FROM {db_prefix}log_notify AS ln
306
			JOIN {db_prefix}members AS mem ON (mem.id_member = ln.id_member)
307
			LEFT JOIN {db_prefix}topics AS t ON (ln.id_topic != {int:empty_topic} AND t.id_topic = ln.id_topic)
308
		WHERE mem.is_activated = {int:is_activated}',
309
		array(
310
			'empty_topic' => 0,
311
			'is_activated' => 1,
312
		)
313
	);
314
	$members = array();
315
	$langs = array();
316
	$notify = array();
317
	while ($row = $smcFunc['db_fetch_assoc']($request))
318
	{
319
		if (!isset($members[$row['id_member']]))
320
		{
321
			$members[$row['id_member']] = array(
322
				'email' => $row['email_address'],
323
				'name' => $row['member_name'],
324
				'id' => $row['id_member'],
325
				'lang' => $row['lngfile'],
326
			);
327
			$langs[$row['lngfile']] = $row['lngfile'];
328
		}
329
330
		// Store this useful data!
331
		$boards[$row['id_board']] = $row['id_board'];
332
		if ($row['id_topic'])
333
			$notify['topics'][$row['id_topic']][] = $row['id_member'];
334
		else
335
			$notify['boards'][$row['id_board']][] = $row['id_member'];
336
	}
337
	$smcFunc['db_free_result']($request);
338
339
	if (empty($boards))
340
		return true;
341
342
	// Just get the board names.
343
	$request = $smcFunc['db_query']('', '
344
		SELECT id_board, name
345
		FROM {db_prefix}boards
346
		WHERE id_board IN ({array_int:board_list})',
347
		array(
348
			'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...
349
		)
350
	);
351
	$boards = array();
352
	while ($row = $smcFunc['db_fetch_assoc']($request))
353
		$boards[$row['id_board']] = $row['name'];
354
	$smcFunc['db_free_result']($request);
355
356
	if (empty($boards))
357
		return true;
358
359
	// Get the actual topics...
360
	$request = $smcFunc['db_query']('', '
361
		SELECT ld.note_type, t.id_topic, t.id_board, t.id_member_started, m.id_msg, m.subject,
362
			b.name AS board_name
363
		FROM {db_prefix}log_digest AS ld
364
			JOIN {db_prefix}topics AS t ON (t.id_topic = ld.id_topic
365
				AND t.id_board IN ({array_int:board_list}))
366
			JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
367
			JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
368
		WHERE ' . ($is_weekly ? 'ld.daily != {int:daily_value}' : 'ld.daily IN (0, 2)'),
369
		array(
370
			'board_list' => array_keys($boards),
371
			'daily_value' => 2,
372
		)
373
	);
374
	$types = array();
375
	while ($row = $smcFunc['db_fetch_assoc']($request))
376
	{
377
		if (!isset($types[$row['note_type']][$row['id_board']]))
378
			$types[$row['note_type']][$row['id_board']] = array(
379
				'lines' => array(),
380
				'name' => $row['board_name'],
381
				'id' => $row['id_board'],
382
			);
383
384
		if ($row['note_type'] == 'reply')
385
		{
386
			if (isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]))
387
				$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['count']++;
388
			else
389
				$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = array(
390
					'id' => $row['id_topic'],
391
					'subject' => un_htmlspecialchars($row['subject']),
392
					'count' => 1,
393
				);
394
		}
395
		elseif ($row['note_type'] == 'topic')
396
		{
397
			if (!isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]))
398
				$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = array(
399
					'id' => $row['id_topic'],
400
					'subject' => un_htmlspecialchars($row['subject']),
401
				);
402
		}
403
		else
404
		{
405
			if (!isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]))
406
				$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = array(
407
					'id' => $row['id_topic'],
408
					'subject' => un_htmlspecialchars($row['subject']),
409
					'starter' => $row['id_member_started'],
410
				);
411
		}
412
413
		$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['members'] = array();
414
		if (!empty($notify['topics'][$row['id_topic']]))
415
			$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']]);
416
		if (!empty($notify['boards'][$row['id_board']]))
417
			$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']]);
418
	}
419
	$smcFunc['db_free_result']($request);
420
421
	if (empty($types))
422
		return true;
423
424
	// Let's load all the languages into a cache thingy.
425
	$langtxt = array();
426
	foreach ($langs as $lang)
427
	{
428
		loadLanguage('Post', $lang);
429
		loadLanguage('index', $lang);
430
		loadLanguage('EmailTemplates', $lang);
431
		$langtxt[$lang] = array(
432
			'subject' => $txt['digest_subject_' . ($is_weekly ? 'weekly' : 'daily')],
433
			'char_set' => $txt['lang_character_set'],
434
			'intro' => sprintf($txt['digest_intro_' . ($is_weekly ? 'weekly' : 'daily')], $mbname),
435
			'new_topics' => $txt['digest_new_topics'],
436
			'topic_lines' => $txt['digest_new_topics_line'],
437
			'new_replies' => $txt['digest_new_replies'],
438
			'mod_actions' => $txt['digest_mod_actions'],
439
			'replies_one' => $txt['digest_new_replies_one'],
440
			'replies_many' => $txt['digest_new_replies_many'],
441
			'sticky' => $txt['digest_mod_act_sticky'],
442
			'lock' => $txt['digest_mod_act_lock'],
443
			'unlock' => $txt['digest_mod_act_unlock'],
444
			'remove' => $txt['digest_mod_act_remove'],
445
			'move' => $txt['digest_mod_act_move'],
446
			'merge' => $txt['digest_mod_act_merge'],
447
			'split' => $txt['digest_mod_act_split'],
448
			'bye' => sprintf($txt['regards_team'], $context['forum_name']),
449
		);
450
451
		call_integration_hook('integrate_daily_digest_lang', array(&$langtxt, $lang));
452
	}
453
454
	// The preferred way...
455
	require_once($sourcedir . '/Subs-Notify.php');
456
	$prefs = getNotifyPrefs(array_keys($members), array('msg_notify_type', 'msg_notify_pref'), true);
457
458
	// Right - send out the silly things - this will take quite some space!
459
	$members_sent = array();
460
	foreach ($members as $mid => $member)
461
	{
462
		$frequency = isset($prefs[$mid]['msg_notify_pref']) ? $prefs[$mid]['msg_notify_pref'] : 0;
463
		$notify_types = !empty($prefs[$mid]['msg_notify_type']) ? $prefs[$mid]['msg_notify_type'] : 1;
464
465
		// Did they not elect to choose this?
466
		if ($frequency < 3 || $frequency == 4 && !$is_weekly || $frequency == 3 && $is_weekly || $notify_types == 4)
467
			continue;
468
469
		// Right character set!
470
		$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 426. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
471
472
		// Do the start stuff!
473
		$email = array(
474
			'subject' => $mbname . ' - ' . $langtxt[$lang]['subject'],
475
			'body' => $member['name'] . ',' . "\n\n" . $langtxt[$lang]['intro'] . "\n" . $scripturl . '?action=profile;area=notification;u=' . $member['id'] . "\n",
476
			'email' => $member['email'],
477
		);
478
479
		// All new topics?
480
		if (isset($types['topic']))
481
		{
482
			$titled = false;
483
			foreach ($types['topic'] as $id => $board)
484
				foreach ($board['lines'] as $topic)
485
					if (in_array($mid, $topic['members']))
486
					{
487
						if (!$titled)
488
						{
489
							$email['body'] .= "\n" . $langtxt[$lang]['new_topics'] . ':' . "\n" . '-----------------------------------------------';
490
							$titled = true;
491
						}
492
						$email['body'] .= "\n" . sprintf($langtxt[$lang]['topic_lines'], $topic['subject'], $board['name']);
493
					}
494
			if ($titled)
495
				$email['body'] .= "\n";
496
		}
497
498
		// What about replies?
499
		if (isset($types['reply']))
500
		{
501
			$titled = false;
502
			foreach ($types['reply'] as $id => $board)
503
				foreach ($board['lines'] as $topic)
504
					if (in_array($mid, $topic['members']))
505
					{
506
						if (!$titled)
507
						{
508
							$email['body'] .= "\n" . $langtxt[$lang]['new_replies'] . ':' . "\n" . '-----------------------------------------------';
509
							$titled = true;
510
						}
511
						$email['body'] .= "\n" . ($topic['count'] == 1 ? sprintf($langtxt[$lang]['replies_one'], $topic['subject']) : sprintf($langtxt[$lang]['replies_many'], $topic['count'], $topic['subject']));
512
					}
513
514
			if ($titled)
515
				$email['body'] .= "\n";
516
		}
517
518
		// Finally, moderation actions!
519
		if ($notify_types < 3)
520
		{
521
			$titled = false;
522
			foreach ($types as $note_type => $type)
523
			{
524
				if ($note_type == 'topic' || $note_type == 'reply')
525
					continue;
526
527
				foreach ($type as $id => $board)
528
					foreach ($board['lines'] as $topic)
529
						if (in_array($mid, $topic['members']))
530
						{
531
							if (!$titled)
532
							{
533
								$email['body'] .= "\n" . $langtxt[$lang]['mod_actions'] . ':' . "\n" . '-----------------------------------------------';
534
								$titled = true;
535
							}
536
							$email['body'] .= "\n" . sprintf($langtxt[$lang][$note_type], $topic['subject']);
537
						}
538
			}
539
		}
540
541
		call_integration_hook('integrate_daily_digest_email', array(&$email, $types, $notify_types, $langtxt));
542
543
		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...
544
			$email['body'] .= "\n";
545
546
		// Then just say our goodbyes!
547
		$email['body'] .= "\n\n" . sprintf($txt['regards_team'], $context['forum_name']);
548
549
		// Send it - low priority!
550
		sendmail($email['email'], $email['subject'], $email['body'], null, 'digest', false, 4);
551
552
		$members_sent[] = $mid;
553
	}
554
555
	// Clean up...
556
	if ($is_weekly)
557
	{
558
		$smcFunc['db_query']('', '
559
			DELETE FROM {db_prefix}log_digest
560
			WHERE daily != {int:not_daily}',
561
			array(
562
				'not_daily' => 0,
563
			)
564
		);
565
		$smcFunc['db_query']('', '
566
			UPDATE {db_prefix}log_digest
567
			SET daily = {int:daily_value}
568
			WHERE daily = {int:not_daily}',
569
			array(
570
				'daily_value' => 2,
571
				'not_daily' => 0,
572
			)
573
		);
574
	}
575
	else
576
	{
577
		// Clear any only weekly ones, and stop us from sending daily again.
578
		$smcFunc['db_query']('', '
579
			DELETE FROM {db_prefix}log_digest
580
			WHERE daily = {int:daily_value}',
581
			array(
582
				'daily_value' => 2,
583
			)
584
		);
585
		$smcFunc['db_query']('', '
586
			UPDATE {db_prefix}log_digest
587
			SET daily = {int:both_value}
588
			WHERE daily = {int:no_value}',
589
			array(
590
				'both_value' => 1,
591
				'no_value' => 0,
592
			)
593
		);
594
	}
595
596
	// Just in case the member changes their settings mark this as sent.
597
	if (!empty($members_sent))
598
	{
599
		$smcFunc['db_query']('', '
600
			UPDATE {db_prefix}log_notify
601
			SET sent = {int:is_sent}
602
			WHERE id_member IN ({array_int:member_list})',
603
			array(
604
				'member_list' => $members_sent,
605
				'is_sent' => 1,
606
			)
607
		);
608
	}
609
610
	// Log we've done it...
611
	return true;
612
}
613
614
/**
615
 * Like the daily stuff - just seven times less regular ;)
616
 */
617
function scheduled_weekly_digest()
618
{
619
	global $is_weekly;
620
621
	// We just pass through to the daily function - avoid duplication!
622
	$is_weekly = true;
623
	return scheduled_daily_digest();
624
}
625
626
/**
627
 * Send a group of emails from the mail queue.
628
 *
629
 * @param bool|int $number The number to send each loop through or false to use the standard limits
630
 * @param bool $override_limit Whether to bypass the limit
631
 * @param bool $force_send Whether to forcibly send the messages now (useful when using cron jobs)
632
 * @return bool Whether things were sent
633
 */
634
function ReduceMailQueue($number = false, $override_limit = false, $force_send = false)
635
{
636
	global $modSettings, $smcFunc, $sourcedir, $txt, $language;
637
638
	// Are we intending another script to be sending out the queue?
639
	if (!empty($modSettings['mail_queue_use_cron']) && empty($force_send))
640
		return false;
641
642
	// Just in case we run into a problem.
643
	if (!isset($txt))
644
	{
645
		loadEssentialThemeData();
646
		loadLanguage('Errors', $language, false);
647
		loadLanguage('index', $language, false);
648
	}
649
650
	// By default send 5 at once.
651
	if (!$number)
652
		$number = empty($modSettings['mail_quantity']) ? 5 : $modSettings['mail_quantity'];
653
654
	// If we came with a timestamp, and that doesn't match the next event, then someone else has beaten us.
655
	if (isset($_GET['ts']) && $_GET['ts'] != $modSettings['mail_next_send'] && empty($force_send))
656
		return false;
657
658
	// By default move the next sending on by 10 seconds, and require an affected row.
659
	if (!$override_limit)
660
	{
661
		$delay = !empty($modSettings['mail_queue_delay']) ? $modSettings['mail_queue_delay'] : (!empty($modSettings['mail_limit']) && $modSettings['mail_limit'] < 5 ? 10 : 5);
662
663
		$smcFunc['db_query']('', '
664
			UPDATE {db_prefix}settings
665
			SET value = {string:next_mail_send}
666
			WHERE variable = {literal:mail_next_send}
667
				AND value = {string:last_send}',
668
			array(
669
				'next_mail_send' => time() + $delay,
670
				'last_send' => $modSettings['mail_next_send'],
671
			)
672
		);
673
		if ($smcFunc['db_affected_rows']() == 0)
674
			return false;
675
		$modSettings['mail_next_send'] = time() + $delay;
676
	}
677
678
	// If we're not overriding how many are we allow to send?
679
	if (!$override_limit && !empty($modSettings['mail_limit']))
680
	{
681
		list ($mt, $mn) = @explode('|', $modSettings['mail_recent']);
682
683
		// Nothing worth noting...
684
		if (empty($mn) || $mt < time() - 60)
685
		{
686
			$mt = time();
687
			$mn = $number;
688
		}
689
		// Otherwise we have a few more we can spend?
690
		elseif ($mn < $modSettings['mail_limit'])
691
		{
692
			$mn += $number;
693
		}
694
		// No more I'm afraid, return!
695
		else
696
			return false;
697
698
		// Reflect that we're about to send some, do it now to be safe.
699
		updateSettings(array('mail_recent' => $mt . '|' . $mn));
700
	}
701
702
	// Now we know how many we're sending, let's send them.
703
	$request = $smcFunc['db_query']('', '
704
		SELECT id_mail, recipient, body, subject, headers, send_html, time_sent, private
705
		FROM {db_prefix}mail_queue
706
		ORDER BY priority ASC, id_mail ASC
707
		LIMIT {int:limit}',
708
		array(
709
			'limit' => $number,
710
		)
711
	);
712
	$ids = array();
713
	$emails = array();
714
	while ($row = $smcFunc['db_fetch_assoc']($request))
715
	{
716
		// We want to delete these from the database ASAP, so just get the data and go.
717
		$ids[] = $row['id_mail'];
718
		$emails[] = array(
719
			'to' => $row['recipient'],
720
			'body' => $row['body'],
721
			'subject' => $row['subject'],
722
			'headers' => $row['headers'],
723
			'send_html' => $row['send_html'],
724
			'time_sent' => $row['time_sent'],
725
			'private' => $row['private'],
726
		);
727
	}
728
	$smcFunc['db_free_result']($request);
729
730
	// Delete, delete, delete!!!
731
	if (!empty($ids))
732
		$smcFunc['db_query']('', '
733
			DELETE FROM {db_prefix}mail_queue
734
			WHERE id_mail IN ({array_int:mail_list})',
735
			array(
736
				'mail_list' => $ids,
737
			)
738
		);
739
740
	// Don't believe we have any left?
741
	if (count($ids) < $number)
742
	{
743
		// Only update the setting if no-one else has beaten us to it.
744
		$smcFunc['db_query']('', '
745
			UPDATE {db_prefix}settings
746
			SET value = {string:no_send}
747
			WHERE variable = {literal:mail_next_send}
748
				AND value = {string:last_mail_send}',
749
			array(
750
				'no_send' => '0',
751
				'last_mail_send' => $modSettings['mail_next_send'],
752
			)
753
		);
754
	}
755
756
	if (empty($ids))
757
		return false;
758
759
	if (!empty($modSettings['mail_type']) && $modSettings['smtp_host'] != '')
760
		require_once($sourcedir . '/Subs-Post.php');
761
762
	// Send each email, yea!
763
	$failed_emails = array();
764
	foreach ($emails as $email)
765
	{
766
		if (empty($modSettings['mail_type']) || $modSettings['smtp_host'] == '')
767
		{
768
			$email['subject'] = strtr($email['subject'], array("\r" => '', "\n" => ''));
769
			if (!empty($modSettings['mail_strip_carriage']))
770
			{
771
				$email['body'] = strtr($email['body'], array("\r" => ''));
772
				$email['headers'] = strtr($email['headers'], array("\r" => ''));
773
			}
774
775
			// No point logging a specific error here, as we have no language. PHP error is helpful anyway...
776
			$result = mail(strtr($email['to'], array("\r" => '', "\n" => '')), $email['subject'], $email['body'], $email['headers']);
777
778
			// Try to stop a timeout, this would be bad...
779
			@set_time_limit(300);
780
			if (function_exists('apache_reset_timeout'))
781
				@apache_reset_timeout();
782
		}
783
		else
784
			$result = smtp_mail(array($email['to']), $email['subject'], $email['body'], $email['headers']);
785
786
		// Hopefully it sent?
787
		if (!$result)
788
			$failed_emails[] = array($email['to'], $email['body'], $email['subject'], $email['headers'], $email['send_html'], $email['time_sent'], $email['private']);
789
	}
790
791
	// Any emails that didn't send?
792
	if (!empty($failed_emails))
793
	{
794
		// Update the failed attempts check.
795
		$smcFunc['db_insert']('replace',
796
			'{db_prefix}settings',
797
			array('variable' => 'string', 'value' => 'string'),
798
			array('mail_failed_attempts', empty($modSettings['mail_failed_attempts']) ? 1 : ++$modSettings['mail_failed_attempts']),
799
			array('variable')
800
		);
801
802
		// If we have failed to many times, tell mail to wait a bit and try again.
803
		if ($modSettings['mail_failed_attempts'] > 5)
804
			$smcFunc['db_query']('', '
805
				UPDATE {db_prefix}settings
806
				SET value = {string:next_mail_send}
807
				WHERE variable = {literal:mail_next_send}
808
					AND value = {string:last_send}',
809
				array(
810
					'next_mail_send' => time() + 60,
811
					'last_send' => $modSettings['mail_next_send'],
812
				)
813
			);
814
815
		// Add our email back to the queue, manually.
816
		$smcFunc['db_insert']('insert',
817
			'{db_prefix}mail_queue',
818
			array('recipient' => 'string', 'body' => 'string', 'subject' => 'string', 'headers' => 'string', 'send_html' => 'string', 'time_sent' => 'string', 'private' => 'int'),
819
			$failed_emails,
820
			array('id_mail')
821
		);
822
823
		return false;
824
	}
825
	// We where unable to send the email, clear our failed attempts.
826
	elseif (!empty($modSettings['mail_failed_attempts']))
827
		$smcFunc['db_query']('', '
828
			UPDATE {db_prefix}settings
829
			SET value = {string:zero}
830
			WHERE variable = {string:mail_failed_attempts}',
831
			array(
832
				'zero' => '0',
833
				'mail_failed_attempts' => 'mail_failed_attempts',
834
			)
835
		);
836
837
	// Had something to send...
838
	return true;
839
}
840
841
/**
842
 * Calculate the next time the passed tasks should be triggered.
843
 *
844
 * @param string|array $tasks The ID of a single task or an array of tasks
845
 * @param bool $forceUpdate Whether to force the tasks to run now
846
 */
847
function CalculateNextTrigger($tasks = array(), $forceUpdate = false)
848
{
849
	global $modSettings, $smcFunc;
850
851
	$task_query = '';
852
	if (!is_array($tasks))
853
		$tasks = array($tasks);
854
855
	// Actually have something passed?
856
	if (!empty($tasks))
857
	{
858
		if (!isset($tasks[0]) || is_numeric($tasks[0]))
859
			$task_query = ' AND id_task IN ({array_int:tasks})';
860
		else
861
			$task_query = ' AND task IN ({array_string:tasks})';
862
	}
863
	$nextTaskTime = empty($tasks) ? time() + 86400 : $modSettings['next_task_time'];
864
865
	// Get the critical info for the tasks.
866
	$request = $smcFunc['db_query']('', '
867
		SELECT id_task, next_time, time_offset, time_regularity, time_unit
868
		FROM {db_prefix}scheduled_tasks
869
		WHERE disabled = {int:no_disabled}
870
			' . $task_query,
871
		array(
872
			'no_disabled' => 0,
873
			'tasks' => $tasks,
874
		)
875
	);
876
	$tasks = array();
877
	while ($row = $smcFunc['db_fetch_assoc']($request))
878
	{
879
		$next_time = next_time($row['time_regularity'], $row['time_unit'], $row['time_offset']);
880
881
		// Only bother moving the task if it's out of place or we're forcing it!
882
		if ($forceUpdate || $next_time < $row['next_time'] || $row['next_time'] < time())
883
			$tasks[$row['id_task']] = $next_time;
884
		else
885
			$next_time = $row['next_time'];
886
887
		// If this is sooner than the current next task, make this the next task.
888
		if ($next_time < $nextTaskTime)
889
			$nextTaskTime = $next_time;
890
	}
891
	$smcFunc['db_free_result']($request);
892
893
	// Now make the changes!
894
	foreach ($tasks as $id => $time)
895
		$smcFunc['db_query']('', '
896
			UPDATE {db_prefix}scheduled_tasks
897
			SET next_time = {int:next_time}
898
			WHERE id_task = {int:id_task}',
899
			array(
900
				'next_time' => $time,
901
				'id_task' => $id,
902
			)
903
		);
904
905
	// If the next task is now different update.
906
	if ($modSettings['next_task_time'] != $nextTaskTime)
907
		updateSettings(array('next_task_time' => $nextTaskTime));
908
}
909
910
/**
911
 * Simply returns a time stamp of the next instance of these time parameters.
912
 *
913
 * @param int $regularity The regularity
914
 * @param string $unit What unit are we using - 'm' for minutes, 'd' for days, 'w' for weeks or anything else for seconds
915
 * @param int $offset The offset
916
 * @return int The timestamp for the specified time
917
 */
918
function next_time($regularity, $unit, $offset)
919
{
920
	// Just in case!
921
	if ($regularity == 0)
922
		$regularity = 2;
923
924
	$curMin = date('i', time());
925
926
	// If the unit is minutes only check regularity in minutes.
927
	if ($unit == 'm')
928
	{
929
		$off = date('i', $offset);
930
931
		// If it's now just pretend it ain't,
932
		if ($off == $curMin)
933
			$next_time = time() + $regularity;
934
		else
935
		{
936
			// Make sure that the offset is always in the past.
937
			$off = $off > $curMin ? $off - 60 : $off;
938
939
			while ($off <= $curMin)
940
				$off += $regularity;
941
942
			// Now we know when the time should be!
943
			$next_time = time() + 60 * ($off - $curMin);
944
		}
945
	}
946
	// Otherwise, work out what the offset would be with today's date.
947
	else
948
	{
949
		$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

949
		$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('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

949
		$next_time = mktime(date('H', $offset), /** @scrutinizer ignore-type */ date('i', $offset), 0, date('m'), date('d'), 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

949
		$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('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

949
		$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('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

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

1574
		DeleteDraft(/** @scrutinizer ignore-type */ $drafts, false);
Loading history...
1575
	}
1576
1577
	return true;
1578
}
1579
1580
/**
1581
 * Prune log_topics, log_boards & log_mark_boards_read.
1582
 * For users who haven't been active in a long time, purge these records.
1583
 * For users who haven't been active in a shorter time, mark boards as read,
1584
 * pruning log_topics.
1585
 */
1586
function scheduled_prune_log_topics()
1587
{
1588
	global $smcFunc, $sourcedir, $modSettings;
1589
1590
	// If set to zero, bypass
1591
	if (empty($modSettings['mark_read_max_users']) || (empty($modSettings['mark_read_beyond']) && empty($modSettings['mark_read_delete_beyond'])))
1592
		return true;
1593
1594
	// Convert to timestamps for comparison
1595
	if (empty($modSettings['mark_read_beyond']))
1596
		$markReadCutoff = 0;
1597
	else
1598
		$markReadCutoff = time() - $modSettings['mark_read_beyond'] * 86400;
1599
1600
	if (empty($modSettings['mark_read_delete_beyond']))
1601
		$cleanupBeyond = 0;
1602
	else
1603
		$cleanupBeyond = time() - $modSettings['mark_read_delete_beyond'] * 86400;
1604
1605
	$maxMembers = $modSettings['mark_read_max_users'];
1606
1607
	// You're basically saying to just purge, so just purge
1608
	if ($markReadCutoff < $cleanupBeyond)
1609
		$markReadCutoff = $cleanupBeyond;
1610
1611
	// Try to prevent timeouts
1612
	@set_time_limit(300);
1613
	if (function_exists('apache_reset_timeout'))
1614
		@apache_reset_timeout();
1615
1616
	// Start off by finding the records in log_boards, log_topics & log_mark_read
1617
	// for users who haven't been around the longest...
1618
	$members = array();
0 ignored issues
show
Unused Code introduced by
The assignment to $members is dead and can be removed.
Loading history...
1619
	$sql = 'SELECT lb.id_member, m.last_login
1620
			FROM {db_prefix}members m
1621
			INNER JOIN
1622
			(
1623
				SELECT DISTINCT id_member
1624
				FROM {db_prefix}log_boards
1625
			) lb ON m.id_member = lb.id_member
1626
			WHERE m.last_login <= {int:dcutoff}
1627
		UNION
1628
		SELECT lmr.id_member, m.last_login
1629
			FROM {db_prefix}members m
1630
			INNER JOIN
1631
			(
1632
				SELECT DISTINCT id_member
1633
				FROM {db_prefix}log_mark_read
1634
			) lmr ON m.id_member = lmr.id_member
1635
			WHERE m.last_login <= {int:dcutoff}
1636
		UNION
1637
		SELECT lt.id_member, m.last_login
1638
			FROM {db_prefix}members m
1639
			INNER JOIN
1640
			(
1641
				SELECT DISTINCT id_member
1642
				FROM {db_prefix}log_topics
1643
				WHERE unwatched = {int:unwatched}
1644
			) lt ON m.id_member = lt.id_member
1645
			WHERE m.last_login <= {int:mrcutoff}
1646
		ORDER BY last_login
1647
		LIMIT {int:limit}';
1648
	$result = $smcFunc['db_query']('', $sql,
1649
		array(
1650
			'limit' => $maxMembers,
1651
			'dcutoff' => $cleanupBeyond,
1652
			'mrcutoff' => $markReadCutoff,
1653
			'unwatched' => 0,
1654
		)
1655
	);
1656
1657
	// Move to array...
1658
	$members = $smcFunc['db_fetch_all']($result);
1659
	$smcFunc['db_free_result']($result);
1660
1661
	// Nothing to do?
1662
	if (empty($members))
1663
		return true;
1664
1665
	// Determine action based on last_login...
1666
	$purgeMembers = array();
1667
	$markReadMembers = array();
1668
	foreach($members as $member)
1669
	{
1670
		if ($member['last_login'] <= $cleanupBeyond)
1671
			$purgeMembers[] = $member['id_member'];
1672
		elseif ($member['last_login'] <= $markReadCutoff)
1673
			$markReadMembers[] = $member['id_member'];
1674
	}
1675
1676
	if (!empty($purgeMembers) && !empty($modSettings['mark_read_delete_beyond']))
1677
	{
1678
		// Delete rows from log_boards
1679
		$sql = 'DELETE FROM {db_prefix}log_boards
1680
			WHERE id_member IN ({array_int:members})';
1681
		$smcFunc['db_query']('', $sql,
1682
			array(
1683
				'members' => $purgeMembers,
1684
			)
1685
		);
1686
		// Delete rows from log_mark_read
1687
		$sql = 'DELETE FROM {db_prefix}log_mark_read
1688
			WHERE id_member IN ({array_int:members})';
1689
		$smcFunc['db_query']('', $sql,
1690
			array(
1691
				'members' => $purgeMembers,
1692
			)
1693
		);
1694
		// Delete rows from log_topics
1695
		$sql = 'DELETE FROM {db_prefix}log_topics
1696
			WHERE id_member IN ({array_int:members})
1697
				AND unwatched = {int:unwatched}';
1698
		$smcFunc['db_query']('', $sql,
1699
			array(
1700
				'members' => $purgeMembers,
1701
				'unwatched' => 0,
1702
			)
1703
		);
1704
	}
1705
1706
	// Nothing left to do?
1707
	if (empty($markReadMembers) || empty($modSettings['mark_read_beyond']))
1708
		return true;
1709
1710
	// Find board inserts to perform...
1711
	// Get board info for each member from log_topics.
1712
	// Note this user may have read many topics on that board, 
1713
	// but we just want one row each, & the ID of the last message read in each board.
1714
	$boards = array();
0 ignored issues
show
Unused Code introduced by
The assignment to $boards is dead and can be removed.
Loading history...
1715
	$sql = 'SELECT lt.id_member, t.id_board, MAX(lt.id_msg) AS id_last_message
1716
		FROM {db_prefix}topics t
1717
		INNER JOIN
1718
		(
1719
			SELECT id_member, id_topic, id_msg
1720
			FROM {db_prefix}log_topics
1721
			WHERE id_member IN ({array_int:members})
1722
		) lt ON t.id_topic = lt.id_topic
1723
		GROUP BY lt.id_member, t.id_board';
1724
	$result = $smcFunc['db_query']('', $sql,
1725
		array(
1726
			'members' => $markReadMembers,
1727
		)
1728
	);
1729
	$boards = $smcFunc['db_fetch_all']($result);
1730
	$smcFunc['db_free_result']($result);
1731
1732
	// Create one SQL statement for this set of inserts
1733
	if (!empty($boards))
1734
	{
1735
		$smcFunc['db_insert']('replace',
1736
			'{db_prefix}log_mark_read',
1737
			array('id_member' => 'int', 'id_board' => 'int', 'id_msg' => 'int'),
1738
			$boards,
1739
			array('id_member', 'id_board')
1740
		);
1741
	}
1742
1743
	// Finally, delete this set's rows from log_topics
1744
	$sql = 'DELETE FROM {db_prefix}log_topics
1745
		WHERE id_member IN ({array_int:members})
1746
			AND unwatched = {int:unwatched}';
1747
	$smcFunc['db_query']('', $sql,
1748
		array(
1749
			'members' => $markReadMembers,
1750
			'unwatched' => 0,
1751
		)
1752
	);
1753
1754
	return true;
1755
}
1756
1757
?>