Completed
Push — release-2.1 ( b11117...7cbb85 )
by Colin
23:04 queued 14:33
created

ScheduledTasks.php ➔ scheduled_daily_maintenance()   D

Complexity

Conditions 13
Paths 16

Size

Total Lines 98
Code Lines 44

Duplication

Lines 11
Ratio 11.22 %

Importance

Changes 0
Metric Value
cc 13
eloc 44
nc 16
nop 0
dl 11
loc 98
rs 4.9922
c 0
b 0
f 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 2018 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
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;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
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 View Code Duplication
			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);
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
 * Function for sending out approval notices to moderators etc.
163
 */
164
function scheduled_approval_notification()
165
{
166
	global $scripturl, $txt, $sourcedir, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
167
168
	// Grab all the items awaiting approval and sort type then board - clear up any things that are no longer relevant.
169
	$request = $smcFunc['db_query']('', '
170
		SELECT aq.id_msg, aq.id_attach, aq.id_event, m.id_topic, m.id_board, m.subject, t.id_first_msg,
171
			b.id_profile
172
		FROM {db_prefix}approval_queue AS aq
173
			JOIN {db_prefix}messages AS m ON (m.id_msg = aq.id_msg)
174
			JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
175
			JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)',
176
		array(
177
		)
178
	);
179
	$notices = array();
180
	$profiles = array();
181
	while ($row = $smcFunc['db_fetch_assoc']($request))
182
	{
183
		// If this is no longer around we'll ignore it.
184
		if (empty($row['id_topic']))
185
			continue;
186
187
		// What type is it?
188
		if ($row['id_first_msg'] && $row['id_first_msg'] == $row['id_msg'])
189
			$type = 'topic';
190
		elseif ($row['id_attach'])
191
			$type = 'attach';
192
		else
193
			$type = 'msg';
194
195
		// Add it to the array otherwise.
196
		$notices[$row['id_board']][$type][] = array(
197
			'subject' => $row['subject'],
198
			'href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'],
199
		);
200
201
		// Store the profile for a bit later.
202
		$profiles[$row['id_board']] = $row['id_profile'];
203
	}
204
	$smcFunc['db_free_result']($request);
205
206
	// Delete it all!
207
	$smcFunc['db_query']('', '
208
		TRUNCATE {db_prefix}approval_queue',
209
		array(
210
		)
211
	);
212
213
	// If nothing quit now.
214
	if (empty($notices))
215
		return true;
216
217
	// Now we need to think about finding out *who* can approve - this is hard!
218
219
	// First off, get all the groups with this permission and sort by board.
220
	$request = $smcFunc['db_query']('', '
221
		SELECT id_group, id_profile, add_deny
222
		FROM {db_prefix}board_permissions
223
		WHERE permission = {literal:approve_posts}
224
			AND id_profile IN ({array_int:profile_list})',
225
		array(
226
			'profile_list' => $profiles,
227
		)
228
	);
229
	$perms = array();
230
	$addGroups = array(1);
231
	while ($row = $smcFunc['db_fetch_assoc']($request))
232
	{
233
		// Sorry guys, but we have to ignore guests AND members - it would be too many otherwise.
234
		if ($row['id_group'] < 2)
235
			continue;
236
237
		$perms[$row['id_profile']][$row['add_deny'] ? 'add' : 'deny'][] = $row['id_group'];
238
239
		// Anyone who can access has to be considered.
240
		if ($row['add_deny'])
241
			$addGroups[] = $row['id_group'];
242
	}
243
	$smcFunc['db_free_result']($request);
244
245
	// Grab the moderators if they have permission!
246
	$mods = array();
247
	$members = array();
248
	if (in_array(2, $addGroups))
249
	{
250
		$request = $smcFunc['db_query']('', '
251
			SELECT id_member, id_board
252
			FROM {db_prefix}moderators',
253
			array(
254
			)
255
		);
256 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
257
		{
258
			$mods[$row['id_member']][$row['id_board']] = true;
259
			// Make sure they get included in the big loop.
260
			$members[] = $row['id_member'];
261
		}
262
		$smcFunc['db_free_result']($request);
263
	}
264
265
	// Come along one and all... until we reject you ;)
266
	$request = $smcFunc['db_query']('', '
267
		SELECT id_member, real_name, email_address, lngfile, id_group, additional_groups, mod_prefs
268
		FROM {db_prefix}members
269
		WHERE id_group IN ({array_int:additional_group_list})
270
			OR FIND_IN_SET({raw:additional_group_list_implode}, additional_groups) != 0' . (empty($members) ? '' : '
271
			OR id_member IN ({array_int:member_list})') . '
272
		ORDER BY lngfile',
273
		array(
274
			'additional_group_list' => $addGroups,
275
			'member_list' => $members,
276
			'additional_group_list_implode' => implode(', additional_groups) != 0 OR FIND_IN_SET(', $addGroups),
277
		)
278
	);
279
	$members = array();
280
	while ($row = $smcFunc['db_fetch_assoc']($request))
281
	{
282
		// Check whether they are interested.
283
		if (!empty($row['mod_prefs']))
284
		{
285
			list(,, $pref_binary) = explode('|', $row['mod_prefs']);
286
			if (!($pref_binary & 4))
287
				continue;
288
		}
289
290
		$members[$row['id_member']] = array(
291
			'id' => $row['id_member'],
292
			'groups' => array_merge(explode(',', $row['additional_groups']), array($row['id_group'])),
293
			'language' => $row['lngfile'],
294
			'email' => $row['email_address'],
295
			'name' => $row['real_name'],
296
		);
297
	}
298
	$smcFunc['db_free_result']($request);
299
300
	// Get the mailing stuff.
301
	require_once($sourcedir . '/Subs-Post.php');
302
	// Need the below for loadLanguage to work!
303
	loadEssentialThemeData();
304
305
	$current_language = '';
306
	// Finally, loop through each member, work out what they can do, and send it.
307
	foreach ($members as $id => $member)
308
	{
309
		$emailbody = '';
310
311
		// Load the language file as required.
312
		if (empty($current_language) || $current_language != $member['language'])
313
			$current_language = loadLanguage('EmailTemplates', $member['language'], false);
314
315
		// Loop through each notice...
316
		foreach ($notices as $board => $notice)
317
		{
318
			$access = false;
319
320
			// Can they mod in this board?
321
			if (isset($mods[$id][$board]))
322
				$access = true;
323
324
			// Do the group check...
325
			if (!$access && isset($perms[$profiles[$board]]['add']))
326
			{
327
				// They can access?!
328 View Code Duplication
				if (array_intersect($perms[$profiles[$board]]['add'], $member['groups']))
329
					$access = true;
330
331
				// If they have deny rights don't consider them!
332
				if (isset($perms[$profiles[$board]]['deny']))
333 View Code Duplication
					if (array_intersect($perms[$profiles[$board]]['deny'], $member['groups']))
334
						$access = false;
335
			}
336
337
			// Finally, fix it for admins!
338
			if (in_array(1, $member['groups']))
339
				$access = true;
340
341
			// If they can't access it then give it a break!
342
			if (!$access)
343
				continue;
344
345
			foreach ($notice as $type => $items)
346
			{
347
				// Build up the top of this section.
348
				$emailbody .= $txt['scheduled_approval_email_' . $type] . "\n" .
349
					'------------------------------------------------------' . "\n";
350
351
				foreach ($items as $item)
352
					$emailbody .= $item['subject'] . ' - ' . $item['href'] . "\n";
353
354
				$emailbody .= "\n";
355
			}
356
		}
357
358
		if ($emailbody == '')
359
			continue;
360
361
		$replacements = array(
362
			'REALNAME' => $member['name'],
363
			'BODY' => $emailbody,
364
		);
365
366
		$emaildata = loadEmailTemplate('scheduled_approval', $replacements, $current_language);
367
368
		// Send the actual email.
369
		sendmail($member['email'], $emaildata['subject'], $emaildata['body'], null, 'schedapp', $emaildata['is_html'], 2);
370
	}
