Completed
Pull Request — patch_1-1-4 (#3202)
by Spuds
15:49
created

ScheduledTasks.subs.php ➔ processNextTasks()   C

Complexity

Conditions 10
Paths 21

Size

Total Lines 65
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 35
CRAP Score 10.0021

Importance

Changes 0
Metric Value
cc 10
eloc 30
nc 21
nop 1
dl 0
loc 65
ccs 35
cts 36
cp 0.9722
crap 10.0021
rs 6.2553
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
 * Functions to support schedules tasks
5
 *
6
 * @name      ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
9
 *
10
 * @version 1.1
11
 *
12
 */
13
14
/**
15
 * Calculate the next time the passed tasks should be triggered.
16
 *
17
 * @package ScheduledTasks
18
 * @param string[]|string $tasks = array() the tasks
19
 * @param boolean $forceUpdate
20
 */
21
function calculateNextTrigger($tasks = array(), $forceUpdate = false)
22
{
23
	global $modSettings;
24
25
	$db = database();
26
27
	$task_query = '';
28
29
	if (!is_array($tasks))
30
		$tasks = array($tasks);
31
32
	// Actually have something passed?
33
	if (!empty($tasks))
34
	{
35
		if (!isset($tasks[0]) || is_numeric($tasks[0]))
36
			$task_query = ' AND id_task IN ({array_int:tasks})';
37
		else
38
			$task_query = ' AND task IN ({array_string:tasks})';
39
	}
40
41
	$nextTaskTime = empty($tasks) ? time() + 86400 : $modSettings['next_task_time'];
42
43
	// Get the critical info for the tasks.
44
	$request = $db->query('', '
45
		SELECT id_task, next_time, time_offset, time_regularity, time_unit, task
46
		FROM {db_prefix}scheduled_tasks
47
		WHERE disabled = {int:no_disabled}
48
			' . $task_query,
49
		array(
50
			'no_disabled' => 0,
51
			'tasks' => $tasks,
52
		)
53
	);
54
	$tasks = array();
55
	$scheduleTaskImmediate = !empty($modSettings['scheduleTaskImmediate']) ? Util::unserialize($modSettings['scheduleTaskImmediate']) : array();
56
	while ($row = $db->fetch_assoc($request))
57
	{
58
		// scheduleTaskImmediate is a way to speed up scheduled tasks and fire them as fast as possible
59
		if (!empty($scheduleTaskImmediate) && isset($scheduleTaskImmediate[$row['task']]))
60
			$next_time = next_time(1, '', rand(0, 60), true);
61
		else
62
			$next_time = next_time($row['time_regularity'], $row['time_unit'], $row['time_offset']);
63
64
		// Only bother moving the task if it's out of place or we're forcing it!
65
		if ($forceUpdate || $next_time < $row['next_time'] || $row['next_time'] < time())
66
			$tasks[$row['id_task']] = $next_time;
67
		else
68
			$next_time = $row['next_time'];
69
70
		// If this is sooner than the current next task, make this the next task.
71
		if ($next_time < $nextTaskTime)
72
			$nextTaskTime = $next_time;
73
	}
74
	$db->free_result($request);
75
76
	// Now make the changes!
77
	foreach ($tasks as $id => $time)
78
		$db->query('', '
79
			UPDATE {db_prefix}scheduled_tasks
80
			SET next_time = {int:next_time}
81
			WHERE id_task = {int:id_task}',
82
			array(
83
				'next_time' => $time,
84
				'id_task' => $id,
85
			)
86
		);
87
88
	// If the next task is now different update.
89
	if ($modSettings['next_task_time'] != $nextTaskTime)
90
		updateSettings(array('next_task_time' => $nextTaskTime));
91
}
92
93
/**
94
 * Returns a time stamp of the next instance of these time parameters.
95
 *
96
 * @package ScheduledTasks
97
 * @param int $regularity
98
 * @param string $unit
99
 * @param int $offset
100
 * @param boolean $immediate
101
 * @return int
102
 */
103
function next_time($regularity, $unit, $offset, $immediate = false)
104
{
105
	// Just in case!
106 4
	if ($regularity == 0)
107 4
		$regularity = 2;
108
109 4
	$curMin = date('i', time());
110
111
	// If we have scheduleTaskImmediate running, then it's 10 seconds
112 4
	if (empty($unit) && $immediate)
113 4
		$next_time = time() + 10;
114
	// If the unit is minutes only check regularity in minutes.
115 4
	elseif ($unit == 'm')
116
	{
117
		$off = date('i', $offset);
118
119
		// If it's now just pretend it ain't,
120
		if ($off == $curMin)
121
			$next_time = time() + $regularity;
122
		else
123
		{
124
			// Make sure that the offset is always in the past.
125
			$off = $off > $curMin ? $off - 60 : $off;
126
127
			while ($off <= $curMin)
128
				$off += $regularity;
129
130
			// Now we know when the time should be!
131
			$next_time = time() + 60 * ($off - $curMin);
132
		}
133
	}
134
	// Otherwise, work out what the offset would be with todays date.
135
	else
136
	{
137 4
		$next_time = mktime(date('H', $offset), date('i', $offset), 0, date('m'), date('d'), date('Y'));
138
139
		// Make the time offset in the past!
140 4
		if ($next_time > time())
141 4
			$next_time -= 86400;
142
143
		// Default we'll jump in hours.
144 4
		$applyOffset = 3600;
145
146
		// 24 hours = 1 day.
147 4
		if ($unit == 'd')
148 4
			$applyOffset = 86400;
149
150
		// Otherwise a week.
151 4
		if ($unit == 'w')
152 4
			$applyOffset = 604800;
153
154 4
		$applyOffset *= $regularity;
155
156
		// Just add on the offset.
157 4
		while ($next_time <= time())
158 4
			$next_time += $applyOffset;
159
	}
160
161 4
	return $next_time;
162
}
163
164
/**
165
 * Loads a basic tasks list.
166
 *
167
 * @package ScheduledTasks
168
 * @param int[] $tasks
169
 * @return array
170
 */
171 View Code Duplication
function loadTasks($tasks)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
172
{
173
	$db = database();
174
175
	$request = $db->query('', '
176
		SELECT id_task, task
177
		FROM {db_prefix}scheduled_tasks
178
		WHERE id_task IN ({array_int:tasks})
179
		LIMIT ' . count($tasks),
180
		array(
181
			'tasks' => $tasks,
182
		)
183
	);
184
	$task = array();
185
	while ($row = $db->fetch_assoc($request))
186
		$task[$row['id_task']] = $row['task'];
187
	$db->free_result($request);
188
189
	return $task;
190
}
191
192
/**
193
 * Logs a task.
194
 *
195
 * @package ScheduledTasks
196
 * @param int $id_log the id of the log entry of the task just run. If empty it is considered a new log entry
197
 * @param int $task_id the id of the task run (from the table scheduled_tasks)
198
 * @param int|null $total_time How long the task took to finish. If NULL (default value) -1 will be used
199
 * @return int the id_log value
200
 */
201
function logTask($id_log, $task_id, $total_time = null)
202
{
203 4
	$db = database();
204
205 4
	if (empty($id_log))
206 4
	{
207 4
		$db->insert('',
208 4
			'{db_prefix}log_scheduled_tasks',
209 4
			array('id_task' => 'int', 'time_run' => 'int', 'time_taken' => 'float'),
210 4
			array($task_id, time(), $total_time === null ? -1 : $total_time),
211 4
			array('id_task')
212 4
		);
213
214 4
		return $db->insert_id('{db_prefix}log_scheduled_tasks', 'id_log');
215
	}
216
	else
217
	{
218 4
		$db->query('', '
219
			UPDATE {db_prefix}log_scheduled_tasks
220
			SET time_taken = {float:time_taken}
221 4
			WHERE id_log = {int:id_log}',
222
			array(
223 4
				'time_taken' => $total_time,
224 4
				'id_log' => $id_log,
225
			)
226 4
		);
227
228 4
		return $id_log;
229
	}
230
}
231
232
/**
233
 * All the scheduled tasks associated with the id passed to the function are
234
 * enabled, while the remaining are disabled
235
 *
236
 * @package ScheduledTasks
237
 * @param int[] $enablers array od task IDs
238
 */
239
function updateTaskStatus($enablers)
240
{
241
	$db = database();
242
243
	$db->query('', '
244
		UPDATE {db_prefix}scheduled_tasks
245
		SET disabled = CASE WHEN id_task IN ({array_int:id_task_enable}) THEN 0 ELSE 1 END',
246
		array(
247
			'id_task_enable' => $enablers,
248
		)
249
	);
250
}
251
252
/**
253
 * Sets the task status to enabled / disabled by task name (i.e. function)
254
 *
255
 * @package ScheduledTasks
256
 * @param string $enabler the name (the function) of a task
257
 * @param bool $enable is if the tasks should be enabled or disabled
258
 */
259
function toggleTaskStatusByName($enabler, $enable = true)
260
{
261
	$db = database();
262
263
	$db->query('', '
264
		UPDATE {db_prefix}scheduled_tasks
265
		SET disabled = {int:status}
266
		WHERE task = {string:task_enable}',
267
		array(
268
			'task_enable' => $enabler,
269
			'status' => $enable ? 0 : 1,
270
		)
271
	);
272
}
273
274
/**
275
 * Update the properties of a scheduled task.
276
 *
277
 * @package ScheduledTasks
278
 * @param int $id_task
279
 * @param int|null $disabled
280
 * @param int|null $offset
281
 * @param int|null $interval
282
 * @param string|null $unit
283
 */
284
function updateTask($id_task, $disabled = null, $offset = null, $interval = null, $unit = null)
285
{
286
	$db = database();
287
288
	$sets = array(
289
		'disabled' => 'disabled = {int:disabled}',
290
		'offset' => 'time_offset = {int:time_offset}',
291
		'interval' => 'time_regularity = {int:time_regularity}',
292
		'unit' => 'time_unit = {string:time_unit}',
293
	);
294
295
	$updates = array();
296
	foreach ($sets as $key => $set)
297
		if (isset($$key))
298
			$updates[] = $set;
299
300
	$db->query('', '
301
		UPDATE {db_prefix}scheduled_tasks
302
		SET ' . (implode(',
303
			', $updates)) . '
304
		WHERE id_task = {int:id_task}',
305
		array(
306
			'disabled' => $disabled,
307
			'time_offset' => $offset,
308
			'time_regularity' => $interval,
309
			'id_task' => $id_task,
310
			'time_unit' => $unit,
311
		)
312
	);
313
}
314
315
/**
316
 * Loads the details from a given task.
317
 *
318
 * @package ScheduledTasks
319
 *
320
 * @param int $id_task
321
 *
322
 * @return array
323
 * @throws Elk_Exception no_access
324
 */
325
function loadTaskDetails($id_task)
326
{
327
	global $txt;
328
329
	$db = database();
330
331
	$task = array();
332
333
	$request = $db->query('', '
334
		SELECT id_task, next_time, time_offset, time_regularity, time_unit, disabled, task
335
		FROM {db_prefix}scheduled_tasks
336
		WHERE id_task = {int:id_task}',
337
		array(
338
			'id_task' => $id_task,
339
		)
340
	);
341
	// Should never, ever, happen!
342
	if ($db->num_rows($request) == 0)
343
		throw new Elk_Exception('no_access', false);
344
	while ($row = $db->fetch_assoc($request))
345
	{
346
		$task = array(
347
			'id' => $row['id_task'],
348
			'function' => $row['task'],
349
			'name' => isset($txt['scheduled_task_' . $row['task']]) ? $txt['scheduled_task_' . $row['task']] : $row['task'],
350
			'desc' => isset($txt['scheduled_task_desc_' . $row['task']]) ? $txt['scheduled_task_desc_' . $row['task']] : '',
351
			'next_time' => $row['disabled'] ? $txt['scheduled_tasks_na'] : standardTime($row['next_time'] == 0 ? time() : $row['next_time'], true, 'server'),
352
			'disabled' => $row['disabled'],
353
			'offset' => $row['time_offset'],
354
			'regularity' => $row['time_regularity'],
355
			'offset_formatted' => date('H:i', $row['time_offset']),
356
			'unit' => $row['time_unit'],
357
		);
358
	}
359
	$db->free_result($request);
360
361
	return $task;
362
}
363
364
/**
365
 * Returns an array of registered scheduled tasks.
366
 *
367
 * - Used also by createList() callbacks.
368
 *
369
 * @package ScheduledTasks
370
 * @return array
371
 */
372
function scheduledTasks()
373
{
374
	global $txt;
375
376
	$db = database();
377
378
	$request = $db->query('', '
379
		SELECT id_task, next_time, time_offset, time_regularity, time_unit, disabled, task
380
		FROM {db_prefix}scheduled_tasks',
381
		array(
382
		)
383
	);
384
	$known_tasks = array();
385
	while ($row = $db->fetch_assoc($request))
386
	{
387
		// Find the next for regularity - don't offset as it's always server time!
388
		$offset = sprintf($txt['scheduled_task_reg_starting'], date('H:i', $row['time_offset']));
389
		$repeating = sprintf($txt['scheduled_task_reg_repeating'], $row['time_regularity'], $txt['scheduled_task_reg_unit_' . $row['time_unit']]);
390
391
		$known_tasks[] = array(
392
			'id' => $row['id_task'],
393
			'function' => $row['task'],
394
			'name' => isset($txt['scheduled_task_' . $row['task']]) ? $txt['scheduled_task_' . $row['task']] : $row['task'],
395
			'desc' => isset($txt['scheduled_task_desc_' . $row['task']]) ? $txt['scheduled_task_desc_' . $row['task']] : '',
396
			'next_time' => $row['disabled'] ? $txt['scheduled_tasks_na'] : standardTime(($row['next_time'] == 0 ? time() : $row['next_time']), true, 'server'),
397
			'disabled' => $row['disabled'],
398
			'checked_state' => $row['disabled'] ? '' : 'checked="checked"',
399
			'regularity' => $offset . ', ' . $repeating,
400
		);
401
	}
402
	$db->free_result($request);
403
404
	return $known_tasks;
405
}
406
407
/**
408
 * Return task log entries, within the passed limits.
409
 *
410
 * - Used by createList() callbacks.
411
 *
412
 * @package ScheduledTasks
413
 * @param int $start The item to start with (for pagination purposes)
414
 * @param int $items_per_page  The number of items to show per page
415
 * @param string $sort A string indicating how to sort the results
416
 *
417
 * @return array
418
 */
419
function getTaskLogEntries($start, $items_per_page, $sort)
420
{
421
	global $txt;
422
423
	$db = database();
424
425
	$request = $db->query('', '
426
		SELECT lst.id_log, lst.id_task, lst.time_run, lst.time_taken, st.task
427
		FROM {db_prefix}log_scheduled_tasks AS lst
428
			INNER JOIN {db_prefix}scheduled_tasks AS st ON (st.id_task = lst.id_task)
429
		ORDER BY ' . $sort . '
430
		LIMIT ' . $start . ', ' . $items_per_page,
431
		array(
432
		)
433
	);
434
	$log_entries = array();
435
	while ($row = $db->fetch_assoc($request))
436
		$log_entries[] = array(
437
			'id' => $row['id_log'],
438
			'name' => isset($txt['scheduled_task_' . $row['task']]) ? $txt['scheduled_task_' . $row['task']] : $row['task'],
439
			'time_run' => $row['time_run'],
440
			// -1 means failed task, but in order to look better in the UI we switch it to 0
441
			'time_taken' => $row['time_taken'] == -1 ? 0 : $row['time_taken'],
442
			'task_completed' => $row['time_taken'] != -1,
443
		);
444
	$db->free_result($request);
445
446
	return $log_entries;
447
}
448
449
/**
450
 * Return the number of task log entries.
451
 *
452
 * - Used by createList() callbacks.
453
 *
454
 * @package ScheduledTasks
455
 * @return int
456
 */
457
function countTaskLogEntries()
458
{
459
	$db = database();
460
461
	$request = $db->query('', '
462
		SELECT COUNT(*)
463
		FROM {db_prefix}log_scheduled_tasks',
464
		array(
465
		)
466
	);
467
	list ($num_entries) = $db->fetch_row($request);
468
	$db->free_result($request);
469
470
	return $num_entries;
471
}
472
473
/**
474
 * Empty the scheduled tasks log.
475
 */
476
function emptyTaskLog()
477
{
478
	$db = database();
479
480
	$db->query('truncate_table', '
481
		TRUNCATE {db_prefix}log_scheduled_tasks',
482
		array(
483
		)
484
	);
485
}
486
487
/**
488
 * Process the next tasks, one by one, and update the results.
489
 *
490
 * @package ScheduledTasks
491
 * @param int $ts = 0
492
 */
493
function processNextTasks($ts = 0)
494
{
495 4
	$db = database();
496
497
	// Select the next task to do.
498 4
	$request = $db->query('', '
499
		SELECT id_task, task, next_time, time_offset, time_regularity, time_unit
500
		FROM {db_prefix}scheduled_tasks
501
		WHERE disabled = {int:not_disabled}
502
			AND next_time <= {int:current_time}
503
		ORDER BY next_time ASC
504 4
		LIMIT 1',
505
		array(
506 4
			'not_disabled' => 0,
507 4
			'current_time' => time(),
508
		)
509 4
	);
510 4
	if ($db->num_rows($request) != 0)
511 4
	{
512
		// The two important things really...
513 4
		$row = $db->fetch_assoc($request);
514
515
		// When should this next be run?
516 4
		$next_time = next_time($row['time_regularity'], $row['time_unit'], $row['time_offset']);
517
518
		// How long in seconds is the gap?
519 4
		$duration = $row['time_regularity'];
520 4
		if ($row['time_unit'] == 'm')
521 4
			$duration *= 60;
522 4
		elseif ($row['time_unit'] == 'h')
523
			$duration *= 3600;
524 4
		elseif ($row['time_unit'] == 'd')
525 2
			$duration *= 86400;
526 2
		elseif ($row['time_unit'] == 'w')
527 2
			$duration *= 604800;
528
529
		// If we were really late running this task actually skip the next one.
530 4
		if (time() + ($duration / 2) > $next_time)
531 4
			$next_time += $duration;
532
533
		// Update it now, so no others run this!
534 4
		$db->query('', '
535
			UPDATE {db_prefix}scheduled_tasks
536
			SET next_time = {int:next_time}
537
			WHERE id_task = {int:id_task}
538 4
				AND next_time = {int:current_next_time}',
539
			array(
540 4
				'next_time' => $next_time,
541 4
				'id_task' => $row['id_task'],
542 4
				'current_next_time' => $row['next_time'],
543
			)
544 4
		);
545 4
		$affected_rows = $db->affected_rows();
546
547
		// Do also some timestamp checking,
548
		// and do this only if we updated it before.
549 4
		if ((empty($ts) || $ts == $row['next_time']) && $affected_rows)
550 4
		{
551 4
			ignore_user_abort(true);
552 4
			run_this_task($row['id_task'], $row['task']);
553 4
		}
554
555 4
	}
556 4
	$db->free_result($request);
557 4
}
558
559
/**
560
 * Calls the supplied task_name so that it is executed.
561
 *
562
 * - Logs that the task was executed with success or if it failed
563
 *
564
 * @package ScheduledTasks
565
 * @param int $id_task specific id of the task to run, used for logging
566
 * @param string $task_name name of the task, class name, function name, method in ScheduledTask.class
567
 */
568
function run_this_task($id_task, $task_name)
569
{
570 4
	global $time_start, $modSettings;
571
572 4
	Elk_Autoloader::instance()->register(SUBSDIR . '/ScheduledTask', '\\ElkArte\\sources\\subs\\ScheduledTask');
573
574
	// Let's start logging the task and saying we failed it
575 4
	$log_task_id = logTask(0, $id_task);
576
577 4
	$class = '\\ElkArte\\sources\\subs\\ScheduledTask\\' . implode('_', array_map('ucfirst', explode('_', $task_name)));
578
579 4
	if (class_exists($class))
580 4
	{
581 4
		$task = new $class();
582
583 4
		$completed = $task->run();
584 4
	}
585
	else
586
		$completed = run_this_task_compat($task_name);
0 ignored issues
show
Deprecated Code introduced by
The function run_this_task_compat() has been deprecated with message: since 1.1 - Deprecated in favour of naming scheme

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
587
588 4
	$scheduleTaskImmediate = !empty($modSettings['scheduleTaskImmediate']) ? Util::unserialize($modSettings['scheduleTaskImmediate']) : array();
589
	// Log that we did it ;)
590
	if ($completed)
591 4
	{
592
		// Taking care of scheduleTaskImmediate having a maximum of 10 "fast" executions
593 4
		if (!empty($scheduleTaskImmediate) && isset($scheduleTaskImmediate[$task_name]))
594 4
		{
595
			$scheduleTaskImmediate[$task_name]++;
596
597
			if ($scheduleTaskImmediate[$task_name] > 9)
598
				removeScheduleTaskImmediate($task_name, false);
599
			else
600
				updateSettings(array('scheduleTaskImmediate' => serialize($scheduleTaskImmediate)));
601
		}
602
603 4
		$total_time = round(microtime(true) - $time_start, 3);
604
605
		// If the task ended successfully, then log the proper time taken to complete
606 4
		logTask($log_task_id, $id_task, $total_time);
607 4
	}
608 4
}
609
610
/**
611
 * 1.0 compatibility function, used to maintain compatibility with naming
612
 * scheme used in ElkArte 1.0
613
 *
614
 * @package ScheduledTasks
615
 * @param string $task_name name of the task, class name, function name, method in ScheduledTask.class
616
 * @return mixed bool
617
 * @deprecated since 1.1 - Deprecated in favour of naming scheme
618
 */
619
function run_this_task_compat($task_name)
620
{
621
	$completed = false;
622
623
	// The method must exist in ScheduledTask class, or we are wasting our time.
624
	// Actually for extendability sake, we need to have other ways, so:
625
	// A simple procedural function?
626
	if (strpos($task_name, '::') === false && function_exists($task_name))
627
	{
628
		$method = $task_name;
629
630
		// Do the task...
631
		$completed = $method();
632
	}
633
	// It may be a class (no static, sorry)
634
	elseif (strpos($task_name, '::') !== false)
635
	{
636
		$call = explode('::', $task_name);
637
		$task_object = new $call[0];
638
		$method = $call[1];
639
640
		if (method_exists($task_object, $method))
641
		{
642
			// Try to stop a timeout, this would be bad...
643
			detectServer()->setTimeLimit(300);
644
645
			// Do the task...
646
			$completed = $task_object->{$method}();
647
		}
648
	}
649
650
	return $completed;
651
}
652
653
/**
654
 * Retrieve info if there's any next task scheduled and when.
655
 *
656
 * @package ScheduledTasks
657
 * @return mixed int|false
658
 */
659 View Code Duplication
function nextTime()
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
660
{
661 4
	$db = database();
662
663
	// The next stored timestamp, is there any?
664 4
	$request = $db->query('', '
665
		SELECT next_time
666
		FROM {db_prefix}scheduled_tasks
667
		WHERE disabled = {int:not_disabled}
668
		ORDER BY next_time ASC
669 4
		LIMIT 1',
670
		array(
671 4
			'not_disabled' => 0,
672
		)
673 4
	);
674
	// No new task scheduled?
675 4
	if ($db->num_rows($request) === 0)
676 4
		$result = false;
677
	else
678 4
		list ($result) = $db->fetch_row($request);
679
680 4
	$db->free_result($request);
681
682
	return $result;
683
}