Completed
Push — master ( 1fc530...29c643 )
by cam
04:11
created

plugin.php ➔ pipeline_precompile()   F

Complexity

Conditions 14
Paths 805

Size

Total Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
nc 805
nop 1
dl 0
loc 59
rs 2.3708
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, Système de publication pour l'internet                           *
5
 *                                                                         *
6
 *  Copyright © avec tendresse depuis 2001                                 *
7
 *  Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James  *
8
 *                                                                         *
9
 *  Ce programme est un logiciel libre distribué sous licence GNU/GPL.     *
10
 *  Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne.   *
11
\***************************************************************************/
12
13
/**
14
 * Gestion de l'activation des plugins
15
 *
16
 * @package SPIP\Core\Plugins
17
 **/
18
19
if (!defined('_ECRIRE_INC_VERSION')) {
20
	return;
21
}
22
23
/** l'adresse du repertoire de telechargement et de decompactage des plugins */
24
if (!defined('_DIR_PLUGINS_AUTO')) {
25
	define('_DIR_PLUGINS_AUTO', _DIR_PLUGINS . 'auto/');
26
}
27
28
#include_spip('inc/texte'); // ????? Appelle public/parametrer trop tot avant la reconstruction du chemin des plugins.
29
include_spip('plugins/installer');
30
31
/**
32
 * Retourne la description de chaque plugin présent dans un répertoire 
33
 *
34
 * Lecture des sous repertoire plugin existants
35
 * 
36
 * @example
37
 *     - `liste_plugin_files()`
38
 *     - `liste_plugin_files(_DIR_PLUGINS_DIST)`
39
 *     - `liste_plugin_files(_DIR_PLUGINS_SUPPL)`
40
 * 
41
 * @uses fast_find_plugin_dirs()
42
 * @uses plugins_get_infos_dist()
43
 * 
44
 * @param string|null $dir_plugins
45
 *     - string : Chemin (relatif à la racine du site) du répertoire à analyser. 
46
 *     - null : utilise le chemin `_DIR_PLUGINS`.
47
 * @return array
48
**/
49
function liste_plugin_files($dir_plugins = null) {
50
	static $plugin_files = array();
51
	if (is_null($dir_plugins)) {
52
		$dir_plugins = _DIR_PLUGINS;
53
	}
54
	if (!isset($plugin_files[$dir_plugins])
55
		or count($plugin_files[$dir_plugins]) == 0
56
	) {
57
		$plugin_files[$dir_plugins] = array();
58
		foreach (fast_find_plugin_dirs($dir_plugins) as $plugin) {
59
			$plugin_files[$dir_plugins][] = substr($plugin, strlen($dir_plugins));
60
		}
61
62
		sort($plugin_files[$dir_plugins]);
63
		// et on lit le XML de tous les plugins pour le mettre en cache
64
		// et en profiter pour nettoyer ceux qui n'existent plus du cache
65
		$get_infos = charger_fonction('get_infos', 'plugins');
66
		$get_infos($plugin_files[$dir_plugins], false, $dir_plugins, true);
67
	}
68
69
	return $plugin_files[$dir_plugins];
70
}
71
72
/**
73
 * Recherche rapide des répertoires de plugins contenus dans un répertoire
74
 *
75
 * @uses is_plugin_dir()
76
 * 
77
 * @param string $dir
78
 *     Chemin du répertoire dont on souhaite retourner les sous répertoires
79
 * @param int $max_prof
80
 *     Profondeur maximale des sous répertoires
81
 * @return array
82
 *     Liste complète des répeertoires
83
**/
84
function fast_find_plugin_dirs($dir, $max_prof = 100) {
85
	$fichiers = array();
86
	// revenir au repertoire racine si on a recu dossier/truc
87
	// pour regarder dossier/truc/ ne pas oublier le / final
88
	$dir = preg_replace(',/[^/]*$,', '', $dir);
89
	if ($dir == '') {
90
		$dir = '.';
91
	}
92
93
	if (!is_dir($dir)) {
94
		return $fichiers;
95
	}
96
	if (is_plugin_dir($dir, '')) {
97
		$fichiers[] = $dir;
98
99
		return $fichiers;
100
	}
101
	if ($max_prof <= 0) {
102
		return $fichiers;
103
	}
104
105
	$subdirs = array();
106
	if (@is_dir($dir) and is_readable($dir) and $d = opendir($dir)) {
107 View Code Duplication
		while (($f = readdir($d)) !== false) {
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...
108
			if ($f[0] != '.' # ignorer . .. .svn etc
109
				and $f != 'CVS'
110
				and is_dir($f = "$dir/$f")
111
			) {
112
				$subdirs[] = $f;
113
			}
114
		}
115
		closedir($d);
116
	}
117
118
	foreach ($subdirs as $d) {
119
		$fichiers = array_merge($fichiers, fast_find_plugin_dirs("$d/", $max_prof - 1));
120
	}
121
122
	return $fichiers;
123
}
124
125
/**
126
 * Indique si un répertoire (ou plusieurs) est la racine d'un plugin SPIP
127
 *
128
 * Vérifie le ou les chemins relatifs transmis pour vérifier qu'ils contiennent
129
 * un `paquet.xml`. Les chemins valides sont retournés.
130
 * 
131
 * @param string|string[] $dir
132
 *     Chemin (relatif à `$dir_plugins`), ou liste de chemins à tester
133
 * @param string|null $dir_plugins
134
 *     - string : Chemin de répertoire (relatif à la `_DIR_RACINE`), départ des chemin(s) à tester
135
 *     - null (par défaut) : utilise le chemin `_DIR_PLUGINS`
136
 * @return string|string[]
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string? Also, consider making the array more specific, something like array<String>, or 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.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
137
 *     - string : Le chemin accepté (c'était un plugin)
138
 *     - '' : ce n'était pas un chemin valide
139
 *     - array : Ensemble des chemins acceptés (si `$dir` était array)
140
**/
141
function is_plugin_dir($dir, $dir_plugins = null) {
142
143
	if (is_array($dir)) {
144
		foreach ($dir as $k => $d) {
145
			if (!is_plugin_dir($d, $dir_plugins)) {
146
				unset($dir[$k]);
147
			}
148
		}
149
150
		return $dir;
151
	}
152
	if (is_null($dir_plugins)) {
153
		$dir_plugins = _DIR_PLUGINS;
154
	}
155
	$search = array("$dir_plugins$dir/paquet.xml");
156
157
	foreach ($search as $s) {
158
		if (file_exists($s)) {
159
			return $dir;
160
		}
161
	}
162
163
	return '';
164
}
165
166
/** Regexp d'extraction des informations d'un intervalle de compatibilité */
167
define('_EXTRAIRE_INTERVALLE', ',^[\[\(\]]([0-9.a-zRC\s\-]*)[;]([0-9.a-zRC\s\-\*]*)[\]\)\[]$,');
168
169
/**
170
 * Teste si le numéro de version d'un plugin est dans un intervalle donné.
171
 *
172
 * Cette fonction peut être volontairement trompée (phase de développement) :
173
 * voir commentaire infra sur l'utilisation de la constante _DEV_VERSION_SPIP_COMPAT
174
 *
175
 * @uses spip_version_compare()
176
 * 
177
 * @param string $intervalle
178
 *    Un intervalle entre 2 versions. ex: [2.0.0-dev;2.1.*]
179
 * @param string $version
180
 *    Un numéro de version. ex: 3.1.99]
181
 * @param string $avec_quoi
182
 *    Ce avec quoi est testée la compatibilité. par défaut ('')
183
 *    avec un plugin (cas des 'necessite'), parfois ('spip')
184
 *    avec SPIP.
185
 * @return bool
186
 *    True si dans l'intervalle, false sinon.
187
 **/