371
372
	// All went well!
373
	return true;
374
}
375
376
/**
377
 * Do some daily cleaning up.
378
 */
379
function scheduled_daily_maintenance()
380
{
381
	global $smcFunc, $modSettings, $sourcedir, $db_type, $image_proxy_enabled;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
382
383
	// First clean out the cache.
384
	clean_cache();
385
386
	// If warning decrement is enabled and we have people who have not had a new warning in 24 hours, lower their warning level.
387
	list (, , $modSettings['warning_decrement']) = explode(',', $modSettings['warning_settings']);
388
	if ($modSettings['warning_decrement'])
389
	{
390
		// Find every member who has a warning level...
391
		$request = $smcFunc['db_query']('', '
392
			SELECT id_member, warning
393
			FROM {db_prefix}members
394
			WHERE warning > {int:no_warning}',
395
			array(
396
				'no_warning' => 0,
397
			)
398
		);
399
		$members = array();
400
		while ($row = $smcFunc['db_fetch_assoc']($request))
401
			$members[$row['id_member']] = $row['warning'];
402
		$smcFunc['db_free_result']($request);
403
404
		// Have some members to check?
405
		if (!empty($members))
406
		{
407
			// Find out when they were last warned.
408
			$request = $smcFunc['db_query']('', '
409
				SELECT id_recipient, MAX(log_time) AS last_warning
410
				FROM {db_prefix}log_comments
411
				WHERE id_recipient IN ({array_int:member_list})
412
					AND comment_type = {string:warning}
413
				GROUP BY id_recipient',
414
				array(
415
					'member_list' => array_keys($members),
416
					'warning' => 'warning',
417
				)
418
			);
419
			$member_changes = array();
420
			while ($row = $smcFunc['db_fetch_assoc']($request))
421
			{
422
				// More than 24 hours ago?
423
				if ($row['last_warning'] <= time() - 86400)
424
					$member_changes[] = array(
425
						'id' => $row['id_recipient'],
426
						'warning' => $members[$row['id_recipient']] >= $modSettings['warning_decrement'] ? $members[$row['id_recipient']] - $modSettings['warning_decrement'] : 0,
427
					);
428
			}
429
			$smcFunc['db_free_result']($request);
430
431
			// Have some members to change?
432 View Code Duplication
			if (!empty($member_changes))
433
				foreach ($member_changes as $change)
434
					$smcFunc['db_query']('', '
435
						UPDATE {db_prefix}members
436
						SET warning = {int:warning}
437
						WHERE id_member = {int:id_member}',
438
						array(
439
							'warning' => $change['warning'],
440
							'id_member' => $change['id'],
441
						)
442
					);
443
		}
444
	}
445
446
	// Do any spider stuff.
447
	if (!empty($modSettings['spider_mode']) && $modSettings['spider_mode'] > 1)
448
	{
449
		require_once($sourcedir . '/ManageSearchEngines.php');
450
		consolidateSpiderStats();
451
	}
452
453
	// Clean up some old login history information.
454
	$smcFunc['db_query']('', '
455
		DELETE FROM {db_prefix}member_logins
456
		WHERE time < {int:oldLogins}',
457
		array(
458
			'oldLogins' => time() - (!empty($modSettings['loginHistoryDays']) ? 60 * 60 * 24 * $modSettings['loginHistoryDays'] : 2592000),
459
	));
460
461
	// Run Imageproxy housekeeping
462
	if (!empty($image_proxy_enabled))
463
	{
464
		global $proxyhousekeeping;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
465
		$proxyhousekeeping = true;
466
467
		require_once(dirname(__FILE__) . '/proxy.php');
468
		$proxy = new ProxyServer();
469
		$proxy->housekeeping();
470
471
		unset($proxyhousekeeping);
472
	}
473
474
	// Log we've done it...
475
	return true;
476
}
477
478
/**
479
 * Send out a daily email of all subscribed topics.
480
 */
481
function scheduled_daily_digest()
482
{
483
	global $is_weekly, $txt, $mbname, $scripturl, $sourcedir, $smcFunc, $context, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
484
485
	// We'll want this...
486
	require_once($sourcedir . '/Subs-Post.php');
487
	loadEssentialThemeData();
488
489
	$is_weekly = !empty($is_weekly) ? 1 : 0;
490
491
	// Right - get all the notification data FIRST.
492
	$request = $smcFunc['db_query']('', '
493
		SELECT ln.id_topic, COALESCE(t.id_board, ln.id_board) AS id_board, mem.email_address, mem.member_name,
494
			mem.lngfile, mem.id_member
495
		FROM {db_prefix}log_notify AS ln
496
			JOIN {db_prefix}members AS mem ON (mem.id_member = ln.id_member)
497
			LEFT JOIN {db_prefix}topics AS t ON (ln.id_topic != {int:empty_topic} AND t.id_topic = ln.id_topic)
498
		WHERE mem.is_activated = {int:is_activated}',
499
		array(
500
			'empty_topic' => 0,
501
			'is_activated' => 1,
502
		)
503
	);
504
	$members = array();
505
	$langs = array();
506
	$notify = array();
507
	while ($row = $smcFunc['db_fetch_assoc']($request))
508
	{
509
		if (!isset($members[$row['id_member']]))
510
		{
511
			$members[$row['id_member']] = array(
512
				'email' => $row['email_address'],
513
				'name' => $row['member_name'],
514
				'id' => $row['id_member'],
515
				'lang' => $row['lngfile'],
516
			);
517
			$langs[$row['lngfile']] = $row['lngfile'];
518
		}
519
520
		// Store this useful data!
521
		$boards[$row['id_board']] = $row['id_board'];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$boards was never initialized. Although not strictly required by PHP, it is generally a good practice to add $boards = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
522
		if ($row['id_topic'])
523
			$notify['topics'][$row['id_topic']][] = $row['id_member'];
524
		else
525
			$notify['boards'][$row['id_board']][] = $row['id_member'];
526
	}
527
	$smcFunc['db_free_result']($request);
528
529
	if (empty($boards))
530
		return true;
531
532
	// Just get the board names.
533
	$request = $smcFunc['db_query']('', '
534
		SELECT id_board, name
535
		FROM {db_prefix}boards
536
		WHERE id_board IN ({array_int:board_list})',
537
		array(
538
			'board_list' => $boards,
539
		)
540
	);
541
	$boards = array();
542 View Code Duplication
	while ($row = $smcFunc['db_fetch_assoc']($request))
543
		$boards[$row['id_board']] = $row['name'];
544
	$smcFunc['db_free_result']($request);
545
546
	if (empty($boards))
547
		return true;
548
549
	// Get the actual topics...
550
	$request = $smcFunc['db_query']('', '
551
		SELECT ld.note_type, t.id_topic, t.id_board, t.id_member_started, m.id_msg, m.subject,
552
			b.name AS board_name
553
		FROM {db_prefix}log_digest AS ld
554
			JOIN {db_prefix}topics AS t ON (t.id_topic = ld.id_topic
555
				AND t.id_board IN ({array_int:board_list}))
556
			JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
557
			JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
558
		WHERE ' . ($is_weekly ? 'ld.daily != {int:daily_value}' : 'ld.daily IN (0, 2)'),
559
		array(
560
			'board_list' => array_keys($boards),
561
			'daily_value' => 2,
562
		)
563
	);
564
	$types = array();
565
	while ($row = $smcFunc['db_fetch_assoc']($request))
566
	{
567
		if (!isset($types[$row['note_type']][$row['id_board']]))
568
			$types[$row['note_type']][$row['id_board']] = array(
569
				'lines' => array(),
570
				'name' => $row['board_name'],
571
				'id' => $row['id_board'],
572
			);
573
574
		if ($row['note_type'] == 'reply')
575
		{
576
			if (isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]))
577
				$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['count']++;
578
			else
579
				$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = array(
580
					'id' => $row['id_topic'],
581
					'subject' => un_htmlspecialchars($row['subject']),
582
					'count' => 1,
583
				);
584
		}
