source: spip-zone/_core_/plugins/svp/inc/svp_phraser.php

Last change on this file was 123204, checked in by Cerdic, 5 months ago

Support des logos au format svg en provenance de l'empaqueteur, meme si le paquet.xml reference un png
+ support de la balise <logo> du zip et des logos des zips dans un sous-repertoire du depot

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