Completed
Push — master ( 806d95...65f8ba )
by cam
04:19
created

queue.php ➔ queue_affichage_cron()   C

Complexity

Conditions 13
Paths 16

Size

Total Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
nc 16
nop 0
dl 0
loc 56
rs 6.6166
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
 *  SPIP, Systeme de publication pour l'internet                           *
5
 *                                                                         *
6
 *  Copyright (c) 2001-2019                                                *
7
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
8
 *                                                                         *
9
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
10
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
11
\***************************************************************************/
12
13
/**
14
 * Gestion des queues de travaux
15
 *
16
 * @package SPIP\Core\Queue
17
 **/
18
if (!defined("_ECRIRE_INC_VERSION")) {
19
	return;
20
}
21
22
define('_JQ_SCHEDULED', 1);
23
define('_JQ_PENDING', 0);
24
#define('_JQ_MAX_JOBS_EXECUTE',200); // pour personaliser le nombre de jobs traitables a chaque hit
25
#define('_JQ_MAX_JOBS_TIME_TO_EXECUTE',15); // pour personaliser le temps d'excution dispo a chaque hit
26
#define('_JQ_NB_JOBS_OVERFLOW',10000); // nombre de jobs a partir duquel on force le traitement en fin de hit pour purger
27
28
/**
29
 * Ajouter une tâche à la file
30
 *
31
 * Les tâches sont ensuites exécutées par date programmée croissant/priorité décroissante
32
 *
33
 * @param $function
34
 *   The function name to call.
35
 * @param $description
36
 *   A human-readable description of the queued job.
37
 * @param $arguments
38
 *   Optional array of arguments to pass to the function.
39
 * @param $file
40
 *   Optional file path which needs to be included for $fucntion.
41
 * @param $no_duplicate
42
 *   If TRUE, do not add the job to the queue if one with the same function and
43
 *   arguments already exists.
44
 *   If 'function_only' test of existence is only on function name (for cron job)
45
 * @param $time
46
 *    time for starting the job. If 0, job will start as soon as possible
47
 * @param $priority
48
 *    -10 (low priority) to +10 (high priority), 0 is the default
49
 * @return int
50
 *  id of job
51
 */
52
function queue_add_job(
53
	$function,
54
	$description,
55
	$arguments = array(),
56
	$file = '',
57
	$no_duplicate = false,
58
	$time = 0,
59
	$priority = 0
60
) {
61
	include_spip('base/abstract_sql');
62
63
	// cas pourri de ecrire/action/editer_site avec l'option reload=oui
64
	if (defined('_GENIE_SYNDIC_NOW')) {
65
		$arguments['id_syndic'] = _GENIE_SYNDIC_NOW;
66
	}
67
68
	// serialiser les arguments
69
	$arguments = serialize($arguments);
70
	$md5args = md5($arguments);
71
72
	// si pas de date programee, des que possible
73
	$duplicate_where = 'status=' . intval(_JQ_SCHEDULED) . ' AND ';
74
	if (!$time) {
75
		$time = time();
76
		$duplicate_where = ""; // ne pas dupliquer si deja le meme job en cours d'execution
77
	}
78
	$date = date('Y-m-d H:i:s', $time);
79
80
	$set_job = array(
81
		'fonction' => $function,
82
		'descriptif' => $description,
83
		'args' => $arguments,
84
		'md5args' => $md5args,
85
		'inclure' => $file,
86
		'priorite' => max(-10, min(10, intval($priority))),
87
		'date' => $date,
88
		'status' => _JQ_SCHEDULED,
89
	);
90
	// si option ne pas dupliquer, regarder si la fonction existe deja
91
	// avec les memes args et file
92
	if (
93
		$no_duplicate
94
		and
95
		$id_job = sql_getfetsel('id_job', 'spip_jobs',
96
			$duplicate_where =
97
				$duplicate_where . 'fonction=' . sql_quote($function)
98
				. (($no_duplicate === 'function_only') ? '' :
99
					' AND md5args=' . sql_quote($md5args) . ' AND inclure=' . sql_quote($file)))
100
	) {
101
		return $id_job;
102
	}
103
104
	$id_job = sql_insertq('spip_jobs', $set_job);
105
	// en cas de concurrence, deux process peuvent arriver jusqu'ici en parallele
106
	// avec le meme job unique a inserer. Dans ce cas, celui qui a eu l'id le plus grand
107
	// doit s'effacer
108
	if (
109
		$no_duplicate
110
		and
111
		$id_prev = sql_getfetsel('id_job', 'spip_jobs', "id_job<" . intval($id_job) . " AND $duplicate_where")
112
	) {
113
		sql_delete('spip_jobs', 'id_job=' . intval($id_job));
114
115
		return $id_prev;
116
	}
117
118
	// verifier la non duplication qui peut etre problematique en cas de concurence
119
	// il faut dans ce cas que seul le dernier ajoute se supprime !
120
121
	// une option de debug pour verifier que les arguments en base sont bons
122
	// ie cas d'un char non acceptables sur certains type de champs
123
	// qui coupe la valeur
124
	if (defined('_JQ_INSERT_CHECK_ARGS') and $id_job) {
125
		$args = sql_getfetsel('args', 'spip_jobs', 'id_job=' . intval($id_job));
126
		if ($args !== $arguments) {
127
			spip_log('arguments job errones / longueur ' . strlen($args) . " vs " . strlen($arguments) . ' / valeur : ' . var_export($arguments,
128
					true), 'queue');
129
		}
130
	}
131
132
	if ($id_job) {
133
		queue_update_next_job_time($time);
134
	}
135
	// si la mise en file d'attente du job echoue,
136
	// il ne faut pas perdre l'execution de la fonction
137
	// on la lance immediatement, c'est un fallback
138
	// sauf en cas d'upgrade necessaire (table spip_jobs inexistante)
139
	elseif ($GLOBALS['meta']['version_installee'] == $GLOBALS['spip_version_base']) {
140
		$set_job['id_job'] = 0;
141
		queue_start_job($set_job);
142
	}
143
144
	return $id_job;
145
}
146
147
/**
148
 * Purger la file de tâche et reprogrammer les tâches périodiques
149
 *
150
 * @return void
151
 */
