Completed
Push — master ( ce9a8f...cee594 )
by cam
04:47
created

plugin.php ➔ plugins_precompile_chemin()   D

Complexity

Conditions 23
Paths 14

Size

Total Lines 64

Duplication

Lines 7
Ratio 10.94 %

Importance

Changes 0
Metric Value
cc 23
nc 14
nop 2
dl 7
loc 64
rs 4.1666
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 = [
919
		'public' => [],
920
		'prive' => []
921
	];
922
	$contenu = "";
923
	foreach ($ordre as $p => $info) {
924
		// $ordre peur contenir des plugins en attente et non valides pour ce hit
925
		if (isset($plugin_valides[$p])) {
926
			$dir_type = $plugin_valides[$p]['dir_type'];
927
			$plug = $plugin_valides[$p]['dir'];
928
			// definir le plugin, donc le path avant l'include du fichier options
929
			// permet de faire des include_spip pour attraper un inc_ du plugin
930
931
			$dir = $dir_type . ".'" . $plug . "/'";
932
933
			$prefix = strtoupper(preg_replace(',\W,', '_', $info['prefix']));
934
			if (
935
				$prefix !== "SPIP"
936
				and strpos($dir, ":") === false // exclure le cas des procure:
937
			) {
938
				$contenu .= "define('_DIR_PLUGIN_$prefix',$dir);\n";
939
				if (!$info['chemin']) {
940
					$chemins['public'][] = "_DIR_PLUGIN_$prefix";
941
					$chemins['prive'][] = "_DIR_PLUGIN_$prefix";
942
					if (is_dir(constant($dir_type) . $plug . '/squelettes/')) {
943
						$chemins['public'][] = "_DIR_PLUGIN_{$prefix}.'squelettes/'";
944
					}
945
				}
946
				else{
947
					foreach ($info['chemin'] as $chemin) {
948
						if (!isset($chemin['version']) or plugin_version_compatible($chemin['version'],
949
								$GLOBALS['spip_version_branche'], 'spip')
950
						) {
951
							$dir = $chemin['path'];
952 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...
953
								$dir = substr($dir, 1);
954
							}
955
							if (strlen($dir) and $dir == "./") {
956
								$dir = '';
957
							}
958
							if (strlen($dir)) {
959
								$dir = rtrim($dir, '/') . '/';
960
							}
961 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...
962
								$chemins['public'][] = "_DIR_PLUGIN_$prefix" . (strlen($dir) ? ".'$dir'" : "");
963
							}
964 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...
965
								$chemins['prive'][] = "_DIR_PLUGIN_$prefix" . (strlen($dir) ? ".'$dir'" : "");
966
							}
967
						}
968
					}
969
				}
970
			}
971
		}
972
	}
973
	if (count($chemins['public']) or count($chemins['prive'])) {
974
		$contenu .= "if (_DIR_RESTREINT) _chemin([" . implode(',',
975
				array_reverse($chemins['public'])) . "]);\n"
976
			. "else _chemin([" . implode(',', array_reverse($chemins['prive'])) . "]);\n";
977
	}
978
979
	ecrire_fichier_php(_CACHE_PLUGINS_PATH, $contenu);
