Completed
Push — release-2.1 ( af8dd6...5b3c87 )
by Michael
20:47 queued 10:04
created

ScheduledTasks.php ➔ scheduled_daily_maintenance()   C

Complexity

Conditions 12
Paths 8

Size

Total Lines 85
Code Lines 37

Duplication

Lines 11
Ratio 12.94 %

Importance

Changes 0
Metric Value
cc 12
eloc 37
nc 8
nop 0
dl 11
loc 85
rs 5.034
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 2017 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;
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 View Code Duplication
				if ($completed)
112
				{
113
					$total_time = round(array_sum(explode(' ', microtime())) - array_sum(explode(' ', $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;
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;
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
	// Log we've done it...
462
	return true;
463
}
464
465
/**
466
 * Send out a daily email of all subscribed topics.
467
 */
468
function scheduled_daily_digest()
469
{
470
	global $is_weekly, $txt, $mbname, $scripturl, $sourcedir, $smcFunc, $context, $modSettings;
471
472
	// We'll want this...
473
	require_once($sourcedir . '/Subs-Post.php');
474
	loadEssentialThemeData();
475
476
	$is_weekly = !empty($is_weekly) ? 1 : 0;
477
478
	// Right - get all the notification data FIRST.
479
	$request = $smcFunc['db_query']('', '
480
		SELECT ln.id_topic, COALESCE(t.id_board, ln.id_board) AS id_board, mem.email_address, mem.member_name,
481
			mem.lngfile, mem.id_member
482
		FROM {db_prefix}log_notify AS ln
483
			JOIN {db_prefix}members AS mem ON (mem.id_member = ln.id_member)
484
			LEFT JOIN {db_prefix}topics AS t ON (ln.id_topic != {int:empty_topic} AND t.id_topic = ln.id_topic)
485
		WHERE mem.is_activated = {int:is_activated}',
486
		array(
487
			'empty_topic' => 0,
488
			'is_activated' => 1,
489
		)
490
	);
491
	$members = array();
492
	$langs = array();
493
	$notify = array();
494
	while ($row = $smcFunc['db_fetch_assoc']($request))
495
	{
496
		if (!isset($members[$row['id_member']]))
497
		{
498
			$members[$row['id_member']] = array(
499
				'email' => $row['email_address'],
500
				'name' => $row['member_name'],
501
				'id' => $row['id_member'],
502
				'lang' => $row['lngfile'],
503
			);
504
			$langs[$row['lngfile']] = $row['lngfile'];
505
		}
506
507
		// Store this useful data!
508
		$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...
509
		if ($row['id_topic'])
510
			$notify['topics'][$row['id_topic']][] = $row['id_member'];
511
		else
512
			$notify['boards'][$row['id_board']][] = $row['id_member'];
513
	}
514
	$smcFunc['db_free_result']($request);
515
516
	if (empty($boards))
517
		return true;
518
519
	// Just get the board names.
520
	$request = $smcFunc['db_query']('', '
521
		SELECT id_board, name
522
		FROM {db_prefix}boards
523
		WHERE id_board IN ({array_int:board_list})',
524
		array(
525
			'board_list' => $boards,
526
		)
527
	);
528
	$boards = array();
529 View Code Duplication
	while ($row = $smcFunc['db_fetch_assoc']($request))
530
		$boards[$row['id_board']] = $row['name'];
531
	$smcFunc['db_free_result']($request);
532
533
	if (empty($boards))
534
		return true;
535
536
	// Get the actual topics...
537
	$request = $smcFunc['db_query']('', '
538
		SELECT ld.note_type, t.id_topic, t.id_board, t.id_member_started, m.id_msg, m.subject,
539
			b.name AS board_name
540
		FROM {db_prefix}log_digest AS ld
541
			JOIN {db_prefix}topics AS t ON (t.id_topic = ld.id_topic
542
				AND t.id_board IN ({array_int:board_list}))
543
			JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
544
			JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
545
		WHERE ' . ($is_weekly ? 'ld.daily != {int:daily_value}' : 'ld.daily IN (0, 2)'),
546
		array(
547
			'board_list' => array_keys($boards),
548
			'daily_value' => 2,
549
		)
550
	);
551
	$types = array();
552
	while ($row = $smcFunc['db_fetch_assoc']($request))
553
	{
554
		if (!isset($types[$row['note_type']][$row['id_board']]))
555
			$types[$row['note_type']][$row['id_board']] = array(
556
				'lines' => array(),
557
				'name' => $row['board_name'],
558
				'id' => $row['id_board'],
559
			);
560
561
		if ($row['note_type'] == 'reply')
562
		{
563
			if (isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]))
564
				$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['count']++;
565
			else
566
				$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = array(
567
					'id' => $row['id_topic'],
568
					'subject' => un_htmlspecialchars($row['subject']),
569
					'count' => 1,
570
				);
571
		}
572 View Code Duplication
		elseif ($row['note_type'] == 'topic')
573
		{
574
			if (!isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]))
575
				$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = array(
576
					'id' => $row['id_topic'],
577
					'subject' => un_htmlspecialchars($row['subject']),
578
				);
579
		}
580 View Code Duplication
		else
581
		{
582
			if (!isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]))
583
				$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = array(
584
					'id' => $row['id_topic'],
585
					'subject' => un_htmlspecialchars($row['subject']),
586
					'starter' => $row['id_member_started'],
587
				);
588
		}
589
590
		$types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['members'] = array();
591 View Code Duplication
		if (!empty($notify['topics'][$row['id_topic']]))
592
			$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']]);
593 View Code Duplication
		if (!empty($notify['boards'][$row['id_board']]))
594
			$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']]);
595
	}
596
	$smcFunc['db_free_result']($request);
597
598
	if (empty($types))
599
		return true;
600
601
	// Let's load all the languages into a cache thingy.
602
	$langtxt = array();
603
	foreach ($langs as $lang)