188
function plugin_version_compatible($intervalle, $version, $avec_quoi = '') {
189
190
	if (!strlen($intervalle)) {
191
		return true;
192
	}
193
	if (!preg_match(_EXTRAIRE_INTERVALLE, $intervalle, $regs)) {
194
		return false;
195
	}
196
	// Extraction des bornes et traitement de * pour la borne sup :
197
	// -- on autorise uniquement les ecritures 3.0.*, 3.*
198
	$minimum = $regs[1];
199
	$maximum = $regs[2];
200
201
	//  si une version SPIP de compatibilité a été définie (dans
202
	//  mes_options.php, sous la forme : define('_DEV_VERSION_SPIP_COMPAT', '3.1.0');
203
	//  on l'utilise (phase de dev, de test...) mais *que* en cas de comparaison
204
	//  avec la version de SPIP (ne nuit donc pas aux tests de necessite
205
	//  entre plugins)
206
	if (defined('_DEV_VERSION_SPIP_COMPAT') and $avec_quoi == 'spip' and $version !== _DEV_VERSION_SPIP_COMPAT) {
207
		if (plugin_version_compatible($intervalle, _DEV_VERSION_SPIP_COMPAT, $avec_quoi)) {
208
			return true;
209
		}
210
		// si pas de compatibilite avec _DEV_VERSION_SPIP_COMPAT, on essaye quand meme avec la vrai version
211
		// cas du plugin qui n'est compatible qu'avec cette nouvelle version
212
	}
213
214
	$minimum_inc = $intervalle[0] == "[";
215
	$maximum_inc = substr($intervalle, -1) == "]";
216
217 View Code Duplication
	if (strlen($minimum)) {
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...
218
		if ($minimum_inc and spip_version_compare($version, $minimum, '<')) {
219
			return false;
220
		}
221
		if (!$minimum_inc and spip_version_compare($version, $minimum, '<=')) {
222
			return false;
223
		}
224
	}
225 View Code Duplication
	if (strlen($maximum)) {
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...
226
		if ($maximum_inc and spip_version_compare($version, $maximum, '>')) {
227
			return false;
228
		}
229
		if (!$maximum_inc and spip_version_compare($version, $maximum, '>=')) {
230
			return false;
231
		}
232
	}
233
234
	return true;
235
}
236
237
/**
238
 * Construire la liste des infos strictement necessaires aux plugins à activer
239
 * afin de les mémoriser dans une meta pas trop grosse
240
 *
241
 * @uses liste_plugin_files()
242
 * @uses plugins_get_infos_dist()
243
 * @uses plugin_valide_resume()
244
 * @uses plugin_fixer_procure()
245
 * 
246
 * @param array $liste_plug
247
 * @param bool $force
248
 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
249
 */
250
function liste_plugin_valides($liste_plug, $force = false) {
251
	$liste_ext = liste_plugin_files(_DIR_PLUGINS_DIST);
252
	$get_infos = charger_fonction('get_infos', 'plugins');
253
	$infos = array(
254
		// lister les extensions qui sont automatiquement actives
255
		'_DIR_PLUGINS_DIST' => $get_infos($liste_ext, $force, _DIR_PLUGINS_DIST),
256
		'_DIR_PLUGINS' => $get_infos($liste_plug, $force, _DIR_PLUGINS)
257
	);
258
259
	// creer une premiere liste non ordonnee mais qui ne retient
260
	// que les plugins valides, et dans leur derniere version en cas de doublon
261
	$infos['_DIR_RESTREINT'][''] = $get_infos('./', $force, _DIR_RESTREINT);
262
	$infos['_DIR_RESTREINT']['SPIP']['version'] = $GLOBALS['spip_version_branche'];
263
	$infos['_DIR_RESTREINT']['SPIP']['chemin'] = array();
264
	$liste_non_classee = array(
265
		'SPIP' => array(
266
			'nom' => 'SPIP',
267
			'etat' => 'stable',
268
			'version' => $GLOBALS['spip_version_branche'],
269
			'dir_type' => '_DIR_RESTREINT',
270
			'dir' => '',
271
		)
272
	);
273
274
	$invalides = array();
275
	foreach ($liste_ext as $plug) {
276
		if (isset($infos['_DIR_PLUGINS_DIST'][$plug])) {
277
			plugin_valide_resume($liste_non_classee, $plug, $infos, '_DIR_PLUGINS_DIST');
278
		}
279
	}
280 View Code Duplication
	foreach ($liste_plug as $plug) {
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...
281
		if (isset($infos['_DIR_PLUGINS'][$plug])) {
282
			$r = plugin_valide_resume($liste_non_classee, $plug, $infos, '_DIR_PLUGINS');
283
			if (is_array($r)) {
284
				$invalides = array_merge($invalides, $r);
285
			}
286
		}
287
	}
288
289
	if (defined('_DIR_PLUGINS_SUPPL') and _DIR_PLUGINS_SUPPL) {
290
		$infos['_DIR_PLUGINS_SUPPL'] = $get_infos($liste_plug, false, _DIR_PLUGINS_SUPPL);
291 View Code Duplication
		foreach ($liste_plug as $plug) {
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...
292
			if (isset($infos['_DIR_PLUGINS_SUPPL'][$plug])) {
293
				$r = plugin_valide_resume($liste_non_classee, $plug, $infos, '_DIR_PLUGINS_SUPPL');
294
				if (is_array($r)) {
295
					$invalides = array_merge($invalides, $r);
296
				}
297
			}
298
		}
299
	}
300
301
	plugin_fixer_procure($liste_non_classee, $infos);
302
303
	// les plugins qui sont dans $liste_non_classee ne sont pas invalides (on a trouve un autre version valide)
304
	$invalides = array_diff_key($invalides, $liste_non_classee);
305
306
	return array($infos, $liste_non_classee, $invalides);
307
}
308
309
/**
310
 * Ne retenir un plugin que s'il est valide
311
 * et dans leur plus recente version compatible
312
 * avec la version presente de SPIP
313
 *
314
 * @uses plugin_version_compatible()
315
 * @uses spip_version_compare()
316
 * 
317
 * @param array $liste
318
 * @param string $plug
319
 * @param array $infos
320
 * @param string $dir_type
321
 * @return string|array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,array>|array<string,array>|string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
322
 *   string prefixe dans $liste si on a accepte le plugin
323
 *   array description short si on ne le retient pas (pour memorisation dans une table des erreurs)
324
 */
325
function plugin_valide_resume(&$liste, $plug, $infos, $dir_type) {
326
	$i = $infos[$dir_type][$plug];
327
	$p = strtoupper($i['prefix']);
328
	$short_desc = array(
329
		'nom' => $i['nom'],
330
		'etat' => $i['etat'],
331
		'version' => $i['version'],
332
		'dir' => $plug,
333
		'dir_type' => $dir_type
334
	);
335
	if (isset($i['erreur']) and $i['erreur']) {
336
		$short_desc['erreur'] = $i['erreur'];
337
		return array($p=>$short_desc);
338
	}
339
	if (!plugin_version_compatible($i['compatibilite'], $GLOBALS['spip_version_branche'], 'spip')) {
340
		return array($p=>$short_desc);
341
	}
342
	if (!isset($liste[$p])
343
		or spip_version_compare($i['version'], $liste[$p]['version'], '>')
344
	) {
345
		$liste[$p] = $short_desc;
346
	}
347
	// ok le plugin etait deja dans la liste ou on a choisi une version plus recente
348
	return $p;
349
}
350
351
/**
352
 * Compléter la liste des plugins avec les éventuels procure
353
 *
354
 * les balises `<procure>` sont considerées comme des plugins proposés,
355
 * mais surchargeables (on peut activer un plugin qui procure ça pour l'améliorer,
356
 * donc avec le même prefixe, qui sera pris en compte si il a une version plus grande)
357
 *
358
 * @uses spip_version_compare()
359
 * 
360
 * @param array $liste
361
 * @param array $infos
362
 */
