svp_phraser.php 15 KB

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