980
}
981
982
/**
983
 * Écrit les fichiers de chargement des fichiers d'options et de fonctions des plugins
984
 * 
985
 * Les onglets et menus déclarés dans le fichier paquet.xml des plugins sont également 
986
 * ajoutés au fichier de fonctions créé.
987
 * 
988
 * @uses plugin_ongletbouton()
989
 * @uses ecrire_fichier_php()
990
 * 
991
 * @param array $plugin_valides
992
 *     Couples (prefixe => description) des plugins qui seront actifs
993
 * @param array $ordre
994
 *     Couples (prefixe => infos complètes) des plugins qui seront actifs, dans l'ordre de leurs dépendances
995
**/
996
function plugins_precompile_xxxtions($plugin_valides, $ordre) {
997
	$contenu = array('options' => '', 'fonctions' => '');
998
	$boutons = array();
999
	$onglets = array();
1000
	$sign = "";
1001
1002
	foreach ($ordre as $p => $info) {
1003
		// $ordre peur contenir des plugins en attente et non valides pour ce hit
1004
		if (isset($plugin_valides[$p])) {
1005
			$dir_type = $plugin_valides[$p]['dir_type'];
1006
			$plug = $plugin_valides[$p]['dir'];
1007
			$dir = constant($dir_type);
1008
			$root_dir_type = str_replace('_DIR_', '_ROOT_', $dir_type);
1009
			if ($info['menu']) {
1010
				$boutons = array_merge($boutons, $info['menu']);
1011
			}
1012
			if ($info['onglet']) {
1013
				$onglets = array_merge($onglets, $info['onglet']);
1014
			}
1015
			foreach ($contenu as $charge => $v) {
1016
				// si pas declare/detecte a la lecture du paquet.xml,
1017
				// detecer a nouveau ici puisque son ajout ne provoque pas une modif du paquet.xml
1018
				// donc ni sa relecture, ni sa detection
1019
				if (!isset($info[$charge])
1020
					and $dir // exclure le cas du plugin "SPIP"
1021
					and strpos($dir, ":") === false // exclure le cas des procure:
1022
					and file_exists("$dir$plug/paquet.xml") // uniquement pour les paquet.xml
1023
				) {
1024
					if (is_readable("$dir$plug/" . ($file = $info['prefix'] . "_" . $charge . ".php"))) {
1025
						$info[$charge] = array($file);
1026
					}
1027
				}
1028
				if (isset($info[$charge])) {
1029
					$files = $info[$charge];
1030
					foreach ($files as $k => $file) {
1031
						// on genere un if file_exists devant chaque include
1032
						// pour pouvoir garder le meme niveau d'erreur general
1033
						$file = trim($file);
1034
						if (!is_readable("$dir$plug/$file")
1035
							// uniquement pour les paquet.xml
1036
							and file_exists("$dir$plug/paquet.xml")
1037
						) {
1038
							unset($info[$charge][$k]);
1039
						} else {
1040
							$_file = $root_dir_type . ".'$plug/$file'";
1041
							$contenu[$charge] .= "include_once_check($_file);\n";
1042
						}
1043
					}
1044
				}
1045
			}
1046
			$sign .= md5(serialize($info));
1047
		}
1048
	}
1049
1050
	$contenu['options'] = "define('_PLUGINS_HASH','" . md5($sign) . "');\n" . $contenu['options'];
1051
	$contenu['fonctions'] .= plugin_ongletbouton("boutons_plugins", $boutons)
1052
		. plugin_ongletbouton("onglets_plugins", $onglets);
1053
1054
	ecrire_fichier_php(_CACHE_PLUGINS_OPT, $contenu['options']);
1055
	ecrire_fichier_php(_CACHE_PLUGINS_FCT, $contenu['fonctions']);
1056
}
1057
1058
/**
1059
 * Compile les entrées d'un menu et retourne le code php d'exécution
1060
 *
1061
 * Génère et retourne un code php (pour enregistrement dans un fichier de cache)
1062
 * permettant d'obtenir la liste des entrées de menus, ou des onglets
1063
 * de l'espace privé.
1064
 *
1065
 * Définit également une constante (_UPDATED_$nom et _UPDATED_md5_$nom),
1066
 * signalant une modification de ces menus
1067
 *
1068
 * @param string $nom Nom du type de menu
1069
 *     Exemple: boutons_plugins, onglets_plugins
1070
 * @param array $val Liste des entrées de ce menu
1071
 * @return string Code php
1072
 */
1073
function plugin_ongletbouton($nom, $val) {
1074
	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...
1075
		$val = array();
1076
	}
1077
1078
	$val = serialize($val);
1079
	$md5 = md5($val);
1080
1081
	if (!defined("_UPDATED_$nom")) {
1082
		define("_UPDATED_$nom", $val);
1083
		define("_UPDATED_md5_$nom", $md5);
1084
	}
1085
	$val = "unserialize('" . str_replace("'", "\'", $val) . "')";
1086
1087
	return
1088
		"if (!function_exists('$nom')) {\n"
1089
		. "function $nom(){return defined('_UPDATED_$nom')?unserialize(_UPDATED_$nom):$val;}\n"
1090
		. "function md5_$nom(){return defined('_UPDATED_md5_$nom')?_UPDATED_md5_$nom:'" . $md5 . "';}\n"
1091
		. "}\n";