363
function plugin_fixer_procure(&$liste, &$infos) {
364
	foreach ($liste as $p => $resume) {
365
		$i = $infos[$resume['dir_type']][$resume['dir']];
366
		if (isset($i['procure']) and $i['procure']) {
367
			foreach ($i['procure'] as $procure) {
368
				$p = strtoupper($procure['nom']);
369
				$dir = $resume['dir'];
370
				if ($dir) {
371
					$dir .= "/";
372
				}
373
				$dir .= "procure:" . $procure['nom'];
374
375
				$procure['etat'] = '?';
376
				$procure['dir_type'] = $resume['dir_type'];
377
				$procure['dir'] = $dir;
378
379
				// si ce plugin n'est pas deja procure, ou dans une version plus ancienne
380
				// on ajoute cette version a la liste
381
				if (!isset($liste[$p])
382
					or spip_version_compare($procure['version'], $liste[$p]['version'], '>')
383
				) {
384
					$liste[$p] = $procure;
385
386
					// on fournit une information minimale pour ne pas perturber la compilation
387
					$infos[$resume['dir_type']][$dir] = array(
388
						'prefix' => $procure['nom'],
389
						'nom' => $procure['nom'],
390
						'etat' => $procure['etat'],
391
						'version' => $procure['version'],
392
						'chemin' => array(),
393
						'necessite' => array(),
394
						'utilise' => array(),
395
						'lib' => array(),
396
						'menu' => array(),
397
						'onglet' => array(),
398
						'procure' => array(),
399
					);
400
				}
401
			}
402
		}
403
	}
404
}
405
406
/**
407
 * Extrait les chemins d'une liste de plugin
408
 * 
409
 * Sélectionne au passage ceux qui sont dans `$dir_plugins` uniquement
410
 * si valeur non vide
411
 *
412
 * @param array $liste
413
 * @param string $dir_plugins
414
 * @return array
415
 */
416
function liste_chemin_plugin($liste, $dir_plugins = _DIR_PLUGINS) {
417
	foreach ($liste as $prefix => $infos) {
418
		if (!$dir_plugins
419
			or (
420
				defined($infos['dir_type'])
421
				and constant($infos['dir_type']) == $dir_plugins)
422
		) {
423
			$liste[$prefix] = $infos['dir'];
424
		} else {
425
			unset($liste[$prefix]);
426
		}
427
	}
428
429
	return $liste;
430
}
431
432
/**
433
 * Liste les chemins vers les plugins actifs du dossier fourni en argument
434
 * a partir d'une liste d'elelements construits par plugin_valide_resume
435
 *
436
 * @uses liste_plugin_actifs()
437
 * @uses liste_chemin_plugin()
438
 * 
439
 * @param string $dir_plugins
440
 *     Chemin du répertoire de plugins
441
 * @return array
442
 */
443
function liste_chemin_plugin_actifs($dir_plugins = _DIR_PLUGINS) {
444
	include_spip('plugins/installer');
445
446
	return liste_chemin_plugin(liste_plugin_actifs(), $dir_plugins);
447
}
448
449
/**
450
 * Trier les plugins en vériant leur dépendances (qui doivent être présentes)
451
 *
452
 * Pour tester "utilise", il faut connaître tous les plugins
453
 * qui seront forcément absents à la fin,
454
 * car absent de la liste des plugins actifs.
455
 * 
456
 * Il faut donc construire une liste ordonnée.
457
 * 
458
 * Cette fonction détecte des dépendances circulaires,
459
 * avec un doute sur un "utilise" qu'on peut ignorer.
460
 * Mais ne pas insérer silencieusement et risquer un bug sournois latent
461
 * 
462
 * @uses plugin_version_compatible()
463
 * 
464
 * @param array $infos 
465
 *     Répertoire (plugins, plugins-dist, ...) => Couples (prefixes => infos completes) des plugins qu'ils contiennent
466
 * @param array $liste_non_classee
467
 *     Couples (prefixe => description) des plugins qu'on souhaite utiliser
468
 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
469
 *     Tableau de 3 éléments :
470
 *     - $liste : couples (prefixes => description) des plugins valides
471
 *     - $ordre : couples (prefixes => infos completes) des plugins triés
472
 *                (les plugins nécessités avant les plugins qui les utilisent)
473
 *     - $liste_non_classee : couples (prefixes => description) des plugins 
474
 *                qui n'ont pas satisfait leurs dépendances
475
**/
476
function plugin_trier($infos, $liste_non_classee) {
477
	$toute_la_liste = $liste_non_classee;
478
	$liste = $ordre = array();
479
	$count = 0;
480
481
	while ($c = count($liste_non_classee) and $c != $count) { // tant qu'il reste des plugins a classer, et qu'on ne stagne pas
482
		#echo "tour::";var_dump($liste_non_classee);
483
		$count = $c;
484
		foreach ($liste_non_classee as $p => $resume) {
485
			$plug = $resume['dir'];
486
			$dir_type = $resume['dir_type'];
487
			$info1 = $infos[$dir_type][$plug];
488
			// si des plugins sont necessaires,
489
			// on ne peut inserer qu'apres eux
490 View Code Duplication
			foreach ($info1['necessite'] as $need) {
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...
491
				$nom = strtoupper($need['nom']);
492
				$compat = isset($need['compatibilite']) ? $need['compatibilite'] : '';
493
				if (!isset($liste[$nom]) or !plugin_version_compatible($compat, $liste[$nom]['version'])) {
494
					$info1 = false;
495
					break;
496
				}
497
			}
498
			if (!$info1) {
499
				continue;
500
			}
501
			// idem si des plugins sont utiles,
502
			// sauf si ils sont de toute facon absents de la liste
503 View Code Duplication
			foreach ($info1['utilise'] as $need) {
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...
504
				$nom = strtoupper($need['nom']);
505
				$compat = isset($need['compatibilite']) ? $need['compatibilite'] : '';
506
				if (isset($toute_la_liste[$nom])) {
507
					if (!isset($liste[$nom]) or
508
						!plugin_version_compatible($compat, $liste[$nom]['version'])
509
					) {
510
						$info1 = false;
511
						break;
512
					}
513
				}
514
			}
515
			if ($info1) {
516
				$ordre[$p] = $info1;
517
				$liste[$p] = $liste_non_classee[$p];
518
				unset($liste_non_classee[$p]);
519
			}
520
		}
521
	}
522
523
	return array($liste, $ordre, $liste_non_classee);
524
}
525
526
/**
527
 * Collecte les erreurs de dépendances des plugins dans la meta `plugin_erreur_activation`
528
 *
529
 * @uses plugin_necessite()
530
 * @uses plugin_controler_lib()
531
 * 
532
 * @param array $liste_non_classee
533
 *     Couples (prefixe => description) des plugins en erreur
534
 * @param array $liste
535
 *     Couples (prefixe => description) des plugins qu'on souhaite utiliser
536
 * @param array $infos 
537
 *     Répertoire (plugins, plugins-dist, ...) => Couples (prefixes => infos completes) des plugins qu'ils contiennent
538
**/
539
function plugins_erreurs($liste_non_classee, $liste, $infos, $msg = array()) {
540
	static $erreurs = array();
541
542
	if (!is_array($liste)) {
543
		$liste = array();
544
	}
545
546
	// les plugins en erreur ne sont pas actifs ; ils ne doivent pas être dans la liste
547
	$liste = array_diff_key($liste, $liste_non_classee);
548
549
	foreach ($liste_non_classee as $p => $resume) {
550
		$dir_type = $resume['dir_type'];
551
		$plug = $resume['dir'];
552
		$k = $infos[$dir_type][$plug];
553
554
		$plug = constant($dir_type) . $plug;
555
		if (!isset($msg[$p])) {
556
			if (isset($resume['erreur']) and $resume['erreur']) {
557
				$msg[$p] = array($resume['erreur']);
558
			}
559
			elseif (!plugin_version_compatible($k['compatibilite'], $GLOBALS['spip_version_branche'], 'spip')) {
560
				$msg[$p] = array(plugin_message_incompatibilite($k['compatibilite'], $GLOBALS['spip_version_branche'], 'SPIP', 'necessite'));
561
			}
562
			elseif (!$msg[$p] = plugin_necessite($k['necessite'], $liste, 'necessite')) {
563
				$msg[$p] = plugin_necessite($k['utilise'], $liste, 'utilise');
564
			}
565
		} else {
566
			foreach ($msg[$p] as $c => $l) {
567
				$msg[$p][$c] = plugin_controler_lib($l['nom'], $l['lien']);
568
			}
569
		}
570
		$erreurs[$plug] = $msg[$p];
571
	}
572
573
	ecrire_meta('plugin_erreur_activation', serialize($erreurs));
574
}
575
576
/**
577
 * Retourne les erreurs d'activation des plugins, au format html ou brut
578
 *
579
 * @param bool $raw
580
 *     - true : pour obtenir le tableau brut des erreurs
581
 *     - false : Code HTML
582
 * @param bool $raz
583
 *     - true pour effacer la meta qui stocke les erreurs.
584
 * @return string|array
585
 *     - Liste des erreurs ou code HTML des erreurs
586
**/
587
function plugin_donne_erreurs($raw = false, $raz = true) {
588
	if (!isset($GLOBALS['meta']['plugin_erreur_activation'])) {
589
		return $raw ? array() : '';
590
	}
591
	$list = @unserialize($GLOBALS['meta']['plugin_erreur_activation']);
592
	// Compat ancienne version
593
	if (!$list) {
594
		$list = $raw ? array() : $GLOBALS['meta']['plugin_erreur_activation'];
595
	} elseif (!$raw) {
596
		foreach ($list as $plug => $msg) {
597
			$list[$plug] = "<li>" . _T('plugin_impossible_activer', array('plugin' => $plug))
598
				. "<ul><li>" . implode("</li><li>", $msg) . "</li></ul></li>";
599
		}
600
		$list = "<ul>" . join("\n", $list) . "</ul>";
601
	}
602
	if ($raz) {
603
		effacer_meta('plugin_erreur_activation');
604
	}
605
606
	return $list;
607
}
608
609
/**
610
 * Teste des dépendances
611
 * 
612
 * Et vérifie que chaque dépendance est présente
613
 * dans la liste de plugins donnée
614
 *
615
 * @uses plugin_controler_necessite()
616
 * 
617
 * @param array $n
618
 *    Tableau de dépendances dont on souhaite vérifier leur présence
619
 * @param array $liste
620
 *    Tableau des plugins présents
621
 * @return array
622
 *    Tableau des messages d'erreurs reçus. Il sera vide si tout va bien.
623
 *
624
 **/