604
	{
605
		loadLanguage('Post', $lang);
606
		loadLanguage('index', $lang);
607
		loadLanguage('EmailTemplates', $lang);
608
		$langtxt[$lang] = array(
609
			'subject' => $txt['digest_subject_' . ($is_weekly ? 'weekly' : 'daily')],
610
			'char_set' => $txt['lang_character_set'],
611
			'intro' => sprintf($txt['digest_intro_' . ($is_weekly ? 'weekly' : 'daily')], $mbname),
612
			'new_topics' => $txt['digest_new_topics'],
613
			'topic_lines' => $txt['digest_new_topics_line'],
614
			'new_replies' => $txt['digest_new_replies'],
615
			'mod_actions' => $txt['digest_mod_actions'],
616
			'replies_one' => $txt['digest_new_replies_one'],
617
			'replies_many' => $txt['digest_new_replies_many'],
618
			'sticky' => $txt['digest_mod_act_sticky'],
619
			'lock' => $txt['digest_mod_act_lock'],
620
			'unlock' => $txt['digest_mod_act_unlock'],
621
			'remove' => $txt['digest_mod_act_remove'],
622
			'move' => $txt['digest_mod_act_move'],
623
			'merge' => $txt['digest_mod_act_merge'],
624
			'split' => $txt['digest_mod_act_split'],
625
			'bye' => $txt['regards_team'],
626
		);
627
	}
628
629
	// The preferred way...
630
	require_once($sourcedir . '/Subs-Notify.php');
631
	$prefs = getNotifyPrefs(array_keys($members), array('msg_notify_type', 'msg_notify_pref'), true);
632
633
	// Right - send out the silly things - this will take quite some space!
634
	foreach ($members as $mid => $member)
635
	{
636
		$frequency = !empty($prefs[$mid]['msg_notify_pref']) ? $prefs[$mid]['msg_notify_pref'] : 1;
637
		$notify_types = !empty($prefs[$mid]['msg_notify_type']) ? $prefs[$mid]['msg_notify_type'] : 1;
638
639
		// Did they not elect to choose this?
640
		if ($frequency == 4 && !$is_weekly || $frequency == 3 && $is_weekly || $notify_types == 4)
641
			continue;
642
643
		// Right character set!
644
		$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 603. 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...
645
646
		// Do the start stuff!
647
		$email = array(
648
			'subject' => $mbname . ' - ' . $langtxt[$lang]['subject'],
649
			'body' => $member['name'] . ',' . "\n\n" . $langtxt[$lang]['intro'] . "\n" . $scripturl . '?action=profile;area=notification;u=' . $member['id'] . "\n",
650
			'email' => $member['email'],
651
		);
652
653
		// All new topics?
654
		if (isset($types['topic']))
655
		{
656
			$titled = false;
657
			foreach ($types['topic'] as $id => $board)
658 View Code Duplication
				foreach ($board['lines'] as $topic)
659
					if (in_array($mid, $topic['members']))
660
					{
661
						if (!$titled)
662
						{
663
							$email['body'] .= "\n" . $langtxt[$lang]['new_topics'] . ':' . "\n" . '-----------------------------------------------';
664
							$titled = true;
665
						}
666
						$email['body'] .= "\n" . sprintf($langtxt[$lang]['topic_lines'], $topic['subject'], $board['name']);
667
					}
668
			if ($titled)
669
				$email['body'] .= "\n";
670
		}
671
672
		// What about replies?
673
		if (isset($types['reply']))
674
		{
675
			$titled = false;
676
			foreach ($types['reply'] as $id => $board)
677
				foreach ($board['lines'] as $topic)
678
					if (in_array($mid, $topic['members']))
679
					{
680
						if (!$titled)
681
						{
682
							$email['body'] .= "\n" . $langtxt[$lang]['new_replies'] . ':' . "\n" . '-----------------------------------------------';
683
							$titled = true;
684
						}
685
						$email['body'] .= "\n" . ($topic['count'] == 1 ? sprintf($langtxt[$lang]['replies_one'], $topic['subject']) : sprintf($langtxt[$lang]['replies_many'], $topic['count'], $topic['subject']));
686
					}
687
688
			if ($titled)
689
				$email['body'] .= "\n";
690
		}
691
692
		// 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...
693
		if ($notify_types < 3)
694
		{
695
			$titled = false;
696
			foreach ($types as $note_type => $type)
697
			{
698
				if ($note_type == 'topic' || $note_type == 'reply')
699
					continue;
700
701
				foreach ($type as $id => $board)
702 View Code Duplication
					foreach ($board['lines'] as $topic)
703
						if (in_array($mid, $topic['members']))
704
						{
705
							if (!$titled)
706
							{
707
								$email['body'] .= "\n" . $langtxt[$lang]['mod_actions'] . ':' . "\n" . '-----------------------------------------------';
708
								$titled = true;
709
							}
710
							$email['body'] .= "\n" . sprintf($langtxt[$lang][$note_type], $topic['subject']);
711
						}
712
			}
713
		}
714
		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...
715
			$email['body'] .= "\n";
716
717
		// Then just say our goodbyes!
718
		$email['body'] .= "\n\n" . $txt['regards_team'];
719
720
		// Send it - low priority!
721
		sendmail($email['email'], $email['subject'], $email['body'], null, 'digest', false, 4);
722
	}
723
724
	// Clean up...
725
	if ($is_weekly)
726
	{
727
		$smcFunc['db_query']('', '
728
			DELETE FROM {db_prefix}log_digest
729
			WHERE daily != {int:not_daily}',
730
			array(
731
				'not_daily' => 0,
732
			)
733
		);
734
		$smcFunc['db_query']('', '
735
			UPDATE {db_prefix}log_digest
736
			SET daily = {int:daily_value}
737
			WHERE daily = {int:not_daily}',
738
			array(
739
				'daily_value' => 2,
740
				'not_daily' => 0,
741
			)
742
		);
743
	}
744
	else
745
	{
746
		// Clear any only weekly ones, and stop us from sending daily again.
747
		$smcFunc['db_query']('', '
748
			DELETE FROM {db_prefix}log_digest
749
			WHERE daily = {int:daily_value}',
750
			array(
751
				'daily_value' => 2,
752
			)
753
		);
754
		$smcFunc['db_query']('', '
755
			UPDATE {db_prefix}log_digest
756
			SET daily = {int:both_value}
757
			WHERE daily = {int:no_value}',
758
			array(
759
				'both_value' => 1,
760
				'no_value' => 0,
761
			)
762
		);
763
	}
764
765
	// Just in case the member changes their settings mark this as sent.