585 View Code Duplication
		elseif ($row['note_type'] == 'topic')
586
		{
587
			if (!isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]))
588
				$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = array(
589
					'id' => $row['id_topic'],
590
					'subject' => un_htmlspecialchars($row['subject']),
591
				);
592
		}
593 View Code Duplication
		else
594
		{
595
			if (!isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]))
596
				$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = array(
597
					'id' => $row['id_topic'],
598
					'subject' => un_htmlspecialchars($row['subject']),
599
					'starter' => $row['id_member_started'],
600
				);
601
		}
602
603
		$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['members'] = array();
604 View Code Duplication
		if (!empty($notify['topics'][$row['id_topic']]))
605
			$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']]);
606 View Code Duplication
		if (!empty($notify['boards'][$row['id_board']]))
607
			$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']]);
608
	}
609
	$smcFunc['db_free_result']($request);
610
611
	if (empty($types))
612
		return true;
613
614
	// Let's load all the languages into a cache thingy.
615
	$langtxt = array();
616
	foreach ($langs as $lang)
617
	{
618
		loadLanguage('Post', $lang);
619
		loadLanguage('index', $lang);
620
		loadLanguage('EmailTemplates', $lang);
621
		$langtxt[$lang] = array(
622
			'subject' => $txt['digest_subject_' . ($is_weekly ? 'weekly' : 'daily')],
623
			'char_set' => $txt['lang_character_set'],
624
			'intro' => sprintf($txt['digest_intro_' . ($is_weekly ? 'weekly' : 'daily')], $mbname),
625
			'new_topics' => $txt['digest_new_topics'],
626
			'topic_lines' => $txt['digest_new_topics_line'],
627
			'new_replies' => $txt['digest_new_replies'],
628
			'mod_actions' => $txt['digest_mod_actions'],
629
			'replies_one' => $txt['digest_new_replies_one'],
630
			'replies_many' => $txt['digest_new_replies_many'],
631
			'sticky' => $txt['digest_mod_act_sticky'],
632
			'lock' => $txt['digest_mod_act_lock'],
633
			'unlock' => $txt['digest_mod_act_unlock'],
634
			'remove' => $txt['digest_mod_act_remove'],
635
			'move' => $txt['digest_mod_act_move'],
636
			'merge' => $txt['digest_mod_act_merge'],
637
			'split' => $txt['digest_mod_act_split'],
638
			'bye' => $txt['regards_team'],
639
		);
640
	}
641
642
	// The preferred way...
643
	require_once($sourcedir . '/Subs-Notify.php');
644
	$prefs = getNotifyPrefs(array_keys($members), array('msg_notify_type', 'msg_notify_pref'), true);
645
646
	// Right - send out the silly things - this will take quite some space!
647
	foreach ($members as $mid => $member)
648
	{
649
		$frequency = !empty($prefs[$mid]['msg_notify_pref']) ? $prefs[$mid]['msg_notify_pref'] : 1;
650
		$notify_types = !empty($prefs[$mid]['msg_notify_type']) ? $prefs[$mid]['msg_notify_type'] : 1;
651
652
		// Did they not elect to choose this?
653
		if ($frequency == 4 && !$is_weekly || $frequency == 3 && $is_weekly || $notify_types == 4)
654
			continue;
655
656
		// Right character set!
657
		$context['character_set'] = empty($modSettings['global_character_set']) ? $langtxt[$lang]['char_set'] : $modSettings['global_character_set'];
0 ignored issues
show
Bug introduced by
The variable $lang seems to be defined by a foreach iteration on line 616. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
658
659
		// Do the start stuff!
660
		$email = array(
661
			'subject' => $mbname . ' - ' . $langtxt[$lang]['subject'],
662
			'body' => $member['name'] . ',' . "\n\n" . $langtxt[$lang]['intro'] . "\n" . $scripturl . '?action=profile;area=notification;u=' . $member['id'] . "\n",
663
			'email' => $member['email'],
664
		);
665
666
		// All new topics?
667
		if (isset($types['topic']))
668
		{
669
			$titled = false;
670
			foreach ($types['topic'] as $id => $board)
671 View Code Duplication
				foreach ($board['lines'] as $topic)
672
					if (in_array($mid, $topic['members']))
673
					{
674
						if (!$titled)
675
						{
676
							$email['body'] .= "\n" . $langtxt[$lang]['new_topics'] . ':' . "\n" . '-----------------------------------------------';
677
							$titled = true;
678
						}
679
						$email['body'] .= "\n" . sprintf($langtxt[$lang]['topic_lines'], $topic['subject'], $board['name']);
680
					}
681
			if ($titled)
682
				$email['body'] .= "\n";
683
		}
684
685
		// What about replies?
686
		if (isset($types['reply']))
687
		{
688
			$titled = false;
689
			foreach ($types['reply'] as $id => $board)
690
				foreach ($board['lines'] as $topic)
691
					if (in_array($mid, $topic['members']))
692
					{
693
						if (!$titled)
694
						{
695
							$email['body'] .= "\n" . $langtxt[$lang]['new_replies'] . ':' . "\n" . '-----------------------------------------------';
696
							$titled = true;
697
						}
698
						$email['body'] .= "\n" . ($topic['count'] == 1 ? sprintf($langtxt[$lang]['replies_one'], $topic['subject']) : sprintf($langtxt[$lang]['replies_many'], $topic['count'], $topic['subject']));
699
					}
700
701
			if ($titled)
702
				$email['body'] .= "\n";
703
		}
704
705
		// Finally, moderation actions!
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
706
		if ($notify_types < 3)
707
		{
708
			$titled = false;
709
			foreach ($types as $note_type => $type)
710
			{
711
				if ($note_type == 'topic' || $note_type == 'reply')
712
					continue;
713
714
				foreach ($type as $id => $board)
715 View Code Duplication
					foreach ($board['lines'] as $topic)
716
						if (in_array($mid, $topic['members']))
717
						{
718
							if (!$titled)
719
							{
720
								$email['body'] .= "\n" . $langtxt[$lang]['mod_actions'] . ':' . "\n" . '-----------------------------------------------';
721
								$titled = true;
722
							}
723
							$email['body'] .= "\n" . sprintf($langtxt[$lang][$note_type], $topic['subject']);
724
						}
725
			}
726
		}
727
		if ($titled)
0 ignored issues
show
Bug introduced by
The variable $titled does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
728
			$email['body'] .= "\n";
729
730
		// Then just say our goodbyes!
731
		$email['body'] .= "\n\n" . $txt['regards_team'];
732
733
		// Send it - low priority!
734
		sendmail($email['email'], $email['subject'], $email['body'], null, 'digest', false, 4);
735
	}
736
737
	// Clean up...
738
	if ($is_weekly)
739
	{
740
		$smcFunc['db_query']('', '
741
			DELETE FROM {db_prefix}log_digest
742
			WHERE daily != {int:not_daily}',
743
			array(
744
				'not_daily' => 0,
745
			)
746
		);
747
		$smcFunc['db_query']('', '
748
			UPDATE {db_prefix}log_digest
749
			SET daily = {int:daily_value}
750
			WHERE daily = {int:not_daily}',
751
			array(
752
				'daily_value' => 2,
753
				'not_daily' => 0,
754
			)
755
		);
756
	}