1092
}
1093
1094
/**
1095
 * Chargement des plugins actifs dans le path de SPIP
1096
 * et exécution de fichiers d'options des plugins 
1097
 *
1098
 * Les fichiers d'options peuvent déclarer des pipelines ou de
1099
 * nouveaux chemins.
1100
 * 
1101
 * La connaissance chemins peut être nécessaire pour la construction
1102
 * du fichier d'exécution des pipelines. 
1103
**/
1104
function plugins_amorcer_plugins_actifs() {
1105
1106
	if (@is_readable(_CACHE_PLUGINS_PATH)) {
1107
		include_once(_CACHE_PLUGINS_PATH);
1108
	} 
1109
1110
	if (@is_readable(_CACHE_PLUGINS_OPT)) {
1111
		include_once(_CACHE_PLUGINS_OPT);
1112
	} else {
1113
		spip_log("pipelines desactives: impossible de produire " . _CACHE_PLUGINS_OPT);
1114
	}
1115
}
1116
1117
/**
1118
 * Crée la liste des filtres à traverser pour chaque pipeline
1119
 *
1120
 * Complète la globale `spip_pipeline` des fonctions que doit traverser un pipeline,
1121
 * et la globale `spip_matrice` des fichiers à charger qui contiennent ces fonctions.
1122
 * 
1123
 * Retourne aussi pour certaines balises présentes dans les paquet.xml (script, style, genie),
1124
 * un code PHP à insérer au début de la chaîne du ou des pipelines associés à cette balise
1125
 * (insert_head, insert_head_css, taches_generales_cron, ...). Ce sont des écritures 
1126
 * raccourcies pour des usages fréquents de ces pipelines.
1127
 * 
1128
 * @param array $plugin_valides
1129
 *     Couples (prefixe => description) des plugins qui seront actifs
1130
 * @param array $ordre
1131
 *     Couples (prefixe => infos complètes) des plugins qui seront actifs, dans l'ordre de leurs dépendances
1132
 * @param string $pipe_recherche
1133
 * @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...
1134
 *     Couples (nom du pipeline => Code PHP à insérer au début du pipeline)
1135
**/
1136
function pipeline_matrice_precompile($plugin_valides, $ordre, $pipe_recherche) {
1137
	static $liste_pipe_manquants = array();
1138
	if (($pipe_recherche) && (!in_array($pipe_recherche, $liste_pipe_manquants))) {
1139
		$liste_pipe_manquants[] = $pipe_recherche;
1140
	}
1141
1142
	$prepend_code = array();
1143
1144
	foreach ($ordre as $p => $info) {
1145
		// $ordre peur contenir des plugins en attente et non valides pour ce hit
1146
		if (isset($plugin_valides[$p])) {
1147
			$dir_type = $plugin_valides[$p]['dir_type'];
1148
			$root_dir_type = str_replace('_DIR_', '_ROOT_', $dir_type);
1149
			$plug = $plugin_valides[$p]['dir'];
1150
			$prefix = (($info['prefix'] == "spip") ? "" : $info['prefix'] . "_");
1151
			if (isset($info['pipeline']) and is_array($info['pipeline'])) {
1152
				foreach ($info['pipeline'] as $pipe) {
1153
					$nom = $pipe['nom'];
1154
					if (isset($pipe['action'])) {
1155
						$action = $pipe['action'];
1156
					} else {
1157
						$action = $nom;
1158
					}
1159
					$nomlower = strtolower($nom);
1160
					if ($nomlower != $nom
1161
						and isset($GLOBALS['spip_pipeline'][$nom])
1162
						and !isset($GLOBALS['spip_pipeline'][$nomlower])
1163
					) {
1164
						$GLOBALS['spip_pipeline'][$nomlower] = $GLOBALS['spip_pipeline'][$nom];
1165
						unset($GLOBALS['spip_pipeline'][$nom]);
1166
					}
1167
					$nom = $nomlower;
1168
					// une action vide est une declaration qui ne doit pas etre compilee !
1169
					if (!isset($GLOBALS['spip_pipeline'][$nom])) // creer le pipeline eventuel
1170
					{
1171
						$GLOBALS['spip_pipeline'][$nom] = "";
1172
					}
1173
					if ($action) {
1174
						if (strpos($GLOBALS['spip_pipeline'][$nom], "|$prefix$action") === false) {
1175
							$GLOBALS['spip_pipeline'][$nom] = preg_replace(",(\|\||$),", "|$prefix$action\\1",
1176
								$GLOBALS['spip_pipeline'][$nom], 1);
1177
						}
1178
						if (isset($pipe['inclure'])) {
1179
							$GLOBALS['spip_matrice']["$prefix$action"] =
1180
								"$root_dir_type:$plug/" . $pipe['inclure'];
1181
						}
1182
					}
1183
				}
1184
			}
1185
			if (isset($info['genie']) and count($info['genie'])) {
1186
				if (!isset($prepend_code['taches_generales_cron'])) {
1187
					$prepend_code['taches_generales_cron'] = "";
1188
				}
1189
				foreach ($info['genie'] as $genie) {
1190
					$nom = $prefix . $genie['nom'];
1191
					$periode = max(60, intval($genie['periode']));
1192
					if (charger_fonction($nom, "genie", true)) {
1193
						$prepend_code['taches_generales_cron'] .= "\$val['$nom'] = $periode;\n";
1194
					} else {
1195
						spip_log("Fonction genie_$nom introuvable", _LOG_ERREUR);
1196
					}
1197
				}
1198
			}
1199
			if (isset($info['style']) and count($info['style'])) {
1200
				if (!isset($prepend_code['insert_head_css'])) {
1201
					$prepend_code['insert_head_css'] = "";
1202
				}
1203
				if (!isset($prepend_code['header_prive_css'])) {
1204
					$prepend_code['header_prive_css'] = "";
1205
				}
1206
				foreach ($info['style'] as $style) {
1207 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...
1208
						$code = "if (\$f=timestamp(direction_css(find_in_path('" . addslashes($style['path']) . "')))) ";
1209
					} else {
1210
						$code = "if (\$f='" . addslashes($style['url']) . "') ";
1211
					}
1212
					$code .= "\$val .= '<link rel=\"stylesheet\" href=\"'.\$f.'\" type=\"text/css\"";
1213
					if (isset($style['media']) and strlen($style['media'])) {
1214
						$code .= " media=\"" . addslashes($style['media']) . "\"";
1215
					}
1216
					$code .= "/>';\n";
1217
					if ($style['type'] != 'prive') {
1218
						$prepend_code['insert_head_css'] .= $code;
1219
					}
1220
					if ($style['type'] != 'public') {
1221
						$prepend_code['header_prive_css'] .= $code;
1222
					}
1223
				}
1224
			}
1225
			if (!isset($prepend_code['insert_head'])) {
1226
				$prepend_code['insert_head'] = "";
1227
			}
1228
			if (!isset($prepend_code['header_prive'])) {
1229
				$prepend_code['header_prive'] = "";
1230
			}
1231
			if (isset($info['script']) and count($info['script'])) {
1232
				foreach ($info['script'] as $script) {
1233 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...
1234
						$code = "if (\$f=timestamp(find_in_path('" . addslashes($script['path']) . "'))) ";
1235
					} else {
1236
						$code = "if (\$f='" . addslashes($script['url']) . "') ";
1237
					}
1238
					$code .= "\$val .= '<script src=\"'.\$f.'\" type=\"text/javascript\"></script>';\n";
1239
					if ($script['type'] != 'prive') {
1240
						$prepend_code['insert_head'] .= $code;
1241
					}
1242
					if ($script['type'] != 'public') {
1243
						$prepend_code['header_prive'] .= $code;
1244
					}
1245
				}
1246
			}
1247
		}