766
	$members = array_keys($members);
767
	$smcFunc['db_query']('', '
768
		UPDATE {db_prefix}log_notify
769
		SET sent = {int:is_sent}
770
		WHERE id_member IN ({array_int:member_list})',
771
		array(
772
			'member_list' => $members,
773
			'is_sent' => 1,
774
		)
775
	);
776
777
	// Log we've done it...
778
	return true;
779
}
780
781
/**
782
 * Like the daily stuff - just seven times less regular ;)
783
 */
784
function scheduled_weekly_digest()
785
{
786
	global $is_weekly;
787
788
	// We just pass through to the daily function - avoid duplication!
789
	$is_weekly = true;
790
	return scheduled_daily_digest();
791
}
792
793
/**
794
 * Send a group of emails from the mail queue.
795
 *
796
 * @param bool|int $number The number to send each loop through or false to use the standard limits
797
 * @param bool $override_limit Whether to bypass the limit
798
 * @param bool $force_send Whether to forcibly send the messages now (useful when using cron jobs)
799
 * @return bool Whether things were sent
800
 */
801
function ReduceMailQueue($number = false, $override_limit = false, $force_send = false)
802
{
803
	global $modSettings, $smcFunc, $sourcedir;
804
805
	// Are we intending another script to be sending out the queue?
806
	if (!empty($modSettings['mail_queue_use_cron']) && empty($force_send))
807
		return false;
808
809
	// By default send 5 at once.
810
	if (!$number)
811
		$number = empty($modSettings['mail_quantity']) ? 5 : $modSettings['mail_quantity'];
812
813
	// If we came with a timestamp, and that doesn't match the next event, then someone else has beaten us.
814
	if (isset($_GET['ts']) && $_GET['ts'] != $modSettings['mail_next_send'] && empty($force_send))
815
		return false;
816
817
	// By default move the next sending on by 10 seconds, and require an affected row.
818
	if (!$override_limit)
819
	{
820
		$delay = !empty($modSettings['mail_queue_delay']) ? $modSettings['mail_queue_delay'] : (!empty($modSettings['mail_limit']) && $modSettings['mail_limit'] < 5 ? 10 : 5);
821
822
		$smcFunc['db_query']('', '
823
			UPDATE {db_prefix}settings
824
			SET value = {string:next_mail_send}
825
			WHERE variable = {literal:mail_next_send}
826
				AND value = {string:last_send}',
827
			array(
828
				'next_mail_send' => time() + $delay,
829
				'last_send' => $modSettings['mail_next_send'],
830
			)
831
		);
832
		if ($smcFunc['db_affected_rows']() == 0)
833
			return false;
834
		$modSettings['mail_next_send'] = time() + $delay;
835
	}
836
837
	// If we're not overriding how many are we allow to send?
838
	if (!$override_limit && !empty($modSettings['mail_limit']))
839
	{
840
		list ($mt, $mn) = @explode('|', $modSettings['mail_recent']);
841
842
		// Nothing worth noting...
843
		if (empty($mn) || $mt < time() - 60)
844
		{
845
			$mt = time();
846
			$mn = $number;
847
		}
848
		// Otherwise we have a few more we can spend?
849
		elseif ($mn < $modSettings['mail_limit'])
850
		{
851
			$mn += $number;
852
		}
853
		// No more I'm afraid, return!
854
		else
855
			return false;
856
857
		// Reflect that we're about to send some, do it now to be safe.
858
		updateSettings(array('mail_recent' => $mt . '|' . $mn));
859
	}
860
861
	// Now we know how many we're sending, let's send them.
862
	$request = $smcFunc['db_query']('', '
863
		SELECT /*!40001 SQL_NO_CACHE */ id_mail, recipient, body, subject, headers, send_html, time_sent, private
864
		FROM {db_prefix}mail_queue
865
		ORDER BY priority ASC, id_mail ASC
866
		LIMIT {int:limit}',
867
		array(
868
			'limit' => $number,
869
		)
870
	);
871
	$ids = array();
872
	$emails = array();
873
	while ($row = $smcFunc['db_fetch_assoc']($request))
874
	{
875
		// We want to delete these from the database ASAP, so just get the data and go.
876
		$ids[] = $row['id_mail'];
877
		$emails[] = array(
878
			'to' => $row['recipient'],
879
			'body' => $row['body'],
880
			'subject' => $row['subject'],
881
			'headers' => $row['headers'],
882
			'send_html' => $row['send_html'],
883
			'time_sent' => $row['time_sent'],
884
			'private' => $row['private'],
885
		);
886
	}
887
	$smcFunc['db_free_result']($request);
888
889
	// 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...
890
	if (!empty($ids))
891
		$smcFunc['db_query']('', '
892
			DELETE FROM {db_prefix}mail_queue
893
			WHERE id_mail IN ({array_int:mail_list})',
894
			array(
895
				'mail_list' => $ids,
896
			)
897
		);
898
899
	// Don't believe we have any left?
900
	if (count($ids) < $number)
901
	{
902
		// Only update the setting if no-one else has beaten us to it.
903
		$smcFunc['db_query']('', '
904
			UPDATE {db_prefix}settings
905
			SET value = {string:no_send}
906
			WHERE variable = {literal:mail_next_send}
907
				AND value = {string:last_mail_send}',
908
			array(
909
				'no_send' => '0',
910
				'last_mail_send' => $modSettings['mail_next_send'],
911
			)
912
		);
913
	}
914
915
	if (empty($ids))
916
		return false;
917
918
	if (!empty($modSettings['mail_type']) && $modSettings['smtp_host'] != '')
919
		require_once($sourcedir . '/Subs-Post.php');
920
921
	// Send each email, yea!
922
	$failed_emails = array();
923
	foreach ($emails as $email)
924
	{
925
		if (empty($modSettings['mail_type']) || $modSettings['smtp_host'] == '')
926
		{
927
			$email['subject'] = strtr($email['subject'], array("\r" => '', "\n" => ''));
928
			if (!empty($modSettings['mail_strip_carriage']))
929
			{
930
				$email['body'] = strtr($email['body'], array("\r" => ''));
931
				$email['headers'] = strtr($email['headers'], array("\r" => ''));
932
			}
933
934
			// No point logging a specific error here, as we have no language. PHP error is helpful anyway...
935
			$result = mail(strtr($email['to'], array("\r" => '', "\n" => '')), $email['subject'], $email['body'], $email['headers']);
936
937
			// Try to stop a timeout, this would be bad...
938
			@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...
939
			if (function_exists('apache_reset_timeout'))
940
				@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...
941
		}
942
		else
943
			$result = smtp_mail(array($email['to']), $email['subject'], $email['body'], $email['headers']);
944
945
		// Hopefully it sent?
946
		if (!$result)
947
			$failed_emails[] = array($email['to'], $email['body'], $email['subject'], $email['headers'], $email['send_html'], $email['time_sent'], $email['private']);
948
	}
949
950
	// Any emails that didn't send?
951
	if (!empty($failed_emails))
952
	{
953
		// Update the failed attempts check.
954
		$smcFunc['db_insert']('replace',
955
			'{db_prefix}settings',
956
			array('variable' => 'string', 'value' => 'string'),
957
			array('mail_failed_attempts', empty($modSettings['mail_failed_attempts']) ? 1 : ++$modSettings['mail_failed_attempts']),
958
			array('variable')
959
		);
960
961
		// If we have failed to many times, tell mail to wait a bit and try again.
962
		if ($modSettings['mail_failed_attempts'] > 5)
963
			$smcFunc['db_query']('', '
964
				UPDATE {db_prefix}settings
965
				SET value = {string:next_mail_send}
966
				WHERE variable = {literal:mail_next_send}
967
					AND value = {string:last_send}',
968
				array(
969
					'next_mail_send' => time() + 60,
970
					'last_send' => $modSettings['mail_next_send'],
971
			));
972
973
		// Add our email back to the queue, manually.
974
		$smcFunc['db_insert']('insert',
975
			'{db_prefix}mail_queue',
976
			array('recipient' => 'string', 'body' => 'string', 'subject' => 'string', 'headers' => 'string', 'send_html' => 'string', 'time_sent' => 'string', 'private' => 'int'),
977
			$failed_emails,
978
			array('id_mail')
979
		);
980
981
		return false;
982
	}
983
	// We where unable to send the email, clear our failed attempts.
984
	elseif (!empty($modSettings['mail_failed_attempts']))
985
		$smcFunc['db_query']('', '
986
			UPDATE {db_prefix}settings
987
			SET value = {string:zero}
988
			WHERE variable = {string:mail_failed_attempts}',
989
			array(
990
				'zero' => '0',
991
				'mail_failed_attempts' => 'mail_failed_attempts',
992
		));
993
994
	// Had something to send...
995
	return true;
996
}
997
998
/**
999
 * Calculate the next time the passed tasks should be triggered.
1000
 *
1001
 * @param string|array $tasks The ID of a single task or an array of tasks
1002
 * @param bool $forceUpdate Whether to force the tasks to run now
1003
 */