152
function queue_purger() {
153
	include_spip('base/abstract_sql');
154
	sql_delete('spip_jobs');
155
	sql_delete("spip_jobs_liens", "id_job NOT IN (" . sql_get_select("id_job", "spip_jobs") . ")");
156
	include_spip('inc/genie');
157
	genie_queue_watch_dist();
158
}
159
160
/**
161
 * Retirer une tache de la file d'attente
162
 *
163
 * @param int $id_job
164
 *  id de la tache a retirer
165
 * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|string?

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

Loading history...
166
 */
167
function queue_remove_job($id_job) {
168
	include_spip('base/abstract_sql');
169
170
	if ($row = sql_fetsel('fonction,inclure,date', 'spip_jobs', 'id_job=' . intval($id_job))
171
		and $res = sql_delete('spip_jobs', 'id_job=' . intval($id_job))
172
	) {
173
		queue_unlink_job($id_job);
174
		// est-ce une tache cron qu'il faut relancer ?
175
		if ($periode = queue_is_cron_job($row['fonction'], $row['inclure'])) {
176
			// relancer avec les nouveaux arguments de temps
177
			include_spip('inc/genie');
178
			// relancer avec la periode prevue
179
			queue_genie_replan_job($row['fonction'], $periode, strtotime($row['date']));
0 ignored issues
show
Bug introduced by
It seems like $periode defined by queue_is_cron_job($row['...ion'], $row['inclure']) on line 175 can also be of type boolean; however, queue_genie_replan_job() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
180
		}
181
		queue_update_next_job_time();
182
	}
183
184
	return $res;
0 ignored issues
show
Bug introduced by
The variable $res 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...
185
}
186
187
/**
188
 * Associer une tache avec un objet
189
 *
190
 * @param int $id_job
191
 *  id de la tache a lier
192
 * @param array $objets
193
 *  peut être un simple tableau array('objet'=>'article','id_objet'=>23)
194
 *  ou un tableau composé de tableaux simples pour lieur plusieurs objets en une fois
195
 */