625
function plugin_necessite($n, $liste, $balise = 'necessite') {
626
	$msg = array();
627
	foreach ($n as $need) {
628
		$id = strtoupper($need['nom']);
629
		$r = plugin_controler_necessite(
630
			$liste, 
631
			$id, 
632
			isset($need['compatibilite']) ? $need['compatibilite'] : '', 
633
			$balise
634
		);
635
		if ($r) {
636
			$msg[] = $r;
637
		}
638
	}
639
640
	return $msg;
641
}
642
643
/**
644
 * Vérifie qu'une dépendance (plugin) est bien présente.
645
 *
646
 * @uses plugin_version_compatible()
647
 * @uses plugin_message_incompatibilite()
648
 * 
649
 * @param $liste
650
 *    Liste de description des plugins
651
 * @param $nom
652
 *    Le plugin donc on cherche la presence
653
 * @param $intervalle
654
 *    L'éventuelle intervalle de compatibilité de la dépendance. ex: [1.1.0;]
655
 * @param $balise
656
 *    Permet de définir si on teste un utilise ou un nécessite
657
 * @return string.
0 ignored issues
show
Documentation introduced by
The doc-type string. could not be parsed: Unknown type name "string." 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...
658
 *    Vide si ok,
659
 *    Message d'erreur lorsque la dépendance est absente.
660
 **/
661
function plugin_controler_necessite($liste, $nom, $intervalle, $balise) {
662
	if (isset($liste[$nom]) and plugin_version_compatible($intervalle, $liste[$nom]['version'])) {
663
		return '';
664
	}
665
666
	return plugin_message_incompatibilite(
667
		$intervalle, 
668
		(isset($liste[$nom]) ? $liste[$nom]['version'] : ""), 
669
		$nom, 
670
		$balise
671
	);
672
}
673
674
/**
675
 * @param string $intervalle
676
 *     L'éventuelle intervalle de compatibilité de la dépendance. ex: [1.1.0;]
677
 * @param string $version
678
 *     La version en cours active pour le plugin demandé (ou php ou extension php demandée)
679
 * @param string $nom
680
 *     Le plugin (ou php ou extension php) qui est absent
681
 * @param string $balise
682
 *     Le type de balise utilisé (necessite ou utilise)
683
 * @return string
684
 *     Le message d'erreur.
685
 */
686
function plugin_message_incompatibilite($intervalle, $version, $nom, $balise) {
687
688
	// prendre en compte les erreurs de dépendances à PHP
689
	// ou à une extension PHP avec des messages d'erreurs dédiés.
690
	$type = 'plugin';
691
	if ($nom === 'SPIP') {
692
		$type = 'spip';
693
	} elseif ($nom === 'PHP') {
694
		$type = 'php';
695
	} elseif (strncmp($nom, 'PHP:', 4) === 0) {
696
		$type = 'extension_php';
697
		list(,$nom) = explode(':', $nom, 2);
698
	}
699
700
	if (preg_match(_EXTRAIRE_INTERVALLE, $intervalle, $regs)) {
701
		$minimum = $regs[1];
702
		$maximum = $regs[2];
703
704
		$minimum_inclus = $intervalle[0] == "[";
705
		$maximum_inclus = substr($intervalle, -1) == "]";
706
707 View Code Duplication
		if (strlen($minimum)) {
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...
708
			if ($minimum_inclus and spip_version_compare($version, $minimum, '<')) {
709
				return _T("plugin_${balise}_${type}", array(
710
					'plugin' => $nom,
711
					'version' => ' &ge; ' . $minimum
712
				));
713
			}
714
			if (!$minimum_inclus and spip_version_compare($version, $minimum, '<=')) {
715
				return _T("plugin_${balise}_${type}", array(
716
					'plugin' => $nom,
717
					'version' => ' &gt; ' . $minimum
718
				));
719
			}
720
		}
721
722 View Code Duplication
		if (strlen($maximum)) {
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...
723
			if ($maximum_inclus and spip_version_compare($version, $maximum, '>')) {
724
				return _T("plugin_${balise}_${type}", array(
725
					'plugin' => $nom,
726
					'version' => ' &le; ' . $maximum
727
				));
728
			}
729
			if (!$maximum_inclus and spip_version_compare($version, $maximum, '>=')) {
730
				return _T("plugin_${balise}_plugin", array(
731
					'plugin' => $nom,
732
					'version' => ' &lt; ' . $maximum
733
				));
734
			}
735
		}
736
	}
737
738
	// note : il ne peut pas y avoir d'erreur sur
739
	// - un 'utilise' sans version.
740
	// - un 'php' sans version.
741
	return _T("plugin_necessite_${type}_sans_version", array('plugin' => $nom));
742
}
743
744
745
function plugin_controler_lib($lib, $url) {
746
	/* Feature sortie du core, voir STP
747
	 * if ($url) {
748
		include_spip('inc/charger_plugin');
749
		$url = '<br />'	. bouton_telechargement_plugin($url, 'lib');
750
	}*/
751
	return _T('plugin_necessite_lib', array('lib' => $lib)) . " <a href='$url'>$url</a>";
752
}
753
754
755
/**
756
 * Calcule la liste des plugins actifs et recompile les fichiers caches
757
 * qui leurs sont relatifs
758
 *
759
 * @uses ecrire_plugin_actifs()
760
 *
761
 * @param bool $pipe_recherche ?
762
 * @return bool
763
 *     true si il y a eu des modifications sur la liste des plugins actifs, false sinon
764
 **/
765
function actualise_plugins_actifs($pipe_recherche = false) {
766
	return ecrire_plugin_actifs('', $pipe_recherche, 'force');
767
}
768
769
770
/**
771
 * Calcule ou modifie la liste des plugins actifs et recompile les fichiers caches
772
 * qui leurs sont relatifs
773
 *
774
 * @note
775
 *   Les  ecrire_meta() doivent en principe aussi initialiser la valeur a vide
776
 *   si elle n'existe pas risque de pb en php5 a cause du typage ou de null
777
 *   (verifier dans la doc php)
778
 *
779
 * @param string|string[] $plugin
780
 *     Plugin ou plugins concernés (leur chemin depuis le répertoire plugins)
781
 * @param bool $pipe_recherche
782
 *     ?
783
 * @param string $operation
784
 *     - raz : recalcule tout
785
 *     - ajoute : ajoute le plugin indiqué à la liste des plugins actifs
786
 *     - enleve : enleve le plugin indiqué de la liste des plugins actifs
787
 *     - force  : ?
788
 * @return bool
789
 *     true si il y a eu des modifications sur la liste des plugins actifs, false sinon
790
 **/