757
	else
758
	{
759
		// Clear any only weekly ones, and stop us from sending daily again.
760
		$smcFunc['db_query']('', '
761
			DELETE FROM {db_prefix}log_digest
762
			WHERE daily = {int:daily_value}',
763
			array(
764
				'daily_value' => 2,
765
			)
766
		);
767
		$smcFunc['db_query']('', '
768
			UPDATE {db_prefix}log_digest
769
			SET daily = {int:both_value}
770
			WHERE daily = {int:no_value}',
771
			array(
772
				'both_value' => 1,
773
				'no_value' => 0,
774
			)
775
		);
776
	}
777
778
	// Just in case the member changes their settings mark this as sent.
779
	$members = array_keys($members);
780
	$smcFunc['db_query']('', '
781
		UPDATE {db_prefix}log_notify
782
		SET sent = {int:is_sent}
783
		WHERE id_member IN ({array_int:member_list})',
784
		array(
785
			'member_list' => $members,
786
			'is_sent' => 1,
787
		)
788
	);
789
790
	// Log we've done it...
791
	return true;
792
}
793
794
/**
795
 * Like the daily stuff - just seven times less regular ;)
796
 */
797
function scheduled_weekly_digest()
798
{
799
	global $is_weekly;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
800
801
	// We just pass through to the daily function - avoid duplication!
802
	$is_weekly = true;
803
	return scheduled_daily_digest();
804
}
805
806
/**
807
 * Send a group of emails from the mail queue.
808
 *
809
 * @param bool|int $number The number to send each loop through or false to use the standard limits
810
 * @param bool $override_limit Whether to bypass the limit
811
 * @param bool $force_send Whether to forcibly send the messages now (useful when using cron jobs)
812
 * @return bool Whether things were sent
813
 */
814
function ReduceMailQueue($number = false, $override_limit = false, $force_send = false)
815
{
816
	global $modSettings, $smcFunc, $sourcedir;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
817
818
	// Are we intending another script to be sending out the queue?
819
	if (!empty($modSettings['mail_queue_use_cron']) && empty($force_send))
820
		return false;
821
822
	// By default send 5 at once.
823
	if (!$number)
824
		$number = empty($modSettings['mail_quantity']) ? 5 : $modSettings['mail_quantity'];
825
826
	// If we came with a timestamp, and that doesn't match the next event, then someone else has beaten us.
827
	if (isset($_GET['ts']) && $_GET['ts'] != $modSettings['mail_next_send'] && empty($force_send))
828
		return false;
829
830
	// By default move the next sending on by 10 seconds, and require an affected row.
831
	if (!$override_limit)
832
	{
833
		$delay = !empty($modSettings['mail_queue_delay']) ? $modSettings['mail_queue_delay'] : (!empty($modSettings['mail_limit']) && $modSettings['mail_limit'] < 5 ? 10 : 5);
834
835
		$smcFunc['db_query']('', '
836
			UPDATE {db_prefix}settings
837
			SET value = {string:next_mail_send}
838
			WHERE variable = {literal:mail_next_send}
839
				AND value = {string:last_send}',
840
			array(
841
				'next_mail_send' => time() + $delay,
842
				'last_send' => $modSettings['mail_next_send'],
843
			)
844
		);
845
		if ($smcFunc['db_affected_rows']() == 0)
846
			return false;
847
		$modSettings['mail_next_send'] = time() + $delay;
848
	}
849
850
	// If we're not overriding how many are we allow to send?
851
	if (!$override_limit && !empty($modSettings['mail_limit']))
852
	{
853
		list ($mt, $mn) = @explode('|', $modSettings['mail_recent']);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $mt. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $mn. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
854
855
		// Nothing worth noting...
856
		if (empty($mn) || $mt < time() - 60)
857
		{
858
			$mt = time();
859
			$mn = $number;
860
		}
861
		// Otherwise we have a few more we can spend?
862
		elseif ($mn < $modSettings['mail_limit'])
863
		{
864
			$mn += $number;
865
		}
866
		// No more I'm afraid, return!
867
		else
868
			return false;
869
870
		// Reflect that we're about to send some, do it now to be safe.
871
		updateSettings(array('mail_recent' => $mt . '|' . $mn));
872
	}
873
874
	// Now we know how many we're sending, let's send them.
875
	$request = $smcFunc['db_query']('', '
876
		SELECT /*!40001 SQL_NO_CACHE */ id_mail, recipient, body, subject, headers, send_html, time_sent, private
877
		FROM {db_prefix}mail_queue
878
		ORDER BY priority ASC, id_mail ASC
879
		LIMIT {int:limit}',
880
		array(
881
			'limit' => $number,
882
		)
883
	);
884
	$ids = array();
885
	$emails = array();
886
	while ($row = $smcFunc['db_fetch_assoc']($request))
887
	{
888
		// We want to delete these from the database ASAP, so just get the data and go.
889
		$ids[] = $row['id_mail'];
890
		$emails[] = array(
891
			'to' => $row['recipient'],
892
			'body' => $row['body'],
893
			'subject' => $row['subject'],
894
			'headers' => $row['headers'],
895
			'send_html' => $row['send_html'],
896
			'time_sent' => $row['time_sent'],
897
			'private' => $row['private'],
898
		);
899
	}
900
	$smcFunc['db_free_result']($request);
901
902
	// Delete, delete, delete!!!
0 ignored issues
show
Unused Code Comprehensibility introduced by
46% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
903
	if (!empty($ids))
904
		$smcFunc['db_query']('', '
905
			DELETE FROM {db_prefix}mail_queue
906
			WHERE id_mail IN ({array_int:mail_list})',
907
			array(
908
				'mail_list' => $ids,
909
			)
910
		);
911
912
	// Don't believe we have any left?
913
	if (count($ids) < $number)
914
	{
915
		// Only update the setting if no-one else has beaten us to it.
916
		$smcFunc['db_query']('', '
917
			UPDATE {db_prefix}settings
918
			SET value = {string:no_send}
919
			WHERE variable = {literal:mail_next_send}
920
				AND value = {string:last_mail_send}',
921
			array(
922
				'no_send' => '0',
923
				'last_mail_send' => $modSettings['mail_next_send'],
924
			)
925
		);
926
	}
927
928
	if (empty($ids))
929
		return false;
930
931
	if (!empty($modSettings['mail_type']) && $modSettings['smtp_host'] != '')
932
		require_once($sourcedir . '/Subs-Post.php');
933
934
	// Send each email, yea!
935
	$failed_emails = array();
936
	foreach ($emails as $email)
937
	{
938
		if (empty($modSettings['mail_type']) || $modSettings['smtp_host'] == '')
939
		{
940
			$email['subject'] = strtr($email['subject'], array("\r" => '', "\n" => ''));
941
			if (!empty($modSettings['mail_strip_carriage']))
942
			{
943
				$email['body'] = strtr($email['body'], array("\r" => ''));
944
				$email['headers'] = strtr($email['headers'], array("\r" => ''));
945
			}
946
947
			// No point logging a specific error here, as we have no language. PHP error is helpful anyway...
948
			$result = mail(strtr($email['to'], array("\r" => '', "\n" => '')), $email['subject'], $email['body'], $email['headers']);
949
950
			// Try to stop a timeout, this would be bad...
951
			@set_time_limit(300);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
952
			if (function_exists('apache_reset_timeout'))
953
				@apache_reset_timeout();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
954
		}
955
		else
956
			$result = smtp_mail(array($email['to']), $email['subject'], $email['body'], $email['headers']);
957
958
		// Hopefully it sent?
959
		if (!$result)
960
			$failed_emails[] = array($email['to'], $email['body'], $email['subject'], $email['headers'], $email['send_html'], $email['time_sent'], $email['private']);
961
	}
962
963
	// Any emails that didn't send?
964
	if (!empty($failed_emails))
965
	{
966
		// Update the failed attempts check.
967
		$smcFunc['db_insert']('replace',
968
			'{db_prefix}settings',
969
			array('variable' => 'string', 'value' => 'string'),
970
			array('mail_failed_attempts', empty($modSettings['mail_failed_attempts']) ? 1 : ++$modSettings['mail_failed_attempts']),
971
			array('variable')
972
		);
973
974
		// If we have failed to many times, tell mail to wait a bit and try again.
975
		if ($modSettings['mail_failed_attempts'] > 5)
976
			$smcFunc['db_query']('', '
977
				UPDATE {db_prefix}settings
978
				SET value = {string:next_mail_send}
979
				WHERE variable = {literal:mail_next_send}
980
					AND value = {string:last_send}',
981
				array(
982
					'next_mail_send' => time() + 60,
983
					'last_send' => $modSettings['mail_next_send'],
984
			));
985
986
		// Add our email back to the queue, manually.
987
		$smcFunc['db_insert']('insert',
988
			'{db_prefix}mail_queue',
989
			array('recipient' => 'string', 'body' => 'string', 'subject' => 'string', 'headers' => 'string', 'send_html' => 'string', 'time_sent' => 'string', 'private' => 'int'),
990
			$failed_emails,
991
			array('id_mail')
992
		);
993
994
		return false;
995
	}
996
	// We where unable to send the email, clear our failed attempts.
997
	elseif (!empty($modSettings['mail_failed_attempts']))
998
		$smcFunc['db_query']('', '
999
			UPDATE {db_prefix}settings
1000
			SET value = {string:zero}
1001
			WHERE variable = {string:mail_failed_attempts}',
1002
			array(
1003
				'zero' => '0',
1004
				'mail_failed_attempts' => 'mail_failed_attempts',
1005
		));
1006
1007
	// Had something to send...
1008
	return true;
1009
}
1010
1011
/**
1012
 * Calculate the next time the passed tasks should be triggered.
1013
 *
1014
 * @param string|array $tasks The ID of a single task or an array of tasks
1015
 * @param bool $forceUpdate Whether to force the tasks to run now
1016
 */