196
function queue_link_job($id_job, $objets) {
197
	include_spip('base/abstract_sql');
198
199
	if (is_array($objets) and count($objets)) {
200
		if (is_array(reset($objets))) {
201
			foreach ($objets as $k => $o) {
202
				$objets[$k]['id_job'] = $id_job;
203
			}
204
			sql_insertq_multi('spip_jobs_liens', $objets);
205
		} else {
206
			sql_insertq('spip_jobs_liens', array_merge(array('id_job' => $id_job), $objets));
207
		}
208
	}
209
}
210
211
/**
212
 * Dissocier une tache d'un objet
213
 *
214
 * @param int $id_job
215
 *  id de la tache à dissocier
216
 * @return int/bool
0 ignored issues
show
Documentation introduced by
The doc-type int/bool could not be parsed: Unknown type name "int/bool" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
217
 *  resultat du sql_delete
218
 */
219
function queue_unlink_job($id_job) {
220
	return sql_delete("spip_jobs_liens", "id_job=" . intval($id_job));
221
}
222
223
/**
224
 * Lancer une tache decrite par sa ligne SQL
225
 *
226
 * @param array $row
227
 *  describe the job, with field of table spip_jobs
228
 * @return mixed
229
 *  return the result of job
230
 */
231
function queue_start_job($row) {
232
233
	// deserialiser les arguments
234
	$args = unserialize($row['args']);
235
	if ($args === false) {
236
		spip_log('arguments job errones ' . var_export($row, true), 'queue');
237
		$args = array();
238
	}
239
240
	$fonction = $row['fonction'];
241
	if (strlen($inclure = trim($row['inclure']))) {
242
		if (substr($inclure, -1) == '/') { // c'est un chemin pour charger_fonction
243
			$f = charger_fonction($fonction, rtrim($inclure, '/'), false);
244
			if ($f) {
245
				$fonction = $f;
246
			}
247
		} else {
248
			include_spip($inclure);
249
		}
250
	}
251
252
	if (!function_exists($fonction)) {
253
		spip_log("fonction $fonction ($inclure) inexistante " . var_export($row, true), 'queue');
254
255
		return false;
256
	}
257
258
	spip_log("queue [" . $row['id_job'] . "]: $fonction() start", 'queue');
259
	switch (count($args)) {
260
		case 0:
261
			$res = $fonction();
262
			break;
263
		case 1:
264
			$res = $fonction($args[0]);
265
			break;
266
		case 2:
267
			$res = $fonction($args[0], $args[1]);
268
			break;
269
		case 3:
270
			$res = $fonction($args[0], $args[1], $args[2]);
271
			break;
272
		case 4:
273
			$res = $fonction($args[0], $args[1], $args[2], $args[3]);
274
			break;
275
		case 5:
276
			$res = $fonction($args[0], $args[1], $args[2], $args[3], $args[4]);
277
			break;
278
		case 6:
279
			$res = $fonction($args[0], $args[1], $args[2], $args[3], $args[4], $args[5]);
280
			break;
281
		case 7:
282
			$res = $fonction($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6]);
283
			break;
284
		case 8:
285
			$res = $fonction($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7]);
286
			break;
287 View Code Duplication
		case 9:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
288
			$res = $fonction($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], $args[8]);
289
			break;
290 View Code Duplication
		case 10:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
291
			$res = $fonction($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], $args[8],
292
				$args[9]);
293
			break;
294
		default:
295
			# plus lent mais completement generique
296
			$res = call_user_func_array($fonction, $args);
297
	}
298
	spip_log("queue [" . $row['id_job'] . "]: $fonction() end", 'queue');
299
300
	return $res;
301
302
}
303
304
/**
305
 * Exécute les prochaînes tâches cron et replanifie les suivantes
306
 *
307
 * Prend une par une les tâches en attente et les lance, dans la limite
308
 * d'un temps disponible total et d'un nombre maxi de tâches
309
 *
310
 * La date de la prochaine tâche à exécuter est mise à jour
311
 * après chaque chaque tâche finie afin de relancer le scheduler uniquement
312
 * quand c'est nécessaire
313
 *
314
 * @uses queue_sleep_time_to_next_job()
315
 * @uses queue_error_handler() Pour capturer les erreurs en fin de hit
316
 * @uses queue_start_job()
317
 * @uses queue_close_job()
318
 * @uses queue_update_next_job_time()
319
 *
320
 * @param array $force_jobs
0 ignored issues
show
Documentation introduced by
Should the type for parameter $force_jobs not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
321
 *     list of id_job to execute when provided
322
 * @return null|bool
323
 *     - null : pas de tâche à réaliser maintenant
324
 *     - false : pas de connexion SQL
325
 *     - true : une planification a été faite.
326
 */