791
function ecrire_plugin_actifs($plugin, $pipe_recherche = false, $operation = 'raz') {
792
793
	// creer le repertoire cache/ si necessaire ! (installation notamment)
794
	$cache = sous_repertoire(_DIR_CACHE, '', false, true);
795
796
	// Si on n'a ni cache accessible, ni connexion SQL, on ne peut pas faire grand chose encore.
797
	if (!$cache and !spip_connect()) {
798
		return false;
799
	}
800
801
	if ($operation != 'raz') {
802
		$plugin_valides = liste_chemin_plugin_actifs();
803
		$plugin_valides = is_plugin_dir($plugin_valides);
804
		if (defined('_DIR_PLUGINS_SUPPL') && _DIR_PLUGINS_SUPPL) {
805
			$plugin_valides_supp = liste_chemin_plugin_actifs(_DIR_PLUGINS_SUPPL);
806
			$plugin_valides_supp = is_plugin_dir($plugin_valides_supp, _DIR_PLUGINS_SUPPL);
807
			$plugin_valides = array_merge($plugin_valides, $plugin_valides_supp);
808
		}
809
		// si des plugins sont en attentes (coches mais impossible a activer)
810
		// on les reinjecte ici
811
		if (isset($GLOBALS['meta']['plugin_attente'])
812
			and $a = unserialize($GLOBALS['meta']['plugin_attente'])
813
		) {
814
			$plugin_valides = $plugin_valides + liste_chemin_plugin($a);
815
		}
816
817
		if ($operation == 'ajoute') {
818
			$plugin = array_merge($plugin_valides, $plugin);
819
		} elseif ($operation == 'enleve') {
820
			$plugin = array_diff($plugin_valides, $plugin);
821
		} else {
822
			$plugin = $plugin_valides;
823
		}
824
	}
825
	$actifs_avant = isset($GLOBALS['meta']['plugin']) ? $GLOBALS['meta']['plugin'] : '';
826
827
	// si une fonction de gestion de dependances existe, l'appeler ici
828
	if ($ajouter_dependances = charger_fonction("ajouter_dependances", "plugins", true)) {
829
		$plugin = $ajouter_dependances($plugin);
830
	}
831
832
	// recharger le xml des plugins a activer
833
	// on force le reload ici, meme si le fichier xml n'a pas change
834
	// pour ne pas rater l'ajout ou la suppression d'un fichier fonctions/options/administrations
835
	// pourra etre evite quand on ne supportera plus les plugin.xml
836
	// en deplacant la detection de ces fichiers dans la compilation ci dessous
837
	list($infos, $liste, $invalides) = liste_plugin_valides($plugin, true);
838
	// trouver l'ordre d'activation
839
	list($plugin_valides, $ordre, $reste) = plugin_trier($infos, $liste);
840
	if ($invalides or $reste) {
841
		plugins_erreurs(array_merge($invalides, $reste), $liste, $infos);
842
	}
843
844
	// Ignorer les plugins necessitant une lib absente
845
	// et preparer la meta d'entete Http
846
	$err = $msg = $header = array();
847
	foreach ($plugin_valides as $p => $resume) {
848
		// Les headers ne doivent pas indiquer les versions des extensions PHP, ni la version PHP
849
		if (0 !== strpos($p, 'PHP:') and $p !== 'PHP') {
850
			$header[] = $p . ($resume['version'] ? "(" . $resume['version'] . ")" : "");
851
		}
852
		if ($resume['dir']) {
853
			foreach ($infos[$resume['dir_type']][$resume['dir']]['lib'] as $l) {
854
				if (!find_in_path($l['nom'], 'lib/')) {
855
					$err[$p] = $resume;
856
					$msg[$p][] = $l;
857
					unset($plugin_valides[$p]);
858
				}
859
			}
860
		}
861
	}
862
	if ($err) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $err of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
863
		plugins_erreurs($err, '', $infos, $msg);
864
	}
865
866
	if (isset($GLOBALS['meta']['message_crash_plugins'])) {
867
		effacer_meta('message_crash_plugins');
868
	}
869
	ecrire_meta('plugin', serialize($plugin_valides));
870
	$liste = array_diff_key($liste, $plugin_valides);
871
	ecrire_meta('plugin_attente', serialize($liste));
872
	$header = strtolower(implode(",", $header));
873
	if (!isset($GLOBALS['spip_header_silencieux']) or !$GLOBALS['spip_header_silencieux']) {
874
		ecrire_fichier(_DIR_VAR . "config.txt",
875
			(defined('_HEADER_COMPOSED_BY') ? _HEADER_COMPOSED_BY : "Composed-By: SPIP") . ' ' . $GLOBALS['spip_version_affichee'] . " @ www.spip.net + " . $header);
876
	} else {
877
		@unlink(_DIR_VAR . "config.txt");
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...
878
	}
879
	// generer charger_plugins_chemin.php
880
	plugins_precompile_chemin($plugin_valides, $ordre);
881
	// generer les fichiers
882
	// - charger_plugins_options.php
883
	// - charger_plugins_fonctions.php
884
	plugins_precompile_xxxtions($plugin_valides, $ordre);
885
	// charger les chemins des plugins et les fichiers d'options 
886
	// (qui peuvent déclarer / utiliser des pipelines, ajouter d'autres chemins)
887
	plugins_amorcer_plugins_actifs();
888
	// mise a jour de la matrice des pipelines
889
	$prepend_code = pipeline_matrice_precompile($plugin_valides, $ordre, $pipe_recherche);
890
	// generer le fichier _CACHE_PIPELINE
891
	pipeline_precompile($prepend_code);
892
893
	if (spip_connect()) {
894
		// lancer et initialiser les nouveaux crons !
895
		include_spip('inc/genie');
896
		genie_queue_watch_dist();
897
	}
898
899
	return ($GLOBALS['meta']['plugin'] != $actifs_avant);