1017
function CalculateNextTrigger($tasks = array(), $forceUpdate = false)
1018
{
1019
	global $modSettings, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1020
1021
	$task_query = '';
1022
	if (!is_array($tasks))
1023
		$tasks = array($tasks);
1024
1025
	// Actually have something passed?
1026
	if (!empty($tasks))
1027
	{
1028
		if (!isset($tasks[0]) || is_numeric($tasks[0]))
1029
			$task_query = ' AND id_task IN ({array_int:tasks})';
1030
		else
1031
			$task_query = ' AND task IN ({array_string:tasks})';
1032
	}
1033
	$nextTaskTime = empty($tasks) ? time() + 86400 : $modSettings['next_task_time'];
1034
1035
	// Get the critical info for the tasks.
1036
	$request = $smcFunc['db_query']('', '
1037
		SELECT id_task, next_time, time_offset, time_regularity, time_unit
1038
		FROM {db_prefix}scheduled_tasks
1039
		WHERE disabled = {int:no_disabled}
1040
			' . $task_query,
1041
		array(
1042
			'no_disabled' => 0,
1043
			'tasks' => $tasks,
1044
		)
1045
	);
1046
	$tasks = array();
1047
	while ($row = $smcFunc['db_fetch_assoc']($request))
1048
	{
1049
		$next_time = next_time($row['time_regularity'], $row['time_unit'], $row['time_offset']);
1050
1051
		// Only bother moving the task if it's out of place or we're forcing it!
1052
		if ($forceUpdate || $next_time < $row['next_time'] || $row['next_time'] < time())
1053
			$tasks[$row['id_task']] = $next_time;
1054
		else
1055
			$next_time = $row['next_time'];
1056
1057
		// If this is sooner than the current next task, make this the next task.
1058
		if ($next_time < $nextTaskTime)
1059
			$nextTaskTime = $next_time;
1060
	}
1061
	$smcFunc['db_free_result']($request);
1062
1063
	// Now make the changes!
1064
	foreach ($tasks as $id => $time)
1065
		$smcFunc['db_query']('', '
1066
			UPDATE {db_prefix}scheduled_tasks
1067
			SET next_time = {int:next_time}
1068
			WHERE id_task = {int:id_task}',
1069
			array(
1070
				'next_time' => $time,
1071
				'id_task' => $id,
1072
			)
1073
		);
1074
1075
	// If the next task is now different update.
1076
	if ($modSettings['next_task_time'] != $nextTaskTime)
1077
		updateSettings(array('next_task_time' => $nextTaskTime));
1078
}
1079
1080
/**
1081
 * Simply returns a time stamp of the next instance of these time parameters.
1082
 *
1083
 * @param int $regularity The regularity
1084
 * @param string $unit What unit are we using - 'm' for minutes, 'd' for days, 'w' for weeks or anything else for seconds
1085
 * @param int $offset The offset
1086
 * @return int The timestamp for the specified time
0 ignored issues
show
Documentation introduced by
Should the return type not be double|integer?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1087
 */
1088
function next_time($regularity, $unit, $offset)
1089
{
1090
	// Just in case!
1091
	if ($regularity == 0)
1092
		$regularity = 2;
1093
1094
	$curMin = date('i', time());
1095
1096
	// If the unit is minutes only check regularity in minutes.
1097
	if ($unit == 'm')
1098
	{
1099
		$off = date('i', $offset);
1100
1101
		// If it's now just pretend it ain't,
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1102
		if ($off == $curMin)
1103
			$next_time = time() + $regularity;
1104
		else
1105
		{
1106
			// Make sure that the offset is always in the past.
1107
			$off = $off > $curMin ? $off - 60 : $off;
1108
1109
			while ($off <= $curMin)
1110
				$off += $regularity;
1111
1112
			// Now we know when the time should be!
1113
			$next_time = time() + 60 * ($off - $curMin);
1114
		}
1115
	}
1116
	// Otherwise, work out what the offset would be with today's date.
1117
	else
1118
	{
1119
		$next_time = mktime(date('H', $offset), date('i', $offset), 0, date('m'), date('d'), date('Y'));
1120
1121
		// Make the time offset in the past!
1122
		if ($next_time > time())
1123
		{
1124
			$next_time -= 86400;
1125
		}
1126
1127
		// Default we'll jump in hours.
1128
		$applyOffset = 3600;
1129
		// 24 hours = 1 day.
1130
		if ($unit == 'd')
1131
			$applyOffset = 86400;
1132
		// Otherwise a week.
1133
		if ($unit == 'w')
1134
			$applyOffset = 604800;
1135
1136
		$applyOffset *= $regularity;
1137
1138
		// Just add on the offset.
1139
		while ($next_time <= time())
1140
		{
1141
			$next_time += $applyOffset;
1142
		}
1143
	}
1144
1145
	return $next_time;
1146
}
1147
1148
/**
1149
 * This loads the bare minimum data to allow us to load language files!
1150
 */
