Completed
Pull Request — development (#3149)
by John
09:23
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 25
CRAP Score 10.1228

Importance

Changes 0
Metric Value
cc 10
eloc 30
nc 21
nop 1
dl 0
loc 65
ccs 25
cts 28
cp 0.8929
crap 10.1228
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 2.0 dev
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
		$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
		$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
			$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 2
			$applyOffset = 86400;
149
150
		// Otherwise a week.
151 4
		if ($unit == 'w')
152 2
			$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
	{
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
		);
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
			WHERE id_log = {int:id_log}',
222
			array(
223 4
				'time_taken' => $total_time,
224 4
				'id_log' => $id_log,
225
			)
226
		);
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 View Code Duplication
function countTaskLogEntries()
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...
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
		LIMIT 1',
505
		array(
506 4
			'not_disabled' => 0,
507 4
			'current_time' => time(),
508
		)
509
	);
510 4
	if ($db->num_rows($request) != 0)
511
	{
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
			$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
			$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
				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
		);
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
		{
551 4
			ignore_user_abort(true);
552 4
			run_this_task($row['id_task'], $row['task']);
553
		}
554
555
	}
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
	{
581 4
		$task = new $class();
582
583 4
		$completed = $task->run();
584
	}
585
	else
586
	{
587
		$completed = false;
588
	}
589
590 4
	$scheduleTaskImmediate = !empty($modSettings['scheduleTaskImmediate']) ? Util::unserialize($modSettings['scheduleTaskImmediate']) : array();
591
	// Log that we did it ;)
592 4
	if ($completed)
593
	{
594
		// Taking care of scheduleTaskImmediate having a maximum of 10 "fast" executions
595 4
		if (!empty($scheduleTaskImmediate) && isset($scheduleTaskImmediate[$task_name]))
596
		{
597
			$scheduleTaskImmediate[$task_name]++;
598
599
			if ($scheduleTaskImmediate[$task_name] > 9)
600
				removeScheduleTaskImmediate($task_name, false);
601
			else
602
				updateSettings(array('scheduleTaskImmediate' => serialize($scheduleTaskImmediate)));
603
		}
604
605 4
		$total_time = round(microtime(true) - $time_start, 3);
606
607
		// If the task ended successfully, then log the proper time taken to complete
608 4
		logTask($log_task_id, $id_task, $total_time);
609
	}
610 4
}
611
612
/**
613
 * Retrieve info if there's any next task scheduled and when.
614
 *
615
 * @package ScheduledTasks
616
 * @return mixed int|false
617
 */
618 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...
619
{
620 4
	$db = database();
621
622
	// The next stored timestamp, is there any?
623 4
	$request = $db->query('', '
624
		SELECT next_time
625
		FROM {db_prefix}scheduled_tasks
626
		WHERE disabled = {int:not_disabled}
627
		ORDER BY next_time ASC
628
		LIMIT 1',
629
		array(
630 4
			'not_disabled' => 0,
631
		)
632
	);
633
	// No new task scheduled?
634 4
	if ($db->num_rows($request) === 0)
635
		$result = false;
636
	else
637 4
		list ($result) = $db->fetch_row($request);
638
639 4
	$db->free_result($request);
640
641
	return $result;
642
}