327
function queue_schedule($force_jobs = null) {
328
	$time = time();
329
	if (defined('_DEBUG_BLOCK_QUEUE')) {
330
		spip_log("_DEBUG_BLOCK_QUEUE : schedule stop", 'jq' . _LOG_DEBUG);
331
332
		return;
333
	}
334
335
	// rien a faire si le prochain job est encore dans le futur
336
	if (queue_sleep_time_to_next_job() > 0 and (!$force_jobs or !count($force_jobs))) {
337
		spip_log("queue_sleep_time_to_next_job", 'jq' . _LOG_DEBUG);
338
339
		return;
340
	}
341
342
	include_spip('base/abstract_sql');
343
	// on ne peut rien faire si pas de connexion SQL
344
	if (!spip_connect()) {
345
		return false;
346
	}
347
348
	if (!defined('_JQ_MAX_JOBS_TIME_TO_EXECUTE')) {
349
		$max_time = ini_get('max_execution_time') / 2;
350
		// valeur conservatrice si on a pas reussi a lire le max_execution_time
351
		if (!$max_time) {
352
			$max_time = 5;
353
		}
354
		define('_JQ_MAX_JOBS_TIME_TO_EXECUTE', min($max_time, 15)); // une valeur maxi en temps.
355
	}
356
	$end_time = $time + _JQ_MAX_JOBS_TIME_TO_EXECUTE;
357
358
	spip_log("JQ schedule $time / $end_time", 'jq' . _LOG_DEBUG);
359
360
	if (!defined('_JQ_MAX_JOBS_EXECUTE')) {
361
		define('_JQ_MAX_JOBS_EXECUTE', 200);
362
	}
363
	$nbj = 0;
364
	// attraper les jobs
365
	// dont la date est passee (echus en attente),
366
	// par ordre :
367
	//	- de priorite
368
	//	- de date
369
	// lorsqu'un job cron n'a pas fini, sa priorite est descendue
370
	// pour qu'il ne bloque pas les autres jobs en attente
371
	if (is_array($force_jobs) and count($force_jobs)) {
372
		$cond = "status=" . intval(_JQ_SCHEDULED) . " AND " . sql_in("id_job", $force_jobs);
373
	} else {
374
		$now = date('Y-m-d H:i:s', $time);
375
		$cond = "status=" . intval(_JQ_SCHEDULED) . " AND date<=" . sql_quote($now);
376
	}
377
378
	register_shutdown_function('queue_error_handler'); // recuperer les erreurs auant que possible
379
	$res = sql_allfetsel('*', 'spip_jobs', $cond, '', 'priorite DESC,date', '0,' . (_JQ_MAX_JOBS_EXECUTE + 1));
380
	do {
381
		if ($row = array_shift($res)) {
382
			$nbj++;
383
			// il faut un verrou, a base de sql_delete
384
			if (sql_delete('spip_jobs', "id_job=" . intval($row['id_job']) . " AND status=" . intval(_JQ_SCHEDULED))) {
385
				#spip_log("JQ schedule job ".$nbj." OK",'jq');
386
				// on reinsert dans la base aussitot avec un status=_JQ_PENDING
387
				$row['status'] = _JQ_PENDING;
388
				$row['date'] = date('Y-m-d H:i:s', $time);
389
				sql_insertq('spip_jobs', $row);
390
391
				// on a la main sur le job :
392
				// l'executer
393
				$result = queue_start_job($row);
394
395
				$time = time();
396
				queue_close_job($row, $time, $result);
397
			}
398
		}
399
		spip_log("JQ schedule job end time " . $time, 'jq' . _LOG_DEBUG);
400
	} while ($nbj < _JQ_MAX_JOBS_EXECUTE and $row and $time < $end_time);
401
	spip_log("JQ schedule end time " . time(), 'jq' . _LOG_DEBUG);
402
403
	if ($row = array_shift($res)) {
0 ignored issues
show
Unused Code introduced by
$row is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
404
		queue_update_next_job_time(0); // on sait qu'il y a encore des jobs a lancer ASAP
405
		spip_log("JQ encore !", 'jq' . _LOG_DEBUG);
406
	} else {
407
		queue_update_next_job_time();
408
	}
409
410
	return true;
411
}
412
413
/**
414
 * Terminer un job au status _JQ_PENDING
415
 *
416
 *  - le reprogrammer si c'est un cron
417
 *  - supprimer ses liens
418
 *  - le detruire en dernier
419
 *
420
 * @uses queue_is_cron_job()
421
 * @uses queue_genie_replan_job()
422
 *
423
 * @param array $row
424
 * @param int $time
425
 * @param int $result
426
 */