1151
function loadEssentialThemeData()
1152
{
1153
	global $settings, $modSettings, $smcFunc, $mbname, $context, $sourcedir, $txt;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1154
1155
	// Get all the default theme variables.
1156
	$result = $smcFunc['db_query']('', '
1157
		SELECT id_theme, variable, value
1158
		FROM {db_prefix}themes
1159
		WHERE id_member = {int:no_member}
1160
			AND id_theme IN (1, {int:theme_guests})',
1161
		array(
1162
			'no_member' => 0,
1163
			'theme_guests' => !empty($modSettings['theme_guests']) ? $modSettings['theme_guests'] : 1,
1164
		)
1165
	);
1166
	while ($row = $smcFunc['db_fetch_assoc']($result))
1167
	{
1168
		$settings[$row['variable']] = $row['value'];
1169
1170
		// Is this the default theme?
1171
		if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1')
1172
			$settings['default_' . $row['variable']] = $row['value'];
1173
	}
1174
	$smcFunc['db_free_result']($result);
1175
1176
	// Check we have some directories setup.
1177
	if (empty($settings['template_dirs']))
1178
	{
1179
		$settings['template_dirs'] = array($settings['theme_dir']);
1180
1181
		// Based on theme (if there is one).
1182
		if (!empty($settings['base_theme_dir']))
1183
			$settings['template_dirs'][] = $settings['base_theme_dir'];
1184
1185
		// Lastly the default theme.
1186 View Code Duplication
		if ($settings['theme_dir'] != $settings['default_theme_dir'])
1187
			$settings['template_dirs'][] = $settings['default_theme_dir'];
1188
	}
1189
1190
	// Assume we want this.
1191
	$context['forum_name'] = $mbname;
1192
1193
	// Check loadLanguage actually exists!
1194
	if (!function_exists('loadLanguage'))
1195
	{
1196
		require_once($sourcedir . '/Load.php');
1197
		require_once($sourcedir . '/Subs.php');
1198
	}
1199
1200
	loadLanguage('index+Modifications');
1201
1202
	// Just in case it wasn't already set elsewhere.
1203
	$context['character_set'] = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
1204
	$context['utf8'] = $context['character_set'] === 'UTF-8';
1205
	$context['right_to_left'] = !empty($txt['lang_rtl']);
1206
1207
	// Tell fatal_lang_error() to not reload the theme.
1208
	$context['theme_loaded'] = true;
1209
}
1210
1211
/**
1212
 * This retieves data (e.g. last version of SMF) from sm.org
1213
 */
1214
function scheduled_fetchSMfiles()
1215
{
1216
	global $sourcedir, $txt, $language, $forum_version, $modSettings, $smcFunc, $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1217
1218
	// What files do we want to get
1219
	$request = $smcFunc['db_query']('', '
1220
		SELECT id_file, filename, path, parameters
1221
		FROM {db_prefix}admin_info_files',
1222
		array(
1223
		)
1224
	);
1225
1226
	$js_files = array();
1227
1228 View Code Duplication
	while ($row = $smcFunc['db_fetch_assoc']($request))
1229
	{
1230
		$js_files[$row['id_file']] = array(
1231
			'filename' => $row['filename'],
1232
			'path' => $row['path'],
1233
			'parameters' => sprintf($row['parameters'], $language, urlencode($modSettings['time_format']), urlencode($forum_version)),
1234
		);
1235
	}
1236
1237
	$smcFunc['db_free_result']($request);
1238
1239
	// We're gonna need fetch_web_data() to pull this off.
1240
	require_once($sourcedir . '/Subs-Package.php');
1241
1242
	// Just in case we run into a problem.
1243
	loadEssentialThemeData();
1244
	loadLanguage('Errors', $language, false);
1245
1246
	foreach ($js_files as $ID_FILE => $file)
1247
	{
1248
		// Create the url
1249
		$server = empty($file['path']) || (substr($file['path'], 0, 7) != 'http://' && substr($file['path'], 0, 8) != 'https://') ? 'https://www.simplemachines.org' : '';
1250
		$url = $server . (!empty($file['path']) ? $file['path'] : $file['path']) . $file['filename'] . (!empty($file['parameters']) ? '?' . $file['parameters'] : '');
1251
1252
		// Get the file
1253
		$file_data = fetch_web_data($url);
1254
1255
		// 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.
1256
		if ($file_data === false)
1257
		{
1258
			$context['scheduled_errors']['fetchSMfiles'][] = sprintf($txt['st_cannot_retrieve_file'], $url);
1259
			log_error(sprintf($txt['st_cannot_retrieve_file'], $url));
1260
			return false;
1261
		}
1262
1263
		// Save the file to the database.
1264
		$smcFunc['db_query']('substring', '
1265
			UPDATE {db_prefix}admin_info_files
1266
			SET data = SUBSTRING({string:file_data}, 1, 65534)
1267
			WHERE id_file = {int:id_file}',
1268
			array(
1269
				'id_file' => $ID_FILE,
1270
				'file_data' => $file_data,
1271
			)
1272
		);
1273
	}
1274
	return true;
1275
}
1276
1277
/**
1278
 * Happy birthday!!
1279
 */
1280
function scheduled_birthdayemails()
1281
{
1282
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1283
1284
	$smcFunc['db_insert']('insert', '{db_prefix}background_tasks',
1285
		array('task_file' => 'string-255', 'task_class' => 'string-255', 'task_data' => 'string', 'claimed_time' => 'int'),
1286
		array('$sourcedir/tasks/Birthday-Notify.php', 'Birthday_Notify_Background', '', 0),
1287
		array()
1288
	);
1289
1290
	return true;
1291
}
1292
1293
/**
1294
 * Weekly maintenance
1295
 */