900
}
901
902
/**
903
 * Écrit le fichier de déclaration des chemins (path) des plugins actifs
904
 * 
905
 * Le fichier créé, une fois exécuté permet à SPIP de rechercher
906
 * des fichiers dans les répertoires des plugins concernés.
907
 * 
908
 * @see _chemin() Utilisé pour déclarer les chemins.
909
 * @uses plugin_version_compatible()
910
 * @uses ecrire_fichier_php()
911
 * 
912
 * @param array $plugin_valides
913
 *     Couples (prefixe => description) des plugins qui seront actifs
914
 * @param array $ordre
915
 *     Couples (prefixe => infos complètes) des plugins qui seront actifs, dans l'ordre de leurs dépendances
916
**/
917
function plugins_precompile_chemin($plugin_valides, $ordre) {
918
	$chemins = array();
919
	$contenu = "";
920
	foreach ($ordre as $p => $info) {
921
		// $ordre peur contenir des plugins en attente et non valides pour ce hit
922
		if (isset($plugin_valides[$p])) {
923
			$dir_type = $plugin_valides[$p]['dir_type'];
924
			$plug = $plugin_valides[$p]['dir'];
925
			// definir le plugin, donc le path avant l'include du fichier options
926
			// permet de faire des include_spip pour attraper un inc_ du plugin
927
928
			$dir = $dir_type . ".'" . $plug . "/'";
929
930
			$prefix = strtoupper(preg_replace(',\W,', '_', $info['prefix']));
931
			if (
932
				$prefix !== "SPIP"
933
				and strpos($dir, ":") === false // exclure le cas des procure:
934
			) {
935
				$contenu .= "define('_DIR_PLUGIN_$prefix',$dir);\n";
936
				foreach ($info['chemin'] as $chemin) {
937
					if (!isset($chemin['version']) or plugin_version_compatible($chemin['version'],
938
							$GLOBALS['spip_version_branche'], 'spip')
939
					) {
940
						$dir = $chemin['path'];
941 View Code Duplication
						if (strlen($dir) and $dir[0] == "/") {
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...
942
							$dir = substr($dir, 1);
943
						}
944
						if (strlen($dir) and $dir == "./") {
945
							$dir = '';
946
						}
947
						if (strlen($dir)) {
948
							$dir = rtrim($dir, '/') . '/';
949
						}
950 View Code Duplication
						if (!isset($chemin['type']) or $chemin['type'] == 'public') {
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...
951
							$chemins['public'][] = "_DIR_PLUGIN_$prefix" . (strlen($dir) ? ".'$dir'" : "");
952
						}
953 View Code Duplication
						if (!isset($chemin['type']) or $chemin['type'] == 'prive') {
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...
954
							$chemins['prive'][] = "_DIR_PLUGIN_$prefix" . (strlen($dir) ? ".'$dir'" : "");
955
						}
956
					}
957
				}
958
			}
959
		}
960
	}
961
	if (count($chemins)) {
962
		$contenu .= "if (_DIR_RESTREINT) _chemin(implode(':',array(" . implode(',',
963
				array_reverse($chemins['public'])) . ")));\n"
964
			. "else _chemin(implode(':',array(" . implode(',', array_reverse($chemins['prive'])) . ")));\n";
965
	}
966
967
	ecrire_fichier_php(_CACHE_PLUGINS_PATH, $contenu);
968
}
969
970
/**
971
 * Écrit les fichiers de chargement des fichiers d'options et de fonctions des plugins
972
 * 
973
 * Les onglets et menus déclarés dans le fichier paquet.xml des plugins sont également 
974
 * ajoutés au fichier de fonctions créé.
975
 * 
976
 * @uses plugin_ongletbouton()
977
 * @uses ecrire_fichier_php()
978
 * 
979
 * @param array $plugin_valides
980
 *     Couples (prefixe => description) des plugins qui seront actifs
981
 * @param array $ordre
982
 *     Couples (prefixe => infos complètes) des plugins qui seront actifs, dans l'ordre de leurs dépendances
983
**/
984
function plugins_precompile_xxxtions($plugin_valides, $ordre) {
985
	$contenu = array('options' => '', 'fonctions' => '');
986
	$boutons = array();
987
	$onglets = array();
988
	$sign = "";
989
990
	foreach ($ordre as $p => $info) {
991
		// $ordre peur contenir des plugins en attente et non valides pour ce hit
992
		if (isset($plugin_valides[$p])) {
993
			$dir_type = $plugin_valides[$p]['dir_type'];
994
			$plug = $plugin_valides[$p]['dir'];
995
			$dir = constant($dir_type);
996
			$root_dir_type = str_replace('_DIR_', '_ROOT_', $dir_type);
997
			if ($info['menu']) {
998
				$boutons = array_merge($boutons, $info['menu']);
999
			}
1000
			if ($info['onglet']) {
1001
				$onglets = array_merge($onglets, $info['onglet']);
1002
			}
1003
			foreach ($contenu as $charge => $v) {
1004
				// si pas declare/detecte a la lecture du paquet.xml,
1005
				// detecer a nouveau ici puisque son ajout ne provoque pas une modif du paquet.xml
1006
				// donc ni sa relecture, ni sa detection
1007
				if (!isset($info[$charge])
1008
					and $dir // exclure le cas du plugin "SPIP"
1009
					and strpos($dir, ":") === false // exclure le cas des procure:
1010
					and file_exists("$dir$plug/paquet.xml") // uniquement pour les paquet.xml
1011
				) {
1012
					if (is_readable("$dir$plug/" . ($file = $info['prefix'] . "_" . $charge . ".php"))) {
1013
						$info[$charge] = array($file);
1014
					}
1015
				}
1016
				if (isset($info[$charge])) {
1017
					$files = $info[$charge];
1018
					foreach ($files as $k => $file) {
1019
						// on genere un if file_exists devant chaque include
1020
						// pour pouvoir garder le meme niveau d'erreur general
1021
						$file = trim($file);
1022
						if (!is_readable("$dir$plug/$file")
1023
							// uniquement pour les paquet.xml
1024
							and file_exists("$dir$plug/paquet.xml")
1025
						) {
1026
							unset($info[$charge][$k]);
1027
						} else {
1028
							$_file = $root_dir_type . ".'$plug/$file'";
1029
							$contenu[$charge] .= "include_once_check($_file);\n";
1030
						}
1031
					}
1032
				}
1033
			}
1034
			$sign .= md5(serialize($info));
1035
		}
1036
	}
1037
1038
	$contenu['options'] = "define('_PLUGINS_HASH','" . md5($sign) . "');\n" . $contenu['options'];
1039
	$contenu['fonctions'] .= plugin_ongletbouton("boutons_plugins", $boutons)
1040
		. plugin_ongletbouton("onglets_plugins", $onglets);
1041
1042
	ecrire_fichier_php(_CACHE_PLUGINS_OPT, $contenu['options']);
1043
	ecrire_fichier_php(_CACHE_PLUGINS_FCT, $contenu['fonctions']);
1044
}
1045
1046
/**
1047
 * Compile les entrées d'un menu et retourne le code php d'exécution
1048
 *
1049
 * Génère et retourne un code php (pour enregistrement dans un fichier de cache)
1050
 * permettant d'obtenir la liste des entrées de menus, ou des onglets
1051
 * de l'espace privé.
1052
 *
1053
 * Définit également une constante (_UPDATED_$nom et _UPDATED_md5_$nom),
1054
 * signalant une modification de ces menus
1055
 *
1056
 * @param string $nom Nom du type de menu
1057
 *     Exemple: boutons_plugins, onglets_plugins
1058
 * @param array $val Liste des entrées de ce menu
1059
 * @return string Code php
1060
 */
1061
function plugin_ongletbouton($nom, $val) {
1062
	if (!$val) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $val of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1063
		$val = array();
1064
	}
1065
1066
	$val = serialize($val);
1067
	$md5 = md5($val);
1068
1069
	if (!defined("_UPDATED_$nom")) {
1070
		define("_UPDATED_$nom", $val);
1071
		define("_UPDATED_md5_$nom", $md5);
1072
	}
1073
	$val = "unserialize('" . str_replace("'", "\'", $val) . "')";
1074
1075
	return
1076
		"if (!function_exists('$nom')) {\n"
1077
		. "function $nom(){return defined('_UPDATED_$nom')?unserialize(_UPDATED_$nom):$val;}\n"
1078
		. "function md5_$nom(){return defined('_UPDATED_md5_$nom')?_UPDATED_md5_$nom:'" . $md5 . "';}\n"
1079
		. "}\n";
