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