1296
function scheduled_weekly_maintenance()
1297
{
1298
	global $modSettings, $smcFunc, $cache_enable, $cacheAPI;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1299
1300
	// Delete some settings that needn't be set if they are otherwise empty.
1301
	$emptySettings = array(
1302
		'warning_mute', 'warning_moderate', 'warning_watch', 'warning_show', 'disableCustomPerPage', 'spider_mode', 'spider_group',
1303
		'paid_currency_code', 'paid_currency_symbol', 'paid_email_to', 'paid_email', 'paid_enabled', 'paypal_email',
1304
		'search_enable_captcha', 'search_floodcontrol_time', 'show_spider_online',
1305
	);
1306
1307
	$smcFunc['db_query']('', '
1308
		DELETE FROM {db_prefix}settings
1309
		WHERE variable IN ({array_string:setting_list})
1310
			AND (value = {string:zero_value} OR value = {string:blank_value})',
1311
		array(
1312
			'zero_value' => '0',
1313
			'blank_value' => '',
1314
			'setting_list' => $emptySettings,
1315
		)
1316
	);
1317
1318
	// Some settings we never want to keep - they are just there for temporary purposes.
1319
	$deleteAnywaySettings = array(
1320
		'attachment_full_notified',
1321
	);
1322
1323
	$smcFunc['db_query']('', '
1324
		DELETE FROM {db_prefix}settings
1325
		WHERE variable IN ({array_string:setting_list})',
1326
		array(
1327
			'setting_list' => $deleteAnywaySettings,
1328
		)
1329
	);
1330
1331
	// Ok should we prune the logs?
1332
	if (!empty($modSettings['pruningOptions']))
1333
	{
1334
		if (!empty($modSettings['pruningOptions']) && strpos($modSettings['pruningOptions'], ',') !== false)
1335
			list ($modSettings['pruneErrorLog'], $modSettings['pruneModLog'], $modSettings['pruneBanLog'], $modSettings['pruneReportLog'], $modSettings['pruneScheduledTaskLog'], $modSettings['pruneSpiderHitLog']) = explode(',', $modSettings['pruningOptions']);
1336
1337 View Code Duplication
		if (!empty($modSettings['pruneErrorLog']))
1338
		{
1339
			// Figure out when our cutoff time is.  1 day = 86400 seconds.
1340
			$t = time() - $modSettings['pruneErrorLog'] * 86400;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $t. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1341
1342
			$smcFunc['db_query']('', '
1343
				DELETE FROM {db_prefix}log_errors
1344
				WHERE log_time < {int:log_time}',
1345
				array(
1346
					'log_time' => $t,
1347
				)
1348
			);
1349
		}
1350
1351
		if (!empty($modSettings['pruneModLog']))
1352
		{
1353
			// Figure out when our cutoff time is.  1 day = 86400 seconds.
1354
			$t = time() - $modSettings['pruneModLog'] * 86400;
1355
1356
			$smcFunc['db_query']('', '
1357
				DELETE FROM {db_prefix}log_actions
1358
				WHERE log_time < {int:log_time}
1359
					AND id_log = {int:moderation_log}',
1360
				array(
1361
					'log_time' => $t,
1362
					'moderation_log' => 1,
1363
				)
1364
			);
1365
		}
1366
1367 View Code Duplication
		if (!empty($modSettings['pruneBanLog']))
1368
		{
1369
			// Figure out when our cutoff time is.  1 day = 86400 seconds.
1370
			$t = time() - $modSettings['pruneBanLog'] * 86400;
1371
1372
			$smcFunc['db_query']('', '
1373
				DELETE FROM {db_prefix}log_banned
1374
				WHERE log_time < {int:log_time}',
1375
				array(
1376
					'log_time' => $t,
1377
				)
1378
			);
1379
		}
1380
1381
		if (!empty($modSettings['pruneReportLog']))
1382
		{
1383
			// Figure out when our cutoff time is.  1 day = 86400 seconds.
1384
			$t = time() - $modSettings['pruneReportLog'] * 86400;
1385
1386
			// This one is more complex then the other logs.  First we need to figure out which reports are too old.
1387
			$reports = array();
1388
			$result = $smcFunc['db_query']('', '
1389
				SELECT id_report
1390
				FROM {db_prefix}log_reported
1391
				WHERE time_started < {int:time_started}
1392
					AND closed = {int:closed}
1393
					AND ignore_all = {int:not_ignored}',
1394
				array(
1395
					'time_started' => $t,
1396
					'closed' => 1,
1397
					'not_ignored' => 0,
1398
				)
1399
			);
1400
1401
			while ($row = $smcFunc['db_fetch_row']($result))
1402
				$reports[] = $row[0];
1403
1404
			$smcFunc['db_free_result']($result);
1405
1406
			if (!empty($reports))
1407
			{
1408
				// Now delete the reports...
1409
				$smcFunc['db_query']('', '
1410
					DELETE FROM {db_prefix}log_reported
1411
					WHERE id_report IN ({array_int:report_list})',
1412
					array(
1413
						'report_list' => $reports,
1414
					)
1415
				);
1416
				// And delete the comments for those reports...
1417
				$smcFunc['db_query']('', '
1418
					DELETE FROM {db_prefix}log_reported_comments
1419
					WHERE id_report IN ({array_int:report_list})',
1420
					array(
1421
						'report_list' => $reports,
1422
					)
1423
				);
1424
			}
1425
		}
1426
1427 View Code Duplication
		if (!empty($modSettings['pruneScheduledTaskLog']))
1428
		{
1429
			// Figure out when our cutoff time is.  1 day = 86400 seconds.
1430
			$t = time() - $modSettings['pruneScheduledTaskLog'] * 86400;
1431
1432
			$smcFunc['db_query']('', '
1433
				DELETE FROM {db_prefix}log_scheduled_tasks
1434
				WHERE time_run < {int:time_run}',
1435
				array(
1436
					'time_run' => $t,
1437
				)
1438
			);
1439
		}
1440
1441 View Code Duplication
		if (!empty($modSettings['pruneSpiderHitLog']))
1442
		{
1443
			// Figure out when our cutoff time is.  1 day = 86400 seconds.
1444
			$t = time() - $modSettings['pruneSpiderHitLog'] * 86400;
1445
1446
			$smcFunc['db_query']('', '
1447
				DELETE FROM {db_prefix}log_spider_hits
1448
				WHERE log_time < {int:log_time}',
1449
				array(
1450
					'log_time' => $t,
1451
				)
1452
			);
1453
		}
1454
	}
1455
1456
	// Get rid of any paid subscriptions that were never actioned.
1457
	$smcFunc['db_query']('', '
1458
		DELETE FROM {db_prefix}log_subscribed
1459
		WHERE end_time = {int:no_end_time}
1460
			AND status = {int:not_active}
1461
			AND start_time < {int:start_time}
1462
			AND payments_pending < {int:payments_pending}',
1463
		array(
1464
			'no_end_time' => 0,
1465
			'not_active' => 0,
1466
			'start_time' => time() - 60,
1467
			'payments_pending' => 1,
1468
		)
1469
	);
1470
1471
	// Some OS's don't seem to clean out their sessions.
1472
	$smcFunc['db_query']('', '
1473
		DELETE FROM {db_prefix}sessions
1474
		WHERE last_update < {int:last_update}',
1475
		array(
1476
			'last_update' => time() - 86400,
1477
		)
1478
	);
1479
1480
	// Update the regex of top level domains with the IANA's latest official list
1481
	$smcFunc['db_insert']('insert', '{db_prefix}background_tasks',
1482
		array('task_file' => 'string-255', 'task_class' => 'string-255', 'task_data' => 'string', 'claimed_time' => 'int'),
1483
		array('$sourcedir/tasks/UpdateTldRegex.php', 'Update_TLD_Regex', '', 0), array()
1484
	);
1485
	
1486
	// Run Cache housekeeping
1487
	if (!empty($cache_enable) && !empty($cacheAPI))
1488
	{
1489
		$cacheAPI->housekeeping();
1490
	}
1491
	
1492
	return true;
1493
}
1494
1495
/**
1496
 * Perform the standard checks on expiring/near expiring subscriptions.
1497
 */