427
function queue_close_job(&$row, $time, $result = 0) {
428
	// est-ce une tache cron qu'il faut relancer ?
429
	if ($periode = queue_is_cron_job($row['fonction'], $row['inclure'])) {
430
		// relancer avec les nouveaux arguments de temps
431
		include_spip('inc/genie');
432
		if ($result < 0) // relancer tout de suite, mais en baissant la priorite
433
		{
434
			queue_genie_replan_job($row['fonction'], $periode, 0 - $result, null, $row['priorite'] - 1);
0 ignored issues
show
Bug introduced by
It seems like $periode defined by queue_is_cron_job($row['...ion'], $row['inclure']) on line 429 can also be of type boolean; however, queue_genie_replan_job() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
435
		} else // relancer avec la periode prevue
436
		{
437
			queue_genie_replan_job($row['fonction'], $periode, $time);
0 ignored issues
show
Bug introduced by
It seems like $periode defined by queue_is_cron_job($row['...ion'], $row['inclure']) on line 429 can also be of type boolean; however, queue_genie_replan_job() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
438
		}
439
	}
440
	// purger ses liens eventuels avec des objets
441
	sql_delete("spip_jobs_liens", "id_job=" . intval($row['id_job']));
442
	// supprimer le job fini
443
	sql_delete('spip_jobs', 'id_job=' . intval($row['id_job']));
444
}
445
446
/**
447
 * Récuperer des erreurs autant que possible
448
 * en terminant la gestion de la queue
449
 *
450
 * @uses queue_update_next_job_time()
451
 */
452
function queue_error_handler() {
453
	// se remettre dans le bon dossier, car Apache le change parfois (toujours?)
454
	chdir(_ROOT_CWD);
455
456
	queue_update_next_job_time();
457
}
458
459
460
/**
461
 * Tester si une tâche était une tâche périodique à reprogrammer
462
 *
463
 * @uses taches_generales()
464
 *
465
 * @param string $function
466
 *     Nom de la fonction de tâche
467
 * @param string $inclure
468
 *     Nom de l'inclusion contenant la fonction
469
 * @return bool|int
470
 *     Périodicité de la tâche en secondes, si tâche périodique, sinon false.
471
 */
472
function queue_is_cron_job($function, $inclure) {
473
	static $taches = null;
474
	if (strncmp($inclure, 'genie/', 6) == 0) {
475
		if (is_null($taches)) {
476
			include_spip('inc/genie');
477
			$taches = taches_generales();
478
		}
479
		if (isset($taches[$function])) {
480
			return $taches[$function];
481
		}
482
	}
483
484
	return false;
485
}
486
487
/**
488
 * Mettre a jour la date du prochain job a lancer
489
 * Si une date est fournie (au format time unix)
490
 * on fait simplement un min entre la date deja connue et celle fournie
491
 * (cas de l'ajout simple
492
 * ou cas $next_time=0 car l'on sait qu'il faut revenir ASAP)
493
 *
494
 * @param int $next_time
0 ignored issues
show
Documentation introduced by
Should the type for parameter $next_time not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
495
 *  temps de la tache ajoutee ou 0 pour ASAP
496
 */