1080
}
1081
1082
/**
1083
 * Chargement des plugins actifs dans le path de SPIP
1084
 * et exécution de fichiers d'options des plugins 
1085
 *
1086
 * Les fichiers d'options peuvent déclarer des pipelines ou de
1087
 * nouveaux chemins.
1088
 * 
1089
 * La connaissance chemins peut être nécessaire pour la construction
1090
 * du fichier d'exécution des pipelines. 
1091
**/
1092
function plugins_amorcer_plugins_actifs() {
1093
1094
	if (@is_readable(_CACHE_PLUGINS_PATH)) {
1095
		include_once(_CACHE_PLUGINS_PATH);
1096
	} 
1097
1098
	if (@is_readable(_CACHE_PLUGINS_OPT)) {
1099
		include_once(_CACHE_PLUGINS_OPT);
1100
	} else {
1101
		spip_log("pipelines desactives: impossible de produire " . _CACHE_PLUGINS_OPT);
1102
	}
1103
}
1104
1105
/**
1106
 * Crée la liste des filtres à traverser pour chaque pipeline
1107
 *
1108
 * Complète la globale `spip_pipeline` des fonctions que doit traverser un pipeline,
1109
 * et la globale `spip_matrice` des fichiers à charger qui contiennent ces fonctions.
1110
 * 
1111
 * Retourne aussi pour certaines balises présentes dans les paquet.xml (script, style, genie),
1112
 * un code PHP à insérer au début de la chaîne du ou des pipelines associés à cette balise
1113
 * (insert_head, insert_head_css, taches_generales_cron, ...). Ce sont des écritures 
1114
 * raccourcies pour des usages fréquents de ces pipelines.
1115
 * 
1116
 * @param array $plugin_valides
1117
 *     Couples (prefixe => description) des plugins qui seront actifs
1118
 * @param array $ordre
1119
 *     Couples (prefixe => infos complètes) des plugins qui seront actifs, dans l'ordre de leurs dépendances
1120
 * @param string $pipe_recherche
1121
 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1122
 *     Couples (nom du pipeline => Code PHP à insérer au début du pipeline)
1123
**/
1124
function pipeline_matrice_precompile($plugin_valides, $ordre, $pipe_recherche) {
1125
	static $liste_pipe_manquants = array();
1126
	if (($pipe_recherche) && (!in_array($pipe_recherche, $liste_pipe_manquants))) {
1127
		$liste_pipe_manquants[] = $pipe_recherche;
1128
	}
1129
1130
	$prepend_code = array();
1131
1132
	foreach ($ordre as $p => $info) {
1133
		// $ordre peur contenir des plugins en attente et non valides pour ce hit
1134
		if (isset($plugin_valides[$p])) {
1135
			$dir_type = $plugin_valides[$p]['dir_type'];
1136
			$root_dir_type = str_replace('_DIR_', '_ROOT_', $dir_type);
1137
			$plug = $plugin_valides[$p]['dir'];
1138
			$prefix = (($info['prefix'] == "spip") ? "" : $info['prefix'] . "_");
1139
			if (isset($info['pipeline']) and is_array($info['pipeline'])) {
1140
				foreach ($info['pipeline'] as $pipe) {
1141
					$nom = $pipe['nom'];
1142
					if (isset($pipe['action'])) {
1143
						$action = $pipe['action'];
1144
					} else {
1145
						$action = $nom;
1146
					}
1147
					$nomlower = strtolower($nom);
1148
					if ($nomlower != $nom
1149
						and isset($GLOBALS['spip_pipeline'][$nom])
1150
						and !isset($GLOBALS['spip_pipeline'][$nomlower])
1151
					) {
1152
						$GLOBALS['spip_pipeline'][$nomlower] = $GLOBALS['spip_pipeline'][$nom];
1153
						unset($GLOBALS['spip_pipeline'][$nom]);
1154
					}
1155
					$nom = $nomlower;
1156
					// une action vide est une declaration qui ne doit pas etre compilee !
1157
					if (!isset($GLOBALS['spip_pipeline'][$nom])) // creer le pipeline eventuel
1158
					{
1159
						$GLOBALS['spip_pipeline'][$nom] = "";
1160
					}
1161
					if ($action) {
1162
						if (strpos($GLOBALS['spip_pipeline'][$nom], "|$prefix$action") === false) {
1163
							$GLOBALS['spip_pipeline'][$nom] = preg_replace(",(\|\||$),", "|$prefix$action\\1",
1164
								$GLOBALS['spip_pipeline'][$nom], 1);
1165
						}
1166
						if (isset($pipe['inclure'])) {
1167
							$GLOBALS['spip_matrice']["$prefix$action"] =
1168
								"$root_dir_type:$plug/" . $pipe['inclure'];
1169
						}
1170
					}
1171
				}
1172
			}
1173
			if (isset($info['genie']) and count($info['genie'])) {
1174
				if (!isset($prepend_code['taches_generales_cron'])) {
1175
					$prepend_code['taches_generales_cron'] = "";
1176
				}
1177
				foreach ($info['genie'] as $genie) {
1178
					$nom = $prefix . $genie['nom'];
1179
					$periode = max(60, intval($genie['periode']));
1180
					if (charger_fonction($nom, "genie", true)) {
1181
						$prepend_code['taches_generales_cron'] .= "\$val['$nom'] = $periode;\n";
1182
					} else {
1183
						spip_log("Fonction genie_$nom introuvable", _LOG_ERREUR);
1184
					}
1185
				}
1186
			}
1187
			if (isset($info['style']) and count($info['style'])) {
1188
				if (!isset($prepend_code['insert_head_css'])) {
1189
					$prepend_code['insert_head_css'] = "";
1190
				}
1191
				if (!isset($prepend_code['header_prive_css'])) {
1192
					$prepend_code['header_prive_css'] = "";
1193
				}
1194
				foreach ($info['style'] as $style) {
1195 View Code Duplication
					if (isset($style['path']) and $style['path']) {
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...
1196
						$code = "if (\$f=timestamp(direction_css(find_in_path('" . addslashes($style['path']) . "')))) ";
1197
					} else {
1198
						$code = "if (\$f='" . addslashes($style['url']) . "') ";
1199
					}
1200
					$code .= "\$val .= '<link rel=\"stylesheet\" href=\"'.\$f.'\" type=\"text/css\"";
1201
					if (isset($style['media']) and strlen($style['media'])) {
1202
						$code .= " media=\"" . addslashes($style['media']) . "\"";
1203
					}
1204
					$code .= "/>';\n";
1205
					if ($style['type'] != 'prive') {
1206
						$prepend_code['insert_head_css'] .= $code;
1207
					}
1208
					if ($style['type'] != 'public') {
1209
						$prepend_code['header_prive_css'] .= $code;
1210
					}
1211
				}
1212
			}
1213
			if (!isset($prepend_code['insert_head'])) {
1214
				$prepend_code['insert_head'] = "";
1215
			}
1216
			if (!isset($prepend_code['header_prive'])) {
1217
				$prepend_code['header_prive'] = "";
1218
			}
1219
			if (isset($info['script']) and count($info['script'])) {
1220
				foreach ($info['script'] as $script) {
1221 View Code Duplication
					if (isset($script['path']) and $script['path']) {
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...
1222
						$code = "if (\$f=timestamp(find_in_path('" . addslashes($script['path']) . "'))) ";
1223
					} else {
1224
						$code = "if (\$f='" . addslashes($script['url']) . "') ";
1225
					}
1226
					$code .= "\$val .= '<script src=\"'.\$f.'\" type=\"text/javascript\"></script>';\n";
1227
					if ($script['type'] != 'prive') {
1228
						$prepend_code['insert_head'] .= $code;
1229
					}
1230
					if ($script['type'] != 'public') {
1231
						$prepend_code['header_prive'] .= $code;
1232
					}
1233
				}
1234
			}
1235
		}
1236
	}
1237
1238
	$prepend_code['insert_head'] =
1239
		"include_once_check(_DIR_RESTREINT . 'inc/pipelines.php');\n"
1240
		. "\$val = minipipe('f_jQuery', \$val);\n"
1241
		. $prepend_code['insert_head'];
1242
	$prepend_code['header_prive'] =
1243
		"include_once_check(_DIR_RESTREINT . 'inc/pipelines_ecrire.php');\n"
1244
		. "\$val = minipipe('f_jQuery_prive', \$val);\n"
1245
		. $prepend_code['header_prive'];
1246
1247
	// on ajoute les pipe qui ont ete recenses manquants
1248
	foreach ($liste_pipe_manquants as $add_pipe) {
1249
		if (!isset($GLOBALS['spip_pipeline'][$add_pipe])) {
1250
			$GLOBALS['spip_pipeline'][$add_pipe] = '';
1251
		}
1252
	}
1253
1254
	return $prepend_code;
1255
}
1256
1257
/**
1258
 * Précompilation des pipelines
1259
 *
1260
 * Crée le fichier d'exécution des pipelines 
1261
 * dont le chemin est défini par `_CACHE_PIPELINES`
1262
 * 
1263
 * La liste des pipelines est définie par la globale `spip_pipeline`
1264
 * qui a été remplie soit avec les fichiers d'options, soit avec 
1265
 * des descriptions de plugins (paquet.xml) dont celui de SPIP lui-même.
1266
 * 
1267
 * Les fichiers à charger pour accéder aux fonctions qui doivent traverser
1268
 * un pipeline se trouve dans la globale `spip_matrice`.
1269
 * 
1270
 * @see pipeline_matrice_precompile()
1271
 * 
1272
 * @uses ecrire_fichier_php()
1273
 * @uses clear_path_cache()
1274
 * 
1275
 * @param array $prepend_code
1276
 *     Code PHP à insérer avant le passage dans la chaîne des fonctions d'un pipeline
1277
 *     Couples 'Nom du pipeline' => Code PHP à insérer
1278
**/
1279
function pipeline_precompile($prepend_code = array()) {
1280
1281
	$all_pipes = $all_pipes_end = '';
1282
	if (!empty($GLOBALS['spip_pipeline']['all'])) {
1283
		$a = explode('||', $GLOBALS['spip_pipeline']['all'], 2);
1284
		unset($GLOBALS['spip_pipeline']['all']);
1285
		$all_pipes = trim(array_shift($a));
1286
		if ($all_pipes) {
1287
			$all_pipes = '|' . ltrim($a, '|');
1288
		}
1289
		if (count($a)) {
1290
			$all_pipes_end = '||' . array_shift($a);
1291
		}
1292
	}
1293
	$content = "";
1294
	foreach ($GLOBALS['spip_pipeline'] as $action => $pipeline) {
1295
		$s_inc = "";
1296
		$s_call = "";
1297
		if ($all_pipes) {
1298
			$pipeline = preg_replace(",(\|\||$),", "$all_pipes\\1", $pipeline, 1);
1299
		}
1300
		if ($all_pipes_end) {
1301
			$pipeline .= $all_pipes_end;
1302
		}
1303
		$pipe = array_filter(explode('|', $pipeline));
1304
		// Eclater le pipeline en filtres et appliquer chaque filtre
1305
		foreach ($pipe as $fonc) {
1306
			$fonc = trim($fonc);
1307
			$s_call .= '$val = minipipe(\'' . $fonc . '\', $val);' . "\n";
1308
			if (isset($GLOBALS['spip_matrice'][$fonc])) {
1309
				$file = $GLOBALS['spip_matrice'][$fonc];
1310
				$file = "'$file'";
1311
				// si un _DIR_XXX: est dans la chaine, on extrait la constante
1312
				if (preg_match(",(_(DIR|ROOT)_[A-Z_]+):,Ums", $file, $regs)) {
1313
					$dir = $regs[1];
1314
					$root_dir = str_replace('_DIR_', '_ROOT_', $dir);
1315
					if (defined($root_dir)) {
1316
						$dir = $root_dir;
1317
					}
1318
					$file = str_replace($regs[0], "'." . $dir . ".'", $file);
1319
					$file = str_replace("''.", "", $file);
1320
					$file = str_replace(constant($dir), '', $file);
1321
				}
1322
				$s_inc .= "include_once_check($file);\n";
1323
			}
1324
		}
1325
		if (strlen($s_inc)) {
1326
			$s_inc = "static \$inc=null;\nif (!\$inc){\n$s_inc\$inc=true;\n}\n";
1327
		}
1328
		$content .= "// Pipeline $action \n"
1329
			. "function execute_pipeline_$action(&\$val){\n"
1330
			. $s_inc
1331
			. ((isset($prepend_code[$action]) and strlen($prepend_code[$action])) ? trim($prepend_code[$action]) . "\n" : '')
1332
			. $s_call
1333
			. "return \$val;\n}\n";
1334
	}
1335
	ecrire_fichier_php(_CACHE_PIPELINES, $content);
1336
	clear_path_cache();
1337
}
1338
1339
1340
/**
1341
 * Indique si un chemin de plugin fait parti des plugins activés sur le site
1342
 *
1343
 * @param string $plug_path
1344
 *     Chemin du plugin
1345
 * @return bool
1346
 *     true si le plugin est actif, false sinon
1347
**/
1348
function plugin_est_installe($plug_path) {
1349
	$plugin_installes = isset($GLOBALS['meta']['plugin_installes']) ? unserialize($GLOBALS['meta']['plugin_installes']) : array();
1350
	if (!$plugin_installes) {
1351
		return false;
1352
	}
1353
1354
	return in_array($plug_path, $plugin_installes);
1355
}
1356
1357
1358
/**
1359
 * Parcours les plugins activés et appelle leurs fonctions d'installation si elles existent.
1360
 *
1361
 * Elle ajoute ensuite les plugins qui ont été installés dans la valeur "plugin_installes"
1362
 * de la table meta. Cette meta ne contient que les noms des plugins qui ont une version_base.
1363
 *
1364
 * @uses plugins_installer_dist()
1365
 **/