1498
function scheduled_paid_subscriptions()
1499
{
1500
	global $sourcedir, $scripturl, $smcFunc, $modSettings, $language;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1501
1502
	// Start off by checking for removed subscriptions.
1503
	$request = $smcFunc['db_query']('', '
1504
		SELECT id_subscribe, id_member
1505
		FROM {db_prefix}log_subscribed
1506
		WHERE status = {int:is_active}
1507
			AND end_time < {int:time_now}',
1508
		array(
1509
			'is_active' => 1,
1510
			'time_now' => time(),
1511
		)
1512
	);
1513
	while ($row = $smcFunc['db_fetch_assoc']($request))
1514
	{
1515
		require_once($sourcedir . '/ManagePaid.php');
1516
		removeSubscription($row['id_subscribe'], $row['id_member']);
1517
	}
1518
	$smcFunc['db_free_result']($request);
1519
1520
	// Get all those about to expire that have not had a reminder sent.
1521
	$request = $smcFunc['db_query']('', '
1522
		SELECT ls.id_sublog, m.id_member, m.member_name, m.email_address, m.lngfile, s.name, ls.end_time
1523
		FROM {db_prefix}log_subscribed AS ls
1524
			JOIN {db_prefix}subscriptions AS s ON (s.id_subscribe = ls.id_subscribe)
1525
			JOIN {db_prefix}members AS m ON (m.id_member = ls.id_member)
1526
		WHERE ls.status = {int:is_active}
1527
			AND ls.reminder_sent = {int:reminder_sent}
1528
			AND s.reminder > {int:reminder_wanted}
1529
			AND ls.end_time < ({int:time_now} + s.reminder * 86400)',
1530
		array(
1531
			'is_active' => 1,
1532
			'reminder_sent' => 0,
1533
			'reminder_wanted' => 0,
1534
			'time_now' => time(),
1535
		)
1536
	);
1537
	$subs_reminded = array();
1538
	$members = array();
1539
	while ($row = $smcFunc['db_fetch_assoc']($request))
1540
	{
1541
		// If this is the first one load the important bits.
1542
		if (empty($subs_reminded))
1543
		{
1544
			require_once($sourcedir . '/Subs-Post.php');
1545
			// Need the below for loadLanguage to work!
1546
			loadEssentialThemeData();
1547
		}
1548
1549
		$subs_reminded[] = $row['id_sublog'];
1550
		$members[$row['id_member']] = $row;
1551
	}
1552
	$smcFunc['db_free_result']($request);
1553
1554
	// Load alert preferences
1555
	require_once($sourcedir . '/Subs-Notify.php');
1556
	$notifyPrefs = getNotifyPrefs(array_keys($members), 'paidsubs_expiring', true);
1557
	$alert_rows = array();
1558
	foreach ($members as $row)
1559
	{
1560
		$replacements = array(
1561
			'PROFILE_LINK' => $scripturl . '?action=profile;area=subscriptions;u=' . $row['id_member'],
1562
			'REALNAME' => $row['member_name'],
1563
			'SUBSCRIPTION' => $row['name'],
1564
			'END_DATE' => strip_tags(timeformat($row['end_time'])),
1565
		);
1566
1567
		$emaildata = loadEmailTemplate('paid_subscription_reminder', $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']);
1568
1569
		// Send the actual email.
1570
		if ($notifyPrefs[$row['id_member']] & 0x02)
1571
			sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, 'paid_sub_remind', $emaildata['is_html'], 2);
1572
1573
		if ($notifyPrefs[$row['id_member']] & 0x01)
1574
		{
1575
			$alert_rows[] = array(
1576
				'alert_time' => time(),
1577
				'id_member' => $row['id_member'],
1578
				'id_member_started' => $row['id_member'],
1579
				'member_name' => $row['member_name'],
1580
				'content_type' => 'paidsubs',
1581
				'content_id' => $row['id_sublog'],
1582
				'content_action' => 'expiring',
1583
				'is_read' => 0,
1584
				'extra' => $smcFunc['json_encode'](array(
1585
					'subscription_name' => $row['name'],
1586
					'end_time' => strip_tags(timeformat($row['end_time'])),
1587
				)),
1588
			);
1589
			updateMemberData($row['id_member'], array('alerts' => '+'));
1590
		}
1591
	}
1592
1593
	// Insert the alerts if any
1594 View Code Duplication
	if (!empty($alert_rows))
1595
		$smcFunc['db_insert']('',
1596
			'{db_prefix}user_alerts',
1597
			array('alert_time' => 'int', 'id_member' => 'int', 'id_member_started' => 'int', 'member_name' => 'string',
1598
				'content_type' => 'string', 'content_id' => 'int', 'content_action' => 'string', 'is_read' => 'int', 'extra' => 'string'),
1599
			$alert_rows,
1600
			array()
1601
		);
1602
1603
	// Mark the reminder as sent.
1604
	if (!empty($subs_reminded))
1605
		$smcFunc['db_query']('', '
1606
			UPDATE {db_prefix}log_subscribed
1607
			SET reminder_sent = {int:reminder_sent}
1608
			WHERE id_sublog IN ({array_int:subscription_list})',
1609
			array(
1610
				'subscription_list' => $subs_reminded,
1611
				'reminder_sent' => 1,
1612
			)
1613
		);
1614
1615
	return true;
1616
}
1617
1618
/**
1619
 * Check for un-posted attachments is something we can do once in a while :P
1620
 * This function uses opendir cycling through all the attachments
1621
 */
1622
function scheduled_remove_temp_attachments()
1623
{
1624
	global $smcFunc, $modSettings, $context, $txt;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1625
1626
	// We need to know where this thing is going.
1627 View Code Duplication
	if (!empty($modSettings['currentAttachmentUploadDir']))
1628
	{
1629
		if (!is_array($modSettings['attachmentUploadDir']))
1630
			$modSettings['attachmentUploadDir'] = $smcFunc['json_decode']($modSettings['attachmentUploadDir'], true);
1631
1632
		// Just use the current path for temp files.
1633
		$attach_dirs = $modSettings['attachmentUploadDir'];
1634
	}
1635
	else
1636
	{
1637
		$attach_dirs = array($modSettings['attachmentUploadDir']);
1638
	}
1639
1640
	foreach ($attach_dirs as $attach_dir)
1641
	{
1642
		$dir = @opendir($attach_dir);
1643
		if (!$dir)
1644
		{
1645
			loadEssentialThemeData();
1646
			loadLanguage('Post');
1647
			$context['scheduled_errors']['remove_temp_attachments'][] = $txt['cant_access_upload_path'] . ' (' . $attach_dir . ')';
1648
			log_error($txt['cant_access_upload_path'] . ' (' . $attach_dir . ')', 'critical');
1649
			return false;
1650
		}
1651
1652
		while ($file = readdir($dir))
1653
		{
1654
			if ($file == '.' || $file == '..')
1655
				continue;
1656
1657
			if (strpos($file, 'post_tmp_') !== false)
1658
			{
1659
				// Temp file is more than 5 hours old!
1660 View Code Duplication
				if (filemtime($attach_dir . '/' . $file) < time() - 18000)
1661
					@unlink($attach_dir . '/' . $file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1662
			}
1663
		}
1664
		closedir($dir);
1665
	}
1666
1667
	return true;
1668
}
1669
1670
/**
1671
 * Check for move topic notices that have past their best by date
1672
 */
1673
function scheduled_remove_topic_redirect()
1674
{
1675
	global $smcFunc, $sourcedir;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1676
1677
	// init
1678
	$topics = array();
1679
1680
	// We will need this for language files
1681
	loadEssentialThemeData();
1682
1683
	// Find all of the old MOVE topic notices that were set to expire
1684
	$request = $smcFunc['db_query']('', '
1685
		SELECT id_topic
1686
		FROM {db_prefix}topics
1687
		WHERE redirect_expires <= {int:redirect_expires}
1688
			AND redirect_expires <> 0',
1689
		array(
1690
			'redirect_expires' => time(),
1691
		)
1692
	);
1693
1694
	while ($row = $smcFunc['db_fetch_row']($request))
1695
		$topics[] = $row[0];
1696
	$smcFunc['db_free_result']($request);
1697
1698
	// Zap, your gone
1699
	if (count($topics) > 0)
1700
	{
1701
		require_once($sourcedir . '/RemoveTopic.php');
1702
		removeTopics($topics, false, true);
1703
	}
1704
1705
	return true;
1706
}
1707
1708
/**
1709
 * Check for old drafts and remove them
1710
 */
1711
function scheduled_remove_old_drafts()
1712
{
1713
	global $smcFunc, $sourcedir, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1714
1715
	if (empty($modSettings['drafts_keep_days']))
1716
		return true;
1717
1718
	// init
1719
	$drafts = array();
1720
1721
	// We need this for language items
1722
	loadEssentialThemeData();
1723
1724
	// Find all of the old drafts
1725
	$request = $smcFunc['db_query']('', '
1726
		SELECT id_draft
1727
		FROM {db_prefix}user_drafts
1728
		WHERE poster_time <= {int:poster_time_old}',
1729
		array(
1730
			'poster_time_old' => time() - (86400 * $modSettings['drafts_keep_days']),
1731
		)
1732
	);
1733
1734
	while ($row = $smcFunc['db_fetch_row']($request))
1735
		$drafts[] = (int) $row[0];
1736
	$smcFunc['db_free_result']($request);
1737
1738
	// If we have old one, remove them
1739
	if (count($drafts) > 0)
1740
	{
1741
		require_once($sourcedir . '/Drafts.php');
1742
		DeleteDraft($drafts, false);
0 ignored issues
show
Documentation introduced by
$drafts is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1743
	}
1744
1745
	return true;
1746
}
1747
1748
?>