1248
	}
1249
1250
	$prepend_code['insert_head'] =
1251
		"include_once_check(_DIR_RESTREINT . 'inc/pipelines.php');\n"
1252
		. "\$val = minipipe('f_jQuery', \$val);\n"
1253
		. $prepend_code['insert_head'];
1254
	$prepend_code['header_prive'] =
1255
		"include_once_check(_DIR_RESTREINT . 'inc/pipelines_ecrire.php');\n"
1256
		. "\$val = minipipe('f_jQuery_prive', \$val);\n"
1257
		. $prepend_code['header_prive'];
1258
1259
	// on ajoute les pipe qui ont ete recenses manquants
1260
	foreach ($liste_pipe_manquants as $add_pipe) {
1261
		if (!isset($GLOBALS['spip_pipeline'][$add_pipe])) {
1262
			$GLOBALS['spip_pipeline'][$add_pipe] = '';
1263
		}
1264
	}
1265
1266
	return $prepend_code;
1267
}
1268
1269
/**
1270
 * Précompilation des pipelines
1271
 *
1272
 * Crée le fichier d'exécution des pipelines 
1273
 * dont le chemin est défini par `_CACHE_PIPELINES`
1274
 * 
1275
 * La liste des pipelines est définie par la globale `spip_pipeline`
1276
 * qui a été remplie soit avec les fichiers d'options, soit avec 
1277
 * des descriptions de plugins (paquet.xml) dont celui de SPIP lui-même.
1278
 * 
1279
 * Les fichiers à charger pour accéder aux fonctions qui doivent traverser
1280
 * un pipeline se trouve dans la globale `spip_matrice`.
1281
 * 
1282
 * @see pipeline_matrice_precompile()
1283
 * 
1284
 * @uses ecrire_fichier_php()
1285
 * @uses clear_path_cache()
1286
 * 
1287
 * @param array $prepend_code
1288
 *     Code PHP à insérer avant le passage dans la chaîne des fonctions d'un pipeline
1289
 *     Couples 'Nom du pipeline' => Code PHP à insérer
1290
**/
1291
function pipeline_precompile($prepend_code = array()) {
1292
1293
	$all_pipes = $all_pipes_end = '';
1294
	if (!empty($GLOBALS['spip_pipeline']['all'])) {
1295
		$a = explode('||', $GLOBALS['spip_pipeline']['all'], 2);
1296
		unset($GLOBALS['spip_pipeline']['all']);
1297
		$all_pipes = trim(array_shift($a));
1298
		if ($all_pipes) {
1299
			$all_pipes = '|' . ltrim($a, '|');
1300
		}
1301
		if (count($a)) {
1302
			$all_pipes_end = '||' . array_shift($a);
1303
		}
1304
	}
1305
	$content = "";
1306
	foreach ($GLOBALS['spip_pipeline'] as $action => $pipeline) {
1307
		$s_inc = "";
1308
		$s_call = "";
1309
		if ($all_pipes) {
1310
			$pipeline = preg_replace(",(\|\||$),", "$all_pipes\\1", $pipeline, 1);
1311
		}
1312
		if ($all_pipes_end) {
1313
			$pipeline .= $all_pipes_end;
1314
		}
1315
		$pipe = array_filter(explode('|', $pipeline));
1316
		// Eclater le pipeline en filtres et appliquer chaque filtre
1317
		foreach ($pipe as $fonc) {
1318
			$fonc = trim($fonc);
1319
			$s_call .= '$val = minipipe(\'' . $fonc . '\', $val);' . "\n";
1320
			if (isset($GLOBALS['spip_matrice'][$fonc])) {
1321
				$file = $GLOBALS['spip_matrice'][$fonc];
1322
				$file = "'$file'";
1323
				// si un _DIR_XXX: est dans la chaine, on extrait la constante
1324
				if (preg_match(",(_(DIR|ROOT)_[A-Z_]+):,Ums", $file, $regs)) {
1325
					$dir = $regs[1];
1326
					$root_dir = str_replace('_DIR_', '_ROOT_', $dir);
1327
					if (defined($root_dir)) {
1328
						$dir = $root_dir;
1329
					}
1330
					$file = str_replace($regs[0], "'." . $dir . ".'", $file);
1331
					$file = str_replace("''.", "", $file);
1332
					$file = str_replace(constant($dir), '', $file);
1333
				}
1334
				$s_inc .= "include_once_check($file);\n";
1335
			}
1336
		}
1337
		if (strlen($s_inc)) {
1338
			$s_inc = "static \$inc=null;\nif (!\$inc){\n$s_inc\$inc=true;\n}\n";
1339
		}
1340
		$content .= "// Pipeline $action \n"
1341
			. "function execute_pipeline_$action(&\$val){\n"
1342
			. $s_inc
1343
			. ((isset($prepend_code[$action]) and strlen($prepend_code[$action])) ? trim($prepend_code[$action]) . "\n" : '')
1344
			. $s_call
1345
			. "return \$val;\n}\n";
1346
	}
1347
	ecrire_fichier_php(_CACHE_PIPELINES, $content);
1348
	clear_path_cache();
1349
}
1350
1351
1352
/**
1353
 * Indique si un chemin de plugin fait parti des plugins activés sur le site
1354
 *
1355
 * @param string $plug_path
1356
 *     Chemin du plugin
1357
 * @return bool
1358
 *     true si le plugin est actif, false sinon
1359
**/
1360
function plugin_est_installe($plug_path) {
1361
	$plugin_installes = isset($GLOBALS['meta']['plugin_installes']) ? unserialize($GLOBALS['meta']['plugin_installes']) : array();
1362
	if (!$plugin_installes) {
1363
		return false;
1364
	}
1365
1366
	return in_array($plug_path, $plugin_installes);
1367
}
1368
1369
1370
/**
1371
 * Parcours les plugins activés et appelle leurs fonctions d'installation si elles existent.
1372
 *
1373
 * Elle ajoute ensuite les plugins qui ont été installés dans la valeur "plugin_installes"
1374
 * de la table meta. Cette meta ne contient que les noms des plugins qui ont une version_base.
1375
 *
1376
 * @uses plugins_installer_dist()
1377
 **/