1366
function plugin_installes_meta() {
1367
	if (isset($GLOBALS['fichier_php_compile_recent'])) {
1368
		// attendre eventuellement l'invalidation du cache opcode
1369
		spip_attend_invalidation_opcode_cache($GLOBALS['fichier_php_compile_recent']);
1370
	}
1371
1372
	$installer_plugins = charger_fonction('installer', 'plugins');
1373
	$meta_plug_installes = array();
1374
	foreach (unserialize($GLOBALS['meta']['plugin']) as $prefix => $resume) {
1375
		if ($plug = $resume['dir']) {
1376
			$infos = $installer_plugins($plug, 'install', $resume['dir_type']);
1377
			if ($infos) {
1378
				if (!is_array($infos) or $infos['install_test'][0]) {
1379
					$meta_plug_installes[] = $plug;
1380
				}
1381
				if (is_array($infos)) {
1382
					list($ok, $trace) = $infos['install_test'];
1383
					include_spip('inc/filtres_boites');
1384
					echo "<div class='install-plugins svp_retour'>"
1385
						. boite_ouvrir(_T('plugin_titre_installation', array('plugin' => typo($infos['nom']))),
1386
							($ok ? 'success' : 'error'))
1387
						. $trace
1388
						. "<div class='result'>"
1389
						. ($ok ? ((isset($infos['upgrade']) && $infos['upgrade']) ? _T("plugin_info_upgrade_ok") : _T("plugin_info_install_ok")) : _T("avis_operation_echec"))
1390
						. "</div>"
1391
						. boite_fermer()
1392
						. "</div>";
1393
				}
1394
			}
1395
		}
1396
	}
1397
	ecrire_meta('plugin_installes', serialize($meta_plug_installes), 'non');
1398
}
1399
1400
/**
1401
 * Écrit un fichier PHP
1402
 *
1403
 * @param string $nom
1404
 *     Chemin du fichier
1405
 * @param string $contenu
1406
 *     Contenu du fichier (sans les balises ouvrantes et fermantes de PHP)
1407
 * @param string $comment
1408
 *     Commentaire : code écrit en tout début de fichier, après la balise PHP ouvrante
1409
**/
1410
function ecrire_fichier_php($nom, $contenu, $comment = '') {
1411
	if (!isset($GLOBALS['fichier_php_compile_recent'])) {
1412
		$GLOBALS['fichier_php_compile_recent'] = 0;
1413
	}
1414
1415
	$contenu = '<' . '?php' . "\n" . $comment . "\nif (defined('_ECRIRE_INC_VERSION')) {\n" . $contenu . "}\n?" . '>';
1416
	// si un fichier existe deja on verifie que son contenu change avant de l'ecraser
1417
	// si pas de modif on ne touche pas au fichier initial
1418
	if (file_exists($nom)) {
1419
		if (substr($nom, -4) == '.php') {
1420
			$fichier_tmp = substr($nom, 0, -4) . '.tmp.php';
1421
		}
1422
		else {
1423
			$fichier_tmp = $nom . '.tmp';
1424
		}
1425
		file_put_contents($fichier_tmp, $contenu);
1426
		if(md5_file($nom) == md5_file($fichier_tmp)) {
1427
			$GLOBALS['fichier_php_compile_recent'] = max($GLOBALS['fichier_php_compile_recent'], filemtime($nom));
1428
			@unlink($fichier_tmp);
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...
1429
			return;
1430
		}
1431
		@unlink($fichier_tmp);
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...
1432
	}
1433
	ecrire_fichier($nom, $contenu);
1434
	$GLOBALS['fichier_php_compile_recent'] = max($GLOBALS['fichier_php_compile_recent'], filemtime($nom));
1435
	spip_clear_opcode_cache(realpath($nom));
1436
}
1437