497
function queue_update_next_job_time($next_time = null) {
498
	static $nb_jobs_scheduled = null;
499
	static $deja_la = false;
500
	// prendre le min des $next_time que l'on voit passer ici, en cas de reentrance
501
	static $next = null;
502
	// queue_close_job peut etre reentrant ici
503
	if ($deja_la) {
504
		return;
505
	}
506
	$deja_la = true;
507
508
	include_spip('base/abstract_sql');
509
	$time = time();
510
511
	// traiter les jobs morts au combat (_JQ_PENDING depuis plus de 180s)
512
	// pour cause de timeout ou autre erreur fatale
513
	$res = sql_allfetsel("*", "spip_jobs",
514
		"status=" . intval(_JQ_PENDING) . " AND date<" . sql_quote(date('Y-m-d H:i:s', $time - 180)));
515
	if (is_array($res)) {
516
		foreach ($res as $row) {
517
			queue_close_job($row, $time);
518
			spip_log ("queue_close_job car _JQ_PENDING depuis +180s : ".print_r($row,1), "job_mort"._LOG_ERREUR);
519
		}
520
	}
521
522
	// chercher la date du prochain job si pas connu
523
	if (is_null($next) or is_null(queue_sleep_time_to_next_job())) {
524
		$date = sql_getfetsel('date', 'spip_jobs', "status=" . intval(_JQ_SCHEDULED), '', 'date', '0,1');
525
		$next = strtotime($date);
526
	}
527
	if (!is_null($next_time)) {
528
		if (is_null($next) or $next > $next_time) {
529
			$next = $next_time;
530
		}
531
	}
532
533
	if ($next) {
534
		if (is_null($nb_jobs_scheduled)) {
535
			$nb_jobs_scheduled = sql_countsel('spip_jobs',
536
				"status=" . intval(_JQ_SCHEDULED) . " AND date<" . sql_quote(date('Y-m-d H:i:s', $time)));
537
		} elseif ($next <= $time) {
538
			$nb_jobs_scheduled++;
539
		}
540
		// si trop de jobs en attente, on force la purge en fin de hit
541
		// pour assurer le coup
542
		if ($nb_jobs_scheduled > (defined('_JQ_NB_JOBS_OVERFLOW') ? _JQ_NB_JOBS_OVERFLOW : 10000)) {
543
			define('_DIRECT_CRON_FORCE', true);
544
		}
545
	}
546
547
	queue_set_next_job_time($next);
548
	$deja_la = false;
549
}
550
551
552
/**
553
 * Mettre a jour la date de prochain job
554
 *
555
 * @param int $next
556
 */
557
function queue_set_next_job_time($next) {
558
559
	// utiliser le temps courant reel plutot que temps de la requete ici
560
	$time = time();
561
562
	// toujours relire la valeur pour comparer, pour tenir compte des maj concourrantes
563
	// et ne mettre a jour que si il y a un interet a le faire
564
	// permet ausis d'initialiser le nom de fichier a coup sur
565
	$curr_next = $_SERVER['REQUEST_TIME'] + max(0, queue_sleep_time_to_next_job(true));
566
	if (
567
		($curr_next <= $time and $next > $time) // le prochain job est dans le futur mais pas la date planifiee actuelle
568
		or $curr_next > $next // le prochain job est plus tot que la date planifiee actuelle
569
	) {
570
		if (function_exists("cache_set") and defined('_MEMOIZE_MEMORY') and _MEMOIZE_MEMORY) {
571
			cache_set(_JQ_NEXT_JOB_TIME_FILENAME, intval($next));
572
		} else {
573
			ecrire_fichier(_JQ_NEXT_JOB_TIME_FILENAME, intval($next));
574
		}
575
		queue_sleep_time_to_next_job($next);
576
	}
577
578
	return queue_sleep_time_to_next_job();
579
}
580
581
/**
582
 * Déclenche le cron en asynchrone ou retourne le code HTML pour le déclencher
583
 *
584
 * Retourne le HTML à ajouter à la page pour declencher le cron
585
 * ou rien si on a réussi à le lancer en asynchrone.
586
 *
587
 * Un verrou (cron.lock) empêche l'exécution du cron plus d'une fois par seconde.
588
 *
589
 * @uses queue_sleep_time_to_next_job()
590
 * @see  action_cron() L'URL appelée pour déclencher le cron
591
 *
592
 * @return string
593
 */