1378
function plugin_installes_meta() {
1379
	if (isset($GLOBALS['fichier_php_compile_recent'])) {
1380
		// attendre eventuellement l'invalidation du cache opcode
1381
		spip_attend_invalidation_opcode_cache($GLOBALS['fichier_php_compile_recent']);
1382
	}
1383
1384
	$installer_plugins = charger_fonction('installer', 'plugins');
1385
	$meta_plug_installes = array();
1386
	foreach (unserialize($GLOBALS['meta']['plugin']) as $prefix => $resume) {
1387
		if ($plug = $resume['dir']) {
1388
			$infos = $installer_plugins($plug, 'install', $resume['dir_type']);
1389
			if ($infos) {
1390
				if (!is_array($infos) or $infos['install_test'][0]) {
1391
					$meta_plug_installes[] = $plug;
1392
				}
1393
				if (is_array($infos)) {
1394
					list($ok, $trace) = $infos['install_test'];
1395
					include_spip('inc/filtres_boites');
1396
					echo "<div class='install-plugins svp_retour'>"
1397
						. boite_ouvrir(_T('plugin_titre_installation', array('plugin' => typo($infos['nom']))),
1398
							($ok ? 'success' : 'error'))
1399
						. $trace
1400
						. "<div class='result'>"
1401
						. ($ok ? ((isset($infos['upgrade']) && $infos['upgrade']) ? _T("plugin_info_upgrade_ok") : _T("plugin_info_install_ok")) : _T("avis_operation_echec"))
1402
						. "</div>"
1403
						. boite_fermer()
1404
						. "</div>";
1405
				}
1406
			}
1407
		}
1408
	}
1409
	ecrire_meta('plugin_installes', serialize($meta_plug_installes), 'non');
1410
}
1411
1412
/**
1413
 * Écrit un fichier PHP
1414
 *
1415
 * @param string $nom
1416
 *     Chemin du fichier
1417
 * @param string $contenu
1418
 *     Contenu du fichier (sans les balises ouvrantes et fermantes de PHP)
1419
 * @param string $comment
1420
 *     Commentaire : code écrit en tout début de fichier, après la balise PHP ouvrante
1421
**/
1422
function ecrire_fichier_php($nom, $contenu, $comment = '') {
1423
	if (!isset($GLOBALS['fichier_php_compile_recent'])) {
1424
		$GLOBALS['fichier_php_compile_recent'] = 0;
1425
	}
1426
1427
	$contenu = '<' . '?php' . "\n" . $comment . "\nif (defined('_ECRIRE_INC_VERSION')) {\n" . $contenu . "}\n?" . '>';
1428
	// si un fichier existe deja on verifie que son contenu change avant de l'ecraser
1429
	// si pas de modif on ne touche pas au fichier initial
1430
	if (file_exists($nom)) {
1431
		if (substr($nom, -4) == '.php') {
1432
			$fichier_tmp = substr($nom, 0, -4) . '.tmp.php';
1433
		}
1434
		else {
1435
			$fichier_tmp = $nom . '.tmp';
1436
		}
1437
		file_put_contents($fichier_tmp, $contenu);
1438
		if(md5_file($nom) == md5_file($fichier_tmp)) {
1439
			$GLOBALS['fichier_php_compile_recent'] = max($GLOBALS['fichier_php_compile_recent'], filemtime($nom));
1440
			@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...
1441
			return;
1442
		}
1443
		@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...
1444
	}
1445
	ecrire_fichier($nom, $contenu);
1446
	$GLOBALS['fichier_php_compile_recent'] = max($GLOBALS['fichier_php_compile_recent'], filemtime($nom));
1447
	spip_clear_opcode_cache(realpath($nom));
1448
}
1449