[SPIP] v3.2.7-->v3.2.9
[lhc/web/www.git] / www / plugins-dist / svp / inc / svp_phraser.php
1 <?php
2
3 /**
4 * Fichier permettant de phraser les XML des fichiers paquet.xml et plugin.xml
5 * ainsi que des fichiers décrivant le contenu d'un dépot de paquets.
6 *
7 * @plugin SVP pour SPIP
8 * @license GPL
9 * @package SPIP\SVP\Plugins
10 **/
11
12 if (!defined("_ECRIRE_INC_VERSION")) {
13 return;
14 }
15
16 include_spip('inc/xml');
17 include_spip('inc/config');
18
19 if (!defined('_SVP_MODE_RUNTIME')) {
20 if (defined('_DEV_VERSION_SPIP_COMPAT')) {
21 /**
22 * Mode d'utilisation de SVP runtime ou pas :
23 * - En mode runtime (true), on ne charge que les plugins compatibles avec la version courante
24 * - En mode non runtime (false) on charge tous les plugins : cas du site Plugins SPIP
25 * Runtime est le mode par defaut
26 *
27 * @var bool
28 */
29 define('_SVP_MODE_RUNTIME', false);
30 } else {
31 define('_SVP_MODE_RUNTIME', (lire_config('svp/mode_runtime', 'oui') == 'oui' ? true : false));
32 }
33 }
34
35
36 // Type parseur XML à appliquer pour récupérer les infos du plugin
37 /** @var string Phraseur à utiliser pour un XML de plugin.xml */
38 define('_SVP_DTD_PLUGIN', 'plugin');
39 /** @var string Phraseur à utiliser pour un XML de paquet.xml */
40 define('_SVP_DTD_PAQUET', 'paquet');
41
42 // Regexp de recherche des balises principales de archives.xml
43 define('_SVP_REGEXP_BALISE_DEPOT', '#<depot[^>]*>(.*)</depot>#Uims');
44 define('_SVP_REGEXP_BALISE_ARCHIVES', '#<archives[^>]*>(.*)</archives>#Uims');
45 define('_SVP_REGEXP_BALISE_ARCHIVE', '#<archive[^s][^>]*>(.*)</archive>#Uims');
46 define('_SVP_REGEXP_BALISE_ZIP', '#<zip[^>]*>(.*)</zip>#Uims');
47 define('_SVP_REGEXP_BALISE_TRADUCTIONS', '#<traductions[^>]*>(.*)</traductions>#Uims');
48 define('_SVP_REGEXP_BALISE_PLUGIN', '#<plugin[^>]*>(.*)</plugin>#Uims');
49 define('_SVP_REGEXP_BALISE_PAQUET', '#<paquet[^>]*>(.*)</paquet>#Uims');
50 define('_SVP_REGEXP_BALISE_MULTIS', '#<multis[^>]*>(.*)</multis>#Uims');
51
52
53 // Liste des categories de plugin
54 # define('_CATEGORIES_PLUGIN', serialize($categories_plugin));
55 $GLOBALS['categories_plugin'] = array(
56 'communication',
57 'edition',
58 'multimedia',
59 'navigation',
60 'date',
61 'divers',
62 'auteur',
63 'statistique',
64 'performance',
65 'maintenance',
66 'outil',
67 'theme',
68 'squelette',
69 'aucune'
70 );
71
72 /** Liste des balises techniques autorisées dans la balise <spip> */
73 $GLOBALS['balises_techniques'] = array(
74 'menu',
75 'chemin',
76 'lib',
77 'necessite',
78 'onglet',
79 'procure',
80 'pipeline',
81 'utilise',
82 'options',
83 'fonctions',
84 'install'
85 );
86 # define('_BALISES_TECHNIQUES', serialize($balises_techniques));
87
88 /** Liste des balises autorisant une traduction */
89 $GLOBALS['balises_multis'] = array(
90 'nom',
91 'slogan',
92 'description'
93 );
94 # define('_BALISES_MULTIS', serialize($balises_multis));
95
96
97 /**
98 * Phrase un fichier décrivant un dépot, dont le chemin local est donné
99 *
100 * Le fichier est au format XML et contient deux balises principales :
101 * - <depot>...</depot> : informations de description du depot (facultatif)
102 * - <archives>...</archives> : liste des informations sur chaque archive (obligatoire)
103 *
104 * La fonction met en cache le résultat du phrasage de chaque archive et ne
105 * rephrase que les archives ayant changées.
106 *
107 * @uses svp_aplatir_balises()
108 * @uses svp_phraser_archives()
109 * @param string $fichier_xml
110 * Chemin local du fichier XML de description du dépot
111 * @return array|bool
112 * false si erreur,
113 * Tableau de 2 index sinon :
114 * - depot : description du dépot
115 * - paquets :
116 */
117 function svp_phraser_depot($fichier_xml) {
118
119 // le fichier xml fournit sous forme de fichier
120 lire_fichier($fichier_xml, $xml);
121
122 // Initialisation du tableau des informations
123 // -- Si aucun bloc depot n'est trouve le titre et le type prennent une valeur par defaut
124 $infos = array(
125 'depot' => array(
126 'titre' => _T('svp:titre_nouveau_depot'),
127 'type' => 'manuel'
128 ),
129 'paquets' => array()
130 );
131
132
133 // Extraction et phrasage du bloc depot si il existe
134 // -- Si le bloc <depot> n'est pas renseigne on ne considere pas cela comme une erreur
135 $balises_depot = array('titre', 'descriptif', 'type', 'url_serveur', 'url_brouteur', 'url_archives', 'url_commits');
136 if (preg_match(_SVP_REGEXP_BALISE_DEPOT, $xml, $matches)) {
137 if (is_array($arbre_depot = spip_xml_parse($matches[1]))) {
138 $infos['depot'] = svp_aplatir_balises($balises_depot, $arbre_depot, 'nonvide', $infos['depot']);
139 }
140 }
141
142 // Extraction et phrasage du bloc des archives si il existe
143 // -- Le bloc <archives> peut etre une chaine de grande taille et provoquer une erreur
144 // sur une recherche de regexp. On ne teste donc pas l'existence de cette balise
145 // -- Si aucun bloc <archive> c'est aussi une erreur
146 if (!preg_match_all(_SVP_REGEXP_BALISE_ARCHIVE, $xml, $matches)) {
147 return false;
148 }
149
150 // lire le cache des md5 pour ne parser que ce qui a change
151 $fichier_xml_md5 = $fichier_xml . ".md5.txt";
152 lire_fichier($fichier_xml_md5, $cache_md5);
153 if (!$cache_md5
154 or !$cache_md5 = unserialize($cache_md5)
155 ) {
156 $cache_md5 = array();
157 }
158
159 $infos['paquets'] = svp_phraser_archives($matches[0], $cache_md5);
160 ecrire_fichier($fichier_xml_md5, serialize($cache_md5));
161
162 // -- Si aucun paquet extrait c'est aussi une erreur
163 if (!$infos['paquets']) {
164 return false;
165 }
166
167 return $infos;
168 }
169
170
171 /**
172 * Phrase la liste des balises <archive>
173 *
174 * Chaque bloc XML est constitue de 3 sous-blocs principaux :
175 * - <zip> : contient les balises d'information sur le zip (obligatoire)
176 * - <traductions> : contient la compilation des informations de traduction (facultatif)
177 * - <plugin> ou <paquet> suivant la DTD : le contenu du fichier plugin.xml ou paquet.xml (facultatif)
178 *
179 * @uses svp_phraser_zip()
180 * @uses svp_phraser_traductions()
181 * @uses svp_phraser_plugin()
182 * @uses plugin_version_compatible()
183 * @param array $archives
184 * Tableau de la liste des archives trouvées dans la description d'un dépot
185 * @param array $md5_cache
186 * Tableau des descriptions d'archives déjà connues : on supprime
187 * à la fin celles qui ne font plus parties du dépot.
188 * @return array
189 * Tableau décrivant chaque archive, avec en index l'url de l'archive.
190 * Tableau (url => Tableau de description de l'archive)
191 */
192 function svp_phraser_archives($archives, &$md5_cache = array()) {
193 include_spip('inc/plugin');
194 $seen = array();
195
196 $paquets = array();
197 $version_spip = $GLOBALS['spip_version_branche'] . "." . $GLOBALS['spip_version_code'];
198
199 // On verifie qu'il existe au moins une archive
200 if (!$archives) {
201 return $paquets;
202 }
203
204 // On phrase chacune des archives
205 // Seul le bloc <zip> est obligatoire
206 foreach ($archives as $_cle => $_archive) {
207 // quand version spip ou mode runtime changent,
208 // il faut mettre le xml a jour pour voir les plugins compatibles ou non
209 $md5 = md5($_archive . ":$version_spip:" . _SVP_MODE_RUNTIME);
210 if (isset($md5_cache[$md5])) {
211 if (is_array($p = $md5_cache[$md5])) {
212 $paquets[$p['file']] = $p;
213 } // ce paquet est connu
214 $seen[] = $md5;
215 } elseif (preg_match(_SVP_REGEXP_BALISE_ZIP, $_archive, $matches)) {
216
217 // Extraction de la balise <zip>
218 $zip = svp_phraser_zip($matches[1]);
219
220 if ($zip) {
221
222 // Extraction de la balise traductions
223 $traductions = array();
224 if (preg_match(_SVP_REGEXP_BALISE_TRADUCTIONS, $_archive, $matches)) {
225 $traductions = svp_phraser_traductions($matches[1]);
226 }
227
228
229 // La balise <archive> peut posseder un attribut qui precise la DTD utilisee pour les plugins (plugin ou paquet)
230 // Sinon, c'est la DTD plugin qui est utilisee
231 list($tag, $attributs) = spip_xml_decompose_tag($_archive);
232 // -- On stocke la DTD d'extraction des infos du plugin
233 $dtd = (isset($attributs['dtd']) and $attributs['dtd']) ? $attributs['dtd'] : _SVP_DTD_PLUGIN;
234
235 // Extraction *des balises* plugin ou *de la balise* paquet suivant la DTD et la version SPIP
236 // -- DTD : si on utilise plugin.xml on extrait la balise <plugin> sinon la balise <paquet>
237 $xml = svp_phraser_plugin($dtd, $_archive);
238
239 // Si on est en mode runtime, on est seulement interesse par les plugins compatibles avec
240 // la version courant de SPIP. On ne stocke donc pas les autres plugins.
241 // Si on est pas en mode runtime on prend tout !
242 if (!_SVP_MODE_RUNTIME
243 or (_SVP_MODE_RUNTIME and isset($xml['compatibilite']) and plugin_version_compatible($xml['compatibilite'],
244 $version_spip, 'spip'))
245 ) {
246 $paquets[$zip['file']] = $zip;
247 $paquets[$zip['file']]['traductions'] = $traductions;
248 $paquets[$zip['file']]['dtd'] = $dtd;
249 $paquets[$zip['file']]['plugin'] = $xml;
250 $paquets[$zip['file']]['md5'] = $md5;
251 $md5_cache[$md5] = $paquets[$zip['file']];
252 $seen[] = $md5;
253 } else {
254 $md5_cache[$md5] = $zip['file'];
255 $seen[] = $md5;
256 }
257 }
258 }
259 }
260
261 // supprimer du cache les zip qui ne sont pas dans le nouveau $archives
262 $oldies = array_diff(array_keys($md5_cache), $seen);
263 foreach ($oldies as $old_md5) {
264 unset($md5_cache[$old_md5]);
265 }
266
267 return $paquets;
268 }
269
270
271 /**
272 * Phrase le contenu du XML décrivant une archive suivant une DTD
273 * de plugin.xml ou de paquet.xml donnée
274 *
275 * La fonction peut-être appelée via archives.xml ou via un xml de plugin.
276 * Elle phrase la balise <multi> dans le cas d'une DTD paquet qui contient
277 * les traductions du nom, slogan et description
278 *
279 * @uses svp_aplatir_balises()
280 *
281 * @global $balises_multis
282 * @static array $informer
283 *
284 * @param string $dtd
285 * Nom du type de dtd : plugin ou paquet (pour phraser un plugin.xml ou un paquet.xml)
286 * @param string $contenu
287 * Contenu XML à phraser
288 * @return array
289 * Description du plugin
290 **/
291 function svp_phraser_plugin($dtd, $contenu) {
292 global $balises_multis;
293 static $informer = array();
294
295 $plugin = array();
296
297 // On initialise les informations du plugin avec le contenu du plugin.xml ou paquet.xml
298 $regexp = ($dtd == 'plugin') ? _SVP_REGEXP_BALISE_PLUGIN : _SVP_REGEXP_BALISE_PAQUET;
299 if ($nb_balises = preg_match_all($regexp, $contenu, $matches)) {
300 $plugins = array();
301 // Pour chacune des occurences de la balise on extrait les infos
302 foreach ($matches[0] as $_balise_plugin) {
303 // Extraction des informations du plugin suivant le standard SPIP
304 if (!isset($informer[$dtd])) {
305 $informer[$dtd] = charger_fonction('infos_' . $dtd, 'plugins');
306 }
307 $plugins[] = $informer[$dtd]($_balise_plugin);
308 }
309
310 // On appelle systematiquement une fonction de mise a jour de la structure de donnees du plugin :
311 // -- Si DTD plugin et que le nombre de balises plugin > 1 ou si DTD paquet avec une presence de balise spip
312 // alors on fusionne donc les informations recoltees
313 // -- sinon on arrange la structure pour deplacer le contenu des balises dites techniques dans un sous tableau
314 // d'index 0 par similitude avec la structure fusionnee
315 $fusionner = charger_fonction('fusion_' . $dtd, 'plugins');
316 if ($dtd == 'plugin') {
317 $plugin = $fusionner($plugins);
318 } else {
319 $plugin = $fusionner($plugins[0]);
320 }
321
322 // Pour la DTD paquet, les traductions du nom, slogan et description sont compilees dans une balise
323 // du fichier archives.xml. Il faut donc completer les informations precedentes avec cette balise
324 if (($dtd == _SVP_DTD_PAQUET) and (preg_match(_SVP_REGEXP_BALISE_MULTIS, $contenu, $matches))) {
325 $multis = array();
326 if (is_array($arbre = spip_xml_parse($matches[1]))) {
327 $multis = svp_aplatir_balises($balises_multis, $arbre);
328 }
329 // Le nom peut etre traduit ou pas, il faut donc le tester
330 if ($multis['nom']) {
331 $plugin['nom'] = $multis['nom'];
332 }
333 // Slogan et description sont forcement des items de langue
334 $plugin['slogan'] = $multis['slogan'];
335 $plugin['description'] = $multis['description'];
336 }
337 }
338
339 return $plugin;
340 }
341
342
343 /**
344 * Phrase le contenu de la balise <zip>
345 *
346 * Extrait du XML les informations du zip
347 *
348 * @uses svp_aplatir_balises()
349 *
350 * @param string $contenu
351 * Description XML de l'archive
352 * @return array
353 * Description du zip.
354 * - Index 'file' : nom du zip
355 * - Index 'size' : taille
356 * - Index 'date' : date de création
357 * - Index 'last_commit' : date du dernier commit
358 * - Index 'source' : arborescence relative des sources
359 * - Index 'logo' : nom du logo
360 */
361 function svp_phraser_zip($contenu) {
362 static $balises_zip = array('file', 'size', 'date', 'source', 'last_commit', 'logo');
363
364 $zip = array();
365 if (is_array($arbre = spip_xml_parse($contenu))) {
366 $zip = svp_aplatir_balises($balises_zip, $arbre);
367 }
368
369 return $zip;
370 }
371
372
373 /**
374 * Phrase le contenu d'une balise <traductions> en un tableau plus
375 * facilement utilisable
376 *
377 * @param string $contenu
378 * Contenu XML de la balise <traductions>
379 * @return array
380 * Tableau complexe avec pour index les noms des modules de langue et pour
381 * valeur leur description. Chaque description contient dedans 3 index :
382 * - reference : la langue de référence
383 * - gestionnaire : quel logiciel à servi à gérer les traductions
384 * - langues : tableau classé par langue puis par traducteurs, qui indique
385 * l'ensemble des traducteurs pour chacune des langues présentes
386 */
387 function svp_phraser_traductions($contenu) {
388
389 $traductions = array();
390 if (is_array($arbre = spip_xml_parse($contenu))) {
391 foreach ($arbre as $_tag => $_langues) {
392 // On commence par les balises <traduction> et leurs attributs
393 list($tag, $attributs_traduction) = spip_xml_decompose_tag($_tag);
394 $traductions[$attributs_traduction['module']]['reference'] = $attributs_traduction['reference'];
395 $traductions[$attributs_traduction['module']]['gestionnaire'] = isset($attributs_traduction['gestionnaire']) ? $attributs_traduction['gestionnaire'] : '';
396
397 // On continue par les balises <langue> qui donnent le code en attribut
398 // et les balises <traducteur> qui donnent uniquement le nom en attribut
399 if (is_array($_langues[0])) {
400 foreach ($_langues[0] as $_tag => $_traducteurs) {
401 list($tag, $attributs_langue) = spip_xml_decompose_tag($_tag);
402 $traducteurs = array();
403 if (is_array($_traducteurs[0])) {
404 foreach ($_traducteurs[0] as $_tag => $_vide) {
405 list($tag, $attributs_traducteur) = spip_xml_decompose_tag($_tag);
406 $traducteurs[] = $attributs_traducteur;
407 }
408 }
409 $traductions[$attributs_traduction['module']]['langues'][$attributs_langue['code']] = $traducteurs;
410 }
411 }
412 }
413 }
414
415 return $traductions;
416 }
417
418
419 /**
420 * Aplatit plusieurs clés d'un arbre xml dans un tableau
421 *
422 * Effectue un trim() de la valeur trouvée dans l'arbre
423 *
424 * @uses spip_xml_aplatit()
425 *
426 * @param array $balises
427 * Liste de noms de balises XML.
428 * Peut aussi être un tableau indiquant un renommage d'une balise
429 * au passage tel que 'x' => 'y' qui cherchera x dans l'arbre XML et
430 * l'applatira dans y.
431 * @param array $arbre_xml
432 * Un arbre issu de spip_xml_parse()
433 * @param string $mode
434 * Mode d'affectation des valeurs trouvées
435 * - 'vide_et_nonvide' : Affecte une chaine vide si la balise n'est
436 * pas trouvée dans l'arbre et affecte la valeur de la balise sinon.
437 * - 'nonvide' : Si la balise n'est pas trouvée dans l'arbre ou si son
438 * contenu est vide, affecte la valeur du tableau initial concernant
439 * cette balise si elle est connue.
440 * @param array $tableau_initial
441 * Tableau initial pouvant contenir des valeurs par défaut à affecter
442 * à chaque balise avec 'x' => 'valeur'
443 */
444 function svp_aplatir_balises($balises, $arbre_xml, $mode = 'vide_et_nonvide', $tableau_initial = array()) {
445 $tableau_aplati = array();
446
447 if (!$balises) {
448 return $tableau_initial;
449 }
450
451 foreach ($balises as $_cle => $_valeur) {
452 $tag = (is_string($_cle)) ? $_cle : $_valeur;
453 $valeur_aplatie = '';
454 if (isset($arbre_xml[$tag])) {
455 $valeur_aplatie = trim(spip_xml_aplatit($arbre_xml[$tag]));
456 }
457 if (($mode == 'vide_et_nonvide')
458 or (($mode == 'nonvide') and $valeur_aplatie)
459 ) {
460 $tableau_aplati[$_valeur] = $valeur_aplatie;
461 } else {
462 $tableau_aplati[$_valeur] = isset($tableau_initial[$_valeur]) ? $tableau_initial[$_valeur] : '';
463 }
464 }
465
466 return $tableau_aplati;
467 }