594
function queue_affichage_cron() {
595
	$texte = "";
596
597
	$time_to_next = queue_sleep_time_to_next_job();
598
	// rien a faire si le prochain job est encore dans le futur
599
	if ($time_to_next > 0 or defined('_DEBUG_BLOCK_QUEUE')) {
600
		return $texte;
601
	}
602
603
	// ne pas relancer si on vient de lancer dans la meme seconde par un hit concurent
604
	if (file_exists($lock = _DIR_TMP . "cron.lock") and !(@filemtime($lock) < $_SERVER['REQUEST_TIME'])) {
605
		return $texte;
606
	}
607
608
	@touch($lock);
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...
609
610
	// il y a des taches en attentes
611
	// si depuis plus de 5min, on essaye de lancer le cron par tous les moyens pour rattraper le coup
612
	// on est sans doute sur un site qui n'autorise pas http sortant ou avec peu de trafic
613
	$urgent = false;
614
	if ($time_to_next < -300) {
615
		$urgent = true;
616
	}
617
618
	$url_cron = generer_url_action('cron', '', false, true);
619
620
	if (!defined('_HTML_BG_CRON_FORCE') or !_HTML_BG_CRON_FORCE) {
621
622
		if (queue_lancer_url_http_async($url_cron) and !$urgent) {
623
			return $texte;
624
		}
625
626
	}
627
628
	// si deja force, on retourne sans rien
629
	if (defined('_DIRECT_CRON_FORCE')) {
630
		return $texte;
631
	}
632
633
	// si c'est un bot
634
	// inutile de faire un appel par image background,
635
	// on force un appel direct en fin de hit
636
	if ((defined('_IS_BOT') and _IS_BOT)) {
637
		define('_DIRECT_CRON_FORCE', true);
638
639
		return $texte;
640
	}
641
642
	// en derniere solution, on insere un appel xhr non bloquant ou une image background dans la page si pas de JS
643
	$url_cron = generer_url_action('cron');
644
	$texte = '<!-- SPIP-CRON -->'
645
	  . "<script>setTimeout(function(){var xo = new XMLHttpRequest();xo.open('GET', '$url_cron', true);xo.send('');},100);</script>"
646
	  . "<noscript><div style=\"background-image: url('$url_cron');\"></div></noscript>";
647
648
	return $texte;
649
}
650
651
/**
652
 * Lancer le cron via un hit http sans attendre le resultat
653
 * @param string $url_cron
654
 * @return bool
655
 */
656
function queue_lancer_url_http_async($url_cron) {
657
	// methode la plus rapide :
658
	// Si fsockopen est possible, on lance le cron via un socket en asynchrone
659
	// si fsockopen echoue (disponibilite serveur, firewall) on essaye pas cURL
660
	// car on a toutes les chances d'echouer pareil mais sans moyen de le savoir
661
	// on passe direct a la methode background-image
662
	if (function_exists('fsockopen')) {
663
		$parts = parse_url($url_cron);
664
665
		switch ($parts['scheme']) {
666
			case 'https':
667
				$scheme = 'ssl://';
668
				$port = 443;
669
				break;
670
			case 'http':
671
			default:
672
				$scheme = '';
673
				$port = 80;
674
		}
675
		$fp = @fsockopen($scheme . $parts['host'],
676
			isset($parts['port']) ? $parts['port'] : $port,
677
			$errno, $errstr, 1);
678
679
		if ($fp) {
680
			$host_sent = $parts['host'];
681
			if (isset($parts['port']) and $parts['port'] !== $port) {
682
				$host_sent .= ':' . $parts['port'];
683
			}
684
			$timeout = 200; // ms
685
			stream_set_timeout($fp, 0, $timeout * 1000);
686
			$query = $parts['path'] . ($parts['query'] ? "?" . $parts['query'] : "");
687
			$out = "GET " . $query . " HTTP/1.1\r\n";
688
			$out .= "Host: " . $host_sent . "\r\n";
689
			$out .= "Connection: Close\r\n\r\n";
690
			fwrite($fp, $out);
691
			spip_timer('read');
692
			$t = 0;
693
			// on lit la reponse si possible pour fermer proprement la connexion
694
			// avec un timeout total de 200ms pour ne pas se bloquer
695
			while (!feof($fp) and $t < $timeout) {
696
				@fgets($fp, 1024);
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...
697
				$t += spip_timer('read', true);
698
				spip_timer('read');
699
			}
700
			fclose($fp);
701
			return true;
702
		}
703
	}
704
	// si fsockopen n'est pas dispo on essaye cURL :
705
	// lancer le cron par un cURL asynchrone si cURL est present
706
	elseif (function_exists("curl_init")) {
707
		//setting the curl parameters.
708
		$ch = curl_init($url_cron);
709
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
710
		// cf bug : http://www.php.net/manual/en/function.curl-setopt.php#104597
711
		curl_setopt($ch, CURLOPT_NOSIGNAL, 1);
712
		// valeur mini pour que la requete soit lancee
713
		curl_setopt($ch, CURLOPT_TIMEOUT_MS, 200);
714
		// lancer
715
		curl_exec($ch);
716
		// fermer
717
		curl_close($ch);
718
		return true;
719
	}
720
721
	return false;
722
}