1004
function CalculateNextTrigger($tasks = array(), $forceUpdate = false)
1005
{
1006
	global $modSettings, $smcFunc;
1007
1008
	$task_query = '';
1009
	if (!is_array($tasks))
1010
		$tasks = array($tasks);
1011
1012
	// Actually have something passed?
1013
	if (!empty($tasks))
1014
	{
1015
		if (!isset($tasks[0]) || is_numeric($tasks[0]))
1016
			$task_query = ' AND id_task IN ({array_int:tasks})';
1017
		else
1018
			$task_query = ' AND task IN ({array_string:tasks})';
1019
	}
1020
	$nextTaskTime = empty($tasks) ? time() + 86400 : $modSettings['next_task_time'];
1021
1022
	// Get the critical info for the tasks.
1023
	$request = $smcFunc['db_query']('', '
1024
		SELECT id_task, next_time, time_offset, time_regularity, time_unit
1025
		FROM {db_prefix}scheduled_tasks
1026
		WHERE disabled = {int:no_disabled}
1027
			' . $task_query,
1028
		array(
1029
			'no_disabled' => 0,
1030
			'tasks' => $tasks,
1031
		)
1032
	);
1033
	$tasks = array();
1034
	while ($row = $smcFunc['db_fetch_assoc']($request))
1035
	{
1036
		$next_time = next_time($row['time_regularity'], $row['time_unit'], $row['time_offset']);
1037
1038
		// Only bother moving the task if it's out of place or we're forcing it!
1039
		if ($forceUpdate || $next_time < $row['next_time'] || $row['next_time'] < time())
1040
			$tasks[$row['id_task']] = $next_time;
1041
		else
1042
			$next_time = $row['next_time'];
1043
1044
		// If this is sooner than the current next task, make this the next task.
1045
		if ($next_time < $nextTaskTime)
1046
			$nextTaskTime = $next_time;
1047
	}
1048
	$smcFunc['db_free_result']($request);
1049
1050
	// Now make the changes!
1051
	foreach ($tasks as $id => $time)
1052
		$smcFunc['db_query']('', '
1053
			UPDATE {db_prefix}scheduled_tasks
1054
			SET next_time = {int:next_time}
1055
			WHERE id_task = {int:id_task}',
1056
			array(
1057
				'next_time' => $time,
1058
				'id_task' => $id,
1059
			)
1060
		);
1061
1062
	// If the next task is now different update.
1063
	if ($modSettings['next_task_time'] != $nextTaskTime)
1064
		updateSettings(array('next_task_time' => $nextTaskTime));
1065
}
1066
1067
/**
1068
 * Simply returns a time stamp of the next instance of these time parameters.
1069
 *
1070
 * @param int $regularity The regularity
1071
 * @param string $unit What unit are we using - 'm' for minutes, 'd' for days, 'w' for weeks or anything else for seconds
1072
 * @param int $offset The offset
1073
 * @return int The timestamp for the specified time
1074
 */
1075
function next_time($regularity, $unit, $offset)
1076
{
1077
	// Just in case!
1078
	if ($regularity == 0)
1079
		$regularity = 2;
1080
1081
	$curMin = date('i', time());
1082
1083
	// If the unit is minutes only check regularity in minutes.
1084
	if ($unit == 'm')
1085
	{
1086
		$off = date('i', $offset);
1087
1088
		// 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...
1089
		if ($off == $curMin)
1090
			$next_time = time() + $regularity;
1091
		else
1092
		{
1093
			// Make sure that the offset is always in the past.
1094
			$off = $off > $curMin ? $off - 60 : $off;
1095
1096
			while ($off <= $curMin)
1097
				$off += $regularity;
1098
1099
			// Now we know when the time should be!
1100
			$next_time = time() + 60 * ($off - $curMin);
1101
		}
1102
	}
1103
	// Otherwise, work out what the offset would be with today's date.
1104
	else
1105
	{
1106
		$next_time = mktime(date('H', $offset), date('i', $offset), 0, date('m'), date('d'), date('Y'));
1107
1108
		// Make the time offset in the past!
1109
		if ($next_time > time())
1110
		{
1111
			$next_time -= 86400;
1112
		}
1113
1114
		// Default we'll jump in hours.
1115
		$applyOffset = 3600;
1116
		// 24 hours = 1 day.
1117
		if ($unit == 'd')
1118
			$applyOffset = 86400;
1119
		// Otherwise a week.
1120
		if ($unit == 'w')
1121
			$applyOffset = 604800;
1122
1123
		$applyOffset *= $regularity;
1124
1125
		// Just add on the offset.
1126
		while ($next_time <= time())
1127
		{
1128
			$next_time += $applyOffset;
1129
		}
1130
	}
1131
1132
	return $next_time;
1133
}
1134
1135
/**
1136
 * This loads the bare minimum data to allow us to load language files!
1137
 */
1138
function loadEssentialThemeData()
1139
{
1140
	global $settings, $modSettings, $smcFunc, $mbname, $context, $sourcedir, $txt;
1141
1142
	// Get all the default theme variables.
1143
	$result = $smcFunc['db_query']('', '
1144
		SELECT id_theme, variable, value
1145
		FROM {db_prefix}themes
1146
		WHERE id_member = {int:no_member}
1147
			AND id_theme IN (1, {int:theme_guests})',
1148
		array(
1149
			'no_member' => 0,
1150
			'theme_guests' => !empty($modSettings['theme_guests']) ? $modSettings['theme_guests'] : 1,
1151
		)
1152
	);
1153
	while ($row = $smcFunc['db_fetch_assoc']($result))
1154
	{
1155
		$settings[$row['variable']] = $row['value'];
1156
1157
		// Is this the default theme?
1158
		if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1')
1159
			$settings['default_' . $row['variable']] = $row['value'];
1160
	}
1161
	$smcFunc['db_free_result']($result);
1162
1163
	// Check we have some directories setup.
1164
	if (empty($settings['template_dirs']))
1165
	{
1166
		$settings['template_dirs'] = array($settings['theme_dir']);
1167
1168
		// Based on theme (if there is one).
1169
		if (!empty($settings['base_theme_dir']))
1170
			$settings['template_dirs'][] = $settings['base_theme_dir'];
1171
1172
		// Lastly the default theme.
1173 View Code Duplication
		if ($settings['theme_dir'] != $settings['default_theme_dir'])
1174
			$settings['template_dirs'][] = $settings['default_theme_dir'];
1175
	}
1176
1177
	// Assume we want this.
1178
	$context['forum_name'] = $mbname;
1179
1180
	// Check loadLanguage actually exists!
1181
	if (!function_exists('loadLanguage'))
1182
	{
1183
		require_once($sourcedir . '/Load.php');
1184
		require_once($sourcedir . '/Subs.php');
1185
	}
1186
1187
	loadLanguage('index+Modifications');
1188
1189
	// Just in case it wasn't already set elsewhere.
1190
	$context['character_set'] = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
1191
	$context['utf8'] = $context['character_set'] === 'UTF-8';
1192
	$context['right_to_left'] = !empty($txt['lang_rtl']);
1193
1194
	// Tell fatal_lang_error() to not reload the theme.
1195
	$context['theme_loaded'] = true;
1196
}
1197
1198
/**
1199
 * This retieves data (e.g. last version of SMF) from sm.org
1200
 */
1201
function scheduled_fetchSMfiles()
1202
{
1203
	global $sourcedir, $txt, $language, $forum_version, $modSettings, $smcFunc, $context;
1204
1205
	// What files do we want to get
1206
	$request = $smcFunc['db_query']('', '
1207
		SELECT id_file, filename, path, parameters
1208
		FROM {db_prefix}admin_info_files',
1209
		array(
1210
		)
1211
	);
1212
1213
	$js_files = array();
1214
1215 View Code Duplication
	while ($row = $smcFunc['db_fetch_assoc']($request))
1216
	{
1217
		$js_files[$row['id_file']] = array(
1218
			'filename' => $row['filename'],
1219
			'path' => $row['path'],
1220
			'parameters' => sprintf($row['parameters'], $language, urlencode($modSettings['time_format']), urlencode($forum_version)),
1221
		);
1222
	}
1223
1224
	$smcFunc['db_free_result']($request);
1225
1226
	// We're gonna need fetch_web_data() to pull this off.
1227
	require_once($sourcedir . '/Subs-Package.php');
1228
1229
	// Just in case we run into a problem.
1230
	loadEssentialThemeData();
1231
	loadLanguage('Errors', $language, false);
1232
1233
	foreach ($js_files as $ID_FILE => $file)
1234
	{
1235
		// Create the url
1236
		$server = empty($file['path']) || (substr($file['path'], 0, 7) != 'http://' && substr($file['path'], 0, 8) != 'https://') ? 'https://www.simplemachines.org' : '';
1237
		$url = $server . (!empty($file['path']) ? $file['path'] : $file['path']) . $file['filename'] . (!empty($file['parameters']) ? '?' . $file['parameters'] : '');
1238
1239
		// Get the file
1240
		$file_data = fetch_web_data($url);
1241
1242
		// 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.
1243
		if ($file_data === false)
1244
		{
1245
			$context['scheduled_errors']['fetchSMfiles'][] = sprintf($txt['st_cannot_retrieve_file'], $url);
1246
			log_error(sprintf($txt['st_cannot_retrieve_file'], $url));
1247
			return false;
1248
		}
1249
1250
		// Save the file to the database.
1251
		$smcFunc['db_query']('substring', '
1252
			UPDATE {db_prefix}admin_info_files
1253
			SET data = SUBSTRING({string:file_data}, 1, 65534)
1254
			WHERE id_file = {int:id_file}',
1255
			array(
1256
				'id_file' => $ID_FILE,
1257
				'file_data' => $file_data,
1258
			)
1259
		);
1260
	}
1261
	return true;
1262
}
1263
1264
/**
1265
 * Happy birthday!!
1266
 */
1267
function scheduled_birthdayemails()
1268
{
1269
	global $smcFunc;
1270
1271
	$smcFunc['db_insert']('insert', '{db_prefix}background_tasks',
1272
		array('task_file' => 'string-255', 'task_class' => 'string-255', 'task_data' => 'string', 'claimed_time' => 'int'),
1273
		array('$sourcedir/tasks/Birthday-Notify.php', 'Birthday_Notify_Background', '', 0),
1274
		array()
1275
	);
1276
1277
	return true;
1278
}
1279
1280
/**
1281
 * Weekly maintenance
1282
 */
1283
function scheduled_weekly_maintenance()
1284
{
1285
	global $modSettings, $smcFunc;
1286
1287
	// Delete some settings that needn't be set if they are otherwise empty.
1288
	$emptySettings = array(
1289
		'warning_mute', 'warning_moderate', 'warning_watch', 'warning_show', 'disableCustomPerPage', 'spider_mode', 'spider_group',
1290
		'paid_currency_code', 'paid_currency_symbol', 'paid_email_to', 'paid_email', 'paid_enabled', 'paypal_email',
1291
		'search_enable_captcha', 'search_floodcontrol_time', 'show_spider_online',
1292
	);
1293
1294
	$smcFunc['db_query']('', '
1295
		DELETE FROM {db_prefix}settings
1296
		WHERE variable IN ({array_string:setting_list})
1297
			AND (value = {string:zero_value} OR value = {string:blank_value})',
1298
		array(
1299
			'zero_value' => '0',
1300
			'blank_value' => '',
1301
			'setting_list' => $emptySettings,
1302
		)
1303
	);
1304
1305
	// Some settings we never want to keep - they are just there for temporary purposes.
1306
	$deleteAnywaySettings = array(
1307
		'attachment_full_notified',
1308
	);
1309
1310
	$smcFunc['db_query']('', '
1311
		DELETE FROM {db_prefix}settings
1312
		WHERE variable IN ({array_string:setting_list})',
1313
		array(
1314
			'setting_list' => $deleteAnywaySettings,
1315
		)
1316
	);
1317
1318
	// Ok should we prune the logs?
1319
	if (!empty($modSettings['pruningOptions']))
1320
	{
1321
		if (!empty($modSettings['pruningOptions']) && strpos($modSettings['pruningOptions'], ',') !== false)
1322
			list ($modSettings['pruneErrorLog'], $modSettings['pruneModLog'], $modSettings['pruneBanLog'], $modSettings['pruneReportLog'], $modSettings['pruneScheduledTaskLog'], $modSettings['pruneSpiderHitLog']) = explode(',', $modSettings['pruningOptions']);
1323
1324 View Code Duplication
		if (!empty($modSettings['pruneErrorLog']))
1325
		{
1326
			// Figure out when our cutoff time is.  1 day = 86400 seconds.
1327
			$t = time() - $modSettings['pruneErrorLog'] * 86400;
1328
1329
			$smcFunc['db_query']('', '
1330
				DELETE FROM {db_prefix}log_errors
1331
				WHERE log_time < {int:log_time}',
1332
				array(
1333
					'log_time' => $t,
1334
				)
1335
			);
1336
		}
1337
1338
		if (!empty($modSettings['pruneModLog']))
1339
		{
1340
			// Figure out when our cutoff time is.  1 day = 86400 seconds.
1341
			$t = time() - $modSettings['pruneModLog'] * 86400;
1342
1343
			$smcFunc['db_query']('', '
1344
				DELETE FROM {db_prefix}log_actions
1345
				WHERE log_time < {int:log_time}
1346
					AND id_log = {int:moderation_log}',
1347
				array(
1348
					'log_time' => $t,
1349
					'moderation_log' => 1,
1350
				)
1351
			);
1352
		}
1353
1354 View Code Duplication
		if (!empty($modSettings['pruneBanLog']))
1355
		{
1356
			// Figure out when our cutoff time is.  1 day = 86400 seconds.
1357
			$t = time() - $modSettings['pruneBanLog'] * 86400;
1358
1359
			$smcFunc['db_query']('', '
1360
				DELETE FROM {db_prefix}log_banned
1361
				WHERE log_time < {int:log_time}',
1362
				array(
1363
					'log_time' => $t,
1364
				)
1365
			);
1366
		}
1367
1368
		if (!empty($modSettings['pruneReportLog']))
1369
		{
1370
			// Figure out when our cutoff time is.  1 day = 86400 seconds.
1371
			$t = time() - $modSettings['pruneReportLog'] * 86400;
1372
1373
			// This one is more complex then the other logs.  First we need to figure out which reports are too old.
1374
			$reports = array();
1375
			$result = $smcFunc['db_query']('', '
1376
				SELECT id_report
1377
				FROM {db_prefix}log_reported
1378
				WHERE time_started < {int:time_started}
1379
					AND closed = {int:closed}
1380
					AND ignore_all = {int:not_ignored}',
1381
				array(
1382
					'time_started' => $t,
1383
					'closed' => 1,
1384
					'not_ignored' => 0,
1385
				)
1386
			);
1387
1388
			while ($row = $smcFunc['db_fetch_row']($result))
1389
				$reports[] = $row[0];
1390
1391
			$smcFunc['db_free_result']($result);
1392
1393
			if (!empty($reports))
1394
			{
1395
				// Now delete the reports...
1396
				$smcFunc['db_query']('', '
1397
					DELETE FROM {db_prefix}log_reported
1398
					WHERE id_report IN ({array_int:report_list})',
1399
					array(
1400
						'report_list' => $reports,
1401
					)
1402
				);
1403
				// And delete the comments for those reports...
1404
				$smcFunc['db_query']('', '
1405
					DELETE FROM {db_prefix}log_reported_comments
1406
					WHERE id_report IN ({array_int:report_list})',
1407
					array(
1408
						'report_list' => $reports,
1409
					)
1410
				);
1411
			}
1412
		}
1413
1414 View Code Duplication
		if (!empty($modSettings['pruneScheduledTaskLog']))
1415
		{
1416
			// Figure out when our cutoff time is.  1 day = 86400 seconds.
1417
			$t = time() - $modSettings['pruneScheduledTaskLog'] * 86400;
1418
1419
			$smcFunc['db_query']('', '
1420
				DELETE FROM {db_prefix}log_scheduled_tasks
1421
				WHERE time_run < {int:time_run}',
1422
				array(
1423
					'time_run' => $t,
1424
				)
1425
			);
1426
		}
1427
1428 View Code Duplication
		if (!empty($modSettings['pruneSpiderHitLog']))
1429
		{
1430
			// Figure out when our cutoff time is.  1 day = 86400 seconds.
1431
			$t = time() - $modSettings['pruneSpiderHitLog'] * 86400;
1432
1433
			$smcFunc['db_query']('', '
1434
				DELETE FROM {db_prefix}log_spider_hits
1435
				WHERE log_time < {int:log_time}',
1436
				array(
1437
					'log_time' => $t,
1438
				)
1439
			);
1440
		}
1441
	}
1442
1443
	// Get rid of any paid subscriptions that were never actioned.
1444
	$smcFunc['db_query']('', '
1445
		DELETE FROM {db_prefix}log_subscribed
1446
		WHERE end_time = {int:no_end_time}
1447
			AND status = {int:not_active}
1448
			AND start_time < {int:start_time}
1449
			AND payments_pending < {int:payments_pending}',
1450
		array(
1451
			'no_end_time' => 0,
1452
			'not_active' => 0,
1453
			'start_time' => time() - 60,
1454
			'payments_pending' => 1,
1455
		)
1456
	);
1457
1458
	// Some OS's don't seem to clean out their sessions.
1459
	$smcFunc['db_query']('', '
1460
		DELETE FROM {db_prefix}sessions
1461
		WHERE last_update < {int:last_update}',
1462
		array(
1463
			'last_update' => time() - 86400,
1464
		)
1465
	);
1466
1467
	// Update the regex of top level domains with the IANA's latest official list
1468
	$smcFunc['db_insert']('insert', '{db_prefix}background_tasks',
1469
		array('task_file' => 'string-255', 'task_class' => 'string-255', 'task_data' => 'string', 'claimed_time' => 'int'),
1470
		array('$sourcedir/tasks/UpdateTldRegex.php', 'Update_TLD_Regex', '', 0), array()
1471
	);
1472
1473
	return true;
1474
}
1475
1476
/**
1477
 * Perform the standard checks on expiring/near expiring subscriptions.
1478
 */
1479
function scheduled_paid_subscriptions()
1480
{
1481
	global $sourcedir, $scripturl, $smcFunc, $modSettings, $language;
1482
1483
	// Start off by checking for removed subscriptions.
1484
	$request = $smcFunc['db_query']('', '
1485
		SELECT id_subscribe, id_member
1486
		FROM {db_prefix}log_subscribed
1487
		WHERE status = {int:is_active}
1488
			AND end_time < {int:time_now}',
1489
		array(
1490
			'is_active' => 1,
1491
			'time_now' => time(),
1492
		)
1493
	);
1494
	while ($row = $smcFunc['db_fetch_assoc']($request))
1495
	{
1496
		require_once($sourcedir . '/ManagePaid.php');
1497
		removeSubscription($row['id_subscribe'], $row['id_member']);
1498
	}
1499
	$smcFunc['db_free_result']($request);
1500
1501
	// Get all those about to expire that have not had a reminder sent.
1502
	$request = $smcFunc['db_query']('', '
1503
		SELECT ls.id_sublog, m.id_member, m.member_name, m.email_address, m.lngfile, s.name, ls.end_time
1504
		FROM {db_prefix}log_subscribed AS ls
1505
			JOIN {db_prefix}subscriptions AS s ON (s.id_subscribe = ls.id_subscribe)
1506
			JOIN {db_prefix}members AS m ON (m.id_member = ls.id_member)
1507
		WHERE ls.status = {int:is_active}
1508
			AND ls.reminder_sent = {int:reminder_sent}
1509
			AND s.reminder > {int:reminder_wanted}
1510
			AND ls.end_time < ({int:time_now} + s.reminder * 86400)',
1511
		array(
1512
			'is_active' => 1,
1513
			'reminder_sent' => 0,
1514
			'reminder_wanted' => 0,
1515
			'time_now' => time(),
1516
		)
1517
	);
1518
	$subs_reminded = array();
1519
	$members = array();
1520
	while ($row = $smcFunc['db_fetch_assoc']($request))
1521
	{
1522
		// If this is the first one load the important bits.
1523
		if (empty($subs_reminded))
1524
		{
1525
			require_once($sourcedir . '/Subs-Post.php');
1526
			// Need the below for loadLanguage to work!
1527
			loadEssentialThemeData();
1528
		}
1529
1530
		$subs_reminded[] = $row['id_sublog'];
1531
		$members[$row['id_member']] = $row;
1532
	}
1533
	$smcFunc['db_free_result']($request);
1534
1535
	// Load alert preferences
1536
	require_once($sourcedir . '/Subs-Notify.php');
1537
	$notifyPrefs = getNotifyPrefs(array_keys($members), 'paidsubs_expiring', true);
1538
	$alert_rows = array();
1539
	foreach ($members as $row)
1540
	{
1541
		$replacements = array(
1542
			'PROFILE_LINK' => $scripturl . '?action=profile;area=subscriptions;u=' . $row['id_member'],
1543
			'REALNAME' => $row['member_name'],
1544
			'SUBSCRIPTION' => $row['name'],
1545
			'END_DATE' => strip_tags(timeformat($row['end_time'])),
1546
		);
1547
1548
		$emaildata = loadEmailTemplate('paid_subscription_reminder', $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']);
1549
1550
		// Send the actual email.
1551
		if ($notifyPrefs[$row['id_member']] & 0x02)
1552
			sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, 'paid_sub_remind', $emaildata['is_html'], 2);
1553
1554
		if ($notifyPrefs[$row['id_member']] & 0x01)
1555
		{
1556
			$alert_rows[] = array(
1557
				'alert_time' => time(),
1558
				'id_member' => $row['id_member'],
1559
				'id_member_started' => $row['id_member'],
1560
				'member_name' => $row['member_name'],
1561
				'content_type' => 'paidsubs',
1562
				'content_id' => $row['id_sublog'],
1563
				'content_action' => 'expiring',
1564
				'is_read' => 0,
1565
				'extra' => $smcFunc['json_encode'](array(
1566
					'subscription_name' => $row['name'],
1567
					'end_time' => strip_tags(timeformat($row['end_time'])),
1568
				)),
1569
			);
1570
			updateMemberData($row['id_member'], array('alerts' => '+'));
1571
		}
1572
	}
1573
1574
	// Insert the alerts if any
1575 View Code Duplication
	if (!empty($alert_rows))
1576
		$smcFunc['db_insert']('',
1577
			'{db_prefix}user_alerts',
1578
			array('alert_time' => 'int', 'id_member' => 'int', 'id_member_started' => 'int', 'member_name' => 'string',
1579
				'content_type' => 'string', 'content_id' => 'int', 'content_action' => 'string', 'is_read' => 'int', 'extra' => 'string'),
1580
			$alert_rows,
1581
			array()
1582
		);
1583
1584
	// Mark the reminder as sent.
1585
	if (!empty($subs_reminded))
1586
		$smcFunc['db_query']('', '
1587
			UPDATE {db_prefix}log_subscribed
1588
			SET reminder_sent = {int:reminder_sent}
1589
			WHERE id_sublog IN ({array_int:subscription_list})',
1590
			array(
1591
				'subscription_list' => $subs_reminded,
1592
				'reminder_sent' => 1,
1593
			)
1594
		);
1595
1596
	return true;
1597
}
1598
1599
/**
1600
 * Check for un-posted attachments is something we can do once in a while :P
1601
 * This function uses opendir cycling through all the attachments
1602
 */
1603
function scheduled_remove_temp_attachments()
1604
{
1605
	global $smcFunc, $modSettings, $context, $txt;
1606
1607
	// We need to know where this thing is going.
1608 View Code Duplication
	if (!empty($modSettings['currentAttachmentUploadDir']))
1609
	{
1610
		if (!is_array($modSettings['attachmentUploadDir']))
1611
			$modSettings['attachmentUploadDir'] = $smcFunc['json_decode']($modSettings['attachmentUploadDir'], true);
1612
1613
		// Just use the current path for temp files.
1614
		$attach_dirs = $modSettings['attachmentUploadDir'];
1615
	}
1616
	else
1617
	{
1618
		$attach_dirs = array($modSettings['attachmentUploadDir']);
1619
	}
1620
1621
	foreach ($attach_dirs as $attach_dir)
1622
	{
1623
		$dir = @opendir($attach_dir);
1624
		if (!$dir)
1625
		{
1626
			loadEssentialThemeData();
1627
			loadLanguage('Post');
1628
			$context['scheduled_errors']['remove_temp_attachments'][] = $txt['cant_access_upload_path'] . ' (' . $attach_dir . ')';
1629
			log_error($txt['cant_access_upload_path'] . ' (' . $attach_dir . ')', 'critical');
1630
			return false;
1631
		}
1632
1633
		while ($file = readdir($dir))
1634
		{
1635
			if ($file == '.' || $file == '..')
1636
				continue;
1637
1638
			if (strpos($file, 'post_tmp_') !== false)
1639
			{
1640
				// Temp file is more than 5 hours old!
1641 View Code Duplication
				if (filemtime($attach_dir . '/' . $file) < time() - 18000)
1642
					@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...
1643
			}
1644
		}
1645
		closedir($dir);
1646
	}
1647
1648
	return true;
1649
}
1650
1651
/**
1652
 * Check for move topic notices that have past their best by date
1653
 */
1654
function scheduled_remove_topic_redirect()
1655
{
1656
	global $smcFunc, $sourcedir;
1657
1658
	// init
1659
	$topics = array();
1660
1661
	// We will need this for language files
1662
	loadEssentialThemeData();
1663
1664
	// Find all of the old MOVE topic notices that were set to expire
1665
	$request = $smcFunc['db_query']('', '
1666
		SELECT id_topic
1667
		FROM {db_prefix}topics
1668
		WHERE redirect_expires <= {int:redirect_expires}
1669
			AND redirect_expires <> 0',
1670
		array(
1671
			'redirect_expires' => time(),
1672
		)
1673
	);
1674
1675
	while ($row = $smcFunc['db_fetch_row']($request))
1676
		$topics[] = $row[0];
1677
	$smcFunc['db_free_result']($request);
1678
1679
	// Zap, your gone
1680
	if (count($topics) > 0)
1681
	{
1682
		require_once($sourcedir . '/RemoveTopic.php');
1683
		removeTopics($topics, false, true);
1684
	}
1685
1686
	return true;
1687
}
1688
1689
/**
1690
 * Check for old drafts and remove them
1691
 */
1692
function scheduled_remove_old_drafts()
1693
{
1694
	global $smcFunc, $sourcedir, $modSettings;
1695
1696
	if (empty($modSettings['drafts_keep_days']))
1697
		return true;
1698
1699
	// init
1700
	$drafts = array();
1701
1702
	// We need this for language items
1703
	loadEssentialThemeData();
1704
1705
	// Find all of the old drafts
1706
	$request = $smcFunc['db_query']('', '
1707
		SELECT id_draft
1708
		FROM {db_prefix}user_drafts
1709
		WHERE poster_time <= {int:poster_time_old}',
1710
		array(
1711
			'poster_time_old' => time() - (86400 * $modSettings['drafts_keep_days']),
1712
		)
1713
	);
1714
1715
	while ($row = $smcFunc['db_fetch_row']($request))
1716
		$drafts[] = (int) $row[0];
1717
	$smcFunc['db_free_result']($request);
1718
1719
	// If we have old one, remove them
1720
	if (count($drafts) > 0)
1721
	{
1722
		require_once($sourcedir . '/Drafts.php');
1723
		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...
1724
	}
1725
1726
	return true;
1727
}
1728
1729
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...