source: spip-zone/_plugins_/noizetier/trunk/inc/noizetier_page.php @ 110843

Last change on this file since 110843 was 110843, checked in by eric@…, 14 months ago

Correction et amélioration de la fonction de chargement des pages.
On évite de forcer le rechargement complet pour uniquement mettre à jour l'indicateur d'activité de la page.

  • Property svn:eol-style set to native
File size: 19.4 KB
Line 
1<?php
2/**
3 * Ce fichier contient l'API de gestion des pages et compositions configurables par le noiZetier.
4 *
5 * @package SPIP\NOIZETIER\PAGE\API
6 */
7if (!defined('_ECRIRE_INC_VERSION')) {
8        return;
9}
10
11
12/**
13 *
14 * @api
15 *
16 * @param bool $recharger
17 *
18 * @return bool
19 */
20function noizetier_page_charger($recharger = false) {
21
22        // Retour de la fonction
23        $retour = false;
24
25        // Initialiser les blocs par défaut
26        include_spip('inc/noizetier_bloc');
27        $options['blocs_defaut'] = noizetier_bloc_lister_defaut();
28
29        // Choisir le bon répertoire des pages
30        $options['repertoire_pages'] = noizetier_page_initialiser_dossier();
31
32        // Initialiser le contexte de rechargement
33        $forcer_chargement = $recharger;
34
35        // Initialiser la table et le where des pages non virtuelles qui sont utilisés plusieurs fois.
36        $from ='spip_noizetier_pages';
37        $where = array('est_virtuelle=' . sql_quote('non'));
38
39        // On recherche les pages et les compositions explicites par le fichier HTML en premier
40        // Si on le trouve, on récupère la configuration du fichier XML ou YAML.
41        if ($fichiers = find_all_in_path($options['repertoire_pages'], '.+[.]html$')) {
42                $pages_nouvelles = $pages_modifiees = $pages_obsoletes = array();
43                // Récupération :
44                // - des signatures md5 des pages déjà enregistrées pour déterminer si les fichiers YAML/XML
45                //   ont subi des changements.
46                // - des blocs exclus qui sont éditables après chargement, il faut donc conserver les modifications éventuelles.
47                // - des plugins nécessités et des indicateurs d'activité (voir fin de traitement).
48                $select = array('page', 'signature', 'blocs_exclus', 'necessite', 'est_active');
49                $signatures = $blocs_exclus = array();
50                if ($pages_existantes = sql_allfetsel($select, $from, $where)) {
51                        // On construit le tableau des blocs exclus de chaque page déjà enregistrée en base.
52                        $blocs_exclus = array_column($pages_existantes, 'blocs_exclus', 'page');
53
54                        // Si on force le rechargement il est inutile de gérer les signatures, les indicateurs d'activité
55                        // et les pages modifiées ou obsolètes.
56                        if (!$forcer_chargement) {
57                                $signatures = array_column($pages_existantes, 'signature', 'page');
58                                // On initialise la liste des pages à supprimer avec l'ensemble des pages non virtuelles
59                                $pages_obsoletes = $signatures ? array_keys($signatures) : array();
60                        }
61                }
62
63                foreach ($fichiers as $_squelette => $_chemin) {
64                        $page = basename($_squelette, '.html');
65                        $dossier = dirname($_chemin);
66                        $est_composition = (noizetier_page_extraire_composition($page) != '');
67                        // Exclure certaines pages :
68                        // -- celles du privé situes dans prive/contenu
69                        // -- page liée au plugin Zpip en v1
70                        // -- z_apl liée aux plugins Zpip v1 et Zcore
71                        // -- les compositions explicites si le plugin Compositions n'est pas activé
72                        if ((substr($dossier, -13) != 'prive/contenu')
73                        and (($page != 'page') or !defined('_DIR_PLUGIN_Z'))
74                        and (($page != 'z_apl') or (!defined('_DIR_PLUGIN_Z') and !defined('_DIR_PLUGIN_ZCORE')))
75                        and (!$est_composition or ($est_composition     and defined('_DIR_PLUGIN_COMPOSITIONS')))) {
76                                // On passe le md5 de la page si il existe sinon la chaîne vide. Cela permet de déterminer
77                                // si on doit ajouter la page ou la mettre à jour.
78                                // Si le md5 est le même et qu'il n'est donc pas utile de recharger la page, la configuration
79                                // retournée est vide.
80                                $options['md5'] = isset($signatures[$page]) ? $signatures[$page] : '';
81                                $options['recharger'] = $forcer_chargement;
82                                if ($configuration = page_phraser_fichier($page, $options)) {
83                                        if (empty($configuration['identique'])) {
84                                                // On met à jour les blocs exclus avec la sauvegarde effectuée au préalable (si la page
85                                                // existait déjà en base).
86                                                if (isset($blocs_exclus[$page])) {
87                                                        $configuration['blocs_exclus'] = $blocs_exclus[$page];
88                                                }
89                                                // On détermine si la page est nouvelle ou modifiée.
90                                                // En mode rechargement forcé toute page est considérée comme nouvelle.
91                                                if (!$options['md5'] or $forcer_chargement) {
92                                                        // La page est soit nouvelle soit on est en mode rechargement forcé:
93                                                        // => il faut la rajouter dans la table.
94                                                        $pages_nouvelles[] = $configuration;
95                                                } else {
96                                                        // La configuration stockée dans la table a été modifiée et pas de forçage du rechargement:
97                                                        // => il faut mettre à jour la page dans la table.
98                                                        $pages_modifiees[] = $configuration;
99                                                        // => il faut donc la supprimer de la liste des pages obsolètes
100                                                        $pages_obsoletes = array_diff($pages_obsoletes, array($page));
101                                                }
102                                        } else {
103                                                // La page n'a pas changée et n'a donc pas été réchargée:
104                                                // => Il faut donc juste indiquer qu'elle n'est pas obsolète.
105                                                $pages_obsoletes = array_diff($pages_obsoletes, array($page));
106                                        }
107                                } else {
108                                        // Il y a eu une erreur sur lors du rechargement de la page.
109                                        // Ce peut être en particulier le cas où une page HTML sans XML n'est plus détectée car le
110                                        // paramètre _NOIZETIER_LISTER_PAGES_SANS_XML a été positionné de true à false.
111                                        // => il faut donc ne rien faire pour laisser la page dans les obsolètes
112                                        continue;
113                                }
114                        }
115                }
116
117                // Mise à jour de la table des pages
118                // -- Suppression des pages obsolètes ou de toute les pages non virtuelles si on est en mode
119                //    rechargement forcé.
120                if (sql_preferer_transaction()) {
121                        sql_demarrer_transaction();
122                }
123                if ($pages_obsoletes) {
124                        sql_delete($from, sql_in('page', $pages_obsoletes));
125                } elseif ($forcer_chargement) {
126                        sql_delete($from, $where);
127                }
128                // -- Update des pages modifiées
129                if ($pages_modifiees) {
130                        sql_replace_multi($from, $pages_modifiees);
131                }
132                // -- Insertion des nouvelles pages
133                if ($pages_nouvelles) {
134                        sql_insertq_multi($from, $pages_nouvelles);
135                }
136                if (sql_preferer_transaction()) {
137                        sql_terminer_transaction();
138                }
139
140                // Pour les pages nouvelles ou modifiées, l'indicateur d'activité a été mis à jour.
141                // Mais ce n'est pas le cas pour les pages existantes dont le fichier YAML/XML n'a pas été modifié
142                // (en général, la plupart des pages).
143                // => Il faut donc mettre à jour l'indicateur d'activité pour ces pages.
144                $pages_exclues = $pages_modifiees ? array_column($pages_modifiees, 'page') : array();
145                foreach ($pages_existantes as $_page) {
146                        if (!$pages_exclues or !in_array($_page['page'], $pages_exclues)) {
147                                $est_active = 'oui';
148                                $plugins_necessites = unserialize($_page['necessite']);
149                                if ($plugins_necessites) {
150                                        foreach ($plugins_necessites as $_plugin_necessite) {
151                                                if (!defined('_DIR_PLUGIN_' . strtoupper($_plugin_necessite))) {
152                                                        $est_active = 'non';
153                                                        break;
154                                                }
155                                        }
156                                }
157
158                                if ($est_active != $_page['est_active']) {
159                                        sql_updateq($from, array('est_active' => $est_active), array('page=' . sql_quote($_page)));
160                                }
161                        }
162                }
163
164                $retour = true;
165        }
166
167        return $retour;
168}
169
170/**
171 * Retourne la configuration de la page, de la composition explicite ou de la composition virtuelle demandée.
172 * La configuration est stockée en base de données, certains champs sont recalculés avant d'être fournis.
173 *
174 * @api
175 *
176 * @uses noizetier_bloc_lister_defaut()
177 *
178 * @param string        $page
179 *              Identifiant de la page ou de la composition.
180 * @param boolean       $traitement_typo
181 *      Indique si les données textuelles doivent être retournées brutes ou si elles doivent être traitées
182 *      en utilisant la fonction _T_ou_typo.
183 *              Les champs sérialisés sont toujours désérialisés.
184 *
185 * @return array
186 */
187function noizetier_page_lire($page, $traitement_typo = true) {
188
189        static $description_page = array();
190
191        if (!isset($description_page[$traitement_typo][$page])) {
192                // Chargement de toute la configuration de la page en base de données.
193                $description = sql_fetsel('*', 'spip_noizetier_pages', array('page=' . sql_quote($page)));
194
195                // Sauvegarde de la description de la page pour une consultation ultérieure dans le même hit.
196                if ($description) {
197                        // Traitements des champs textuels
198                        if ($traitement_typo) {
199                                $description['nom'] = _T_ou_typo($description['nom']);
200                                if (isset($description['description'])) {
201                                        $description['description'] = _T_ou_typo($description['description']);
202                                }
203                        }
204                        // Traitements des champs tableaux sérialisés
205                        $description['blocs_exclus'] = unserialize($description['blocs_exclus']);
206                        $description['necessite'] = unserialize($description['necessite']);
207                        $description['branche'] = unserialize($description['branche']);
208                        // Calcul des blocs
209                        $description['blocs'] = noizetier_page_lister_blocs($page, $description['blocs_exclus']);
210                        $description_page[$traitement_typo][$page] = $description;
211                } else {
212                        $description_page[$traitement_typo][$page] = array();
213                }
214        }
215
216        return $description_page[$traitement_typo][$page];
217}
218
219
220
221/**
222 *
223 * @api
224 *
225 * @param       $page
226 * @param array $blocs_exclus
227 *
228 * @return array
229 */
230function noizetier_page_lister_blocs($page, $blocs_exclus = array()) {
231
232        // Initialisation des blocs avec la liste des blocs par défaut
233        include_spip('inc/noizetier_bloc');
234        $blocs = noizetier_bloc_lister_defaut();
235
236        // Si la liste des blocs exclus n'a pas été passé en argument on les cherche dans la configuration
237        // de la page
238        if (!$blocs_exclus) {
239                $where = array('page=' . sql_quote($page));
240                $blocs_exclus = sql_getfetsel('blocs_exclus', 'spip_noizetier_pages', $where);
241                $blocs_exclus = unserialize($blocs_exclus);
242        }
243
244        if ($blocs_exclus) {
245                $blocs = array_diff($blocs, $blocs_exclus);
246                sort($blocs);
247        }
248
249        return $blocs;
250}
251
252
253/**
254 * Renvoie le type d'une page à partir de son identifiant.
255 *
256 * @api
257 *
258 * @param string $page
259 *              L'identifiant de la page.
260 *
261 * @return string
262 *              Le type de la page choisie, c'est-à-dire:
263 *              - soit l'identifiant complet de la page,
264 *              - soit le mot précédent le tiret dans le cas d'une composition.
265 */
266function noizetier_page_extraire_type($page) {
267
268        $type = explode('-', $page, 2);
269        $type = $type[0];
270
271        return $type;
272}
273
274/**
275 * Détermine, à partir de son identifiant, la composition d'une page si elle existe.
276 *
277 * @api
278 *
279 * @param string $page
280 *              L'identifiant de la page.
281 *
282 * @return string
283 *      La composition de la page choisie, à savoir, le mot suivant le tiret,
284 *              ou la chaine vide sinon.
285 */
286function noizetier_page_extraire_composition($page) {
287
288        $composition = explode('-', $page, 2);
289        $composition = isset($composition[1]) ? $composition[1] : '';
290
291        return $composition;
292}
293
294/**
295 * Détermine si les compositions sont possibles sur un type de page.
296 *
297 * @api
298 *
299 * @param string $type
300 *              Identifiant du type de page.
301 *
302 * @return boolean
303 *              True si les compositions sont autorisées, false sinon.
304 */
305function noizetier_page_composition_activee($type) {
306
307        $est_activee = false;
308
309        if (defined('_DIR_PLUGIN_COMPOSITIONS')) {
310                include_spip('compositions_fonctions');
311                if (in_array($type, compositions_objets_actives())) {
312                        $est_activee = true;
313                }
314        }
315
316        return $est_activee;
317}
318
319/**
320 * Déterminer le répertoire dans lequel le NoiZetier peut lister les pages pouvant supporter
321 * l'insertion de noisettes.
322 *
323 * @api
324 *
325 * @return string
326 *              Le répertoire des pages sous la forme dossier/.
327 */
328function noizetier_page_initialiser_dossier() {
329
330        if (defined('_NOIZETIER_REPERTOIRE_PAGES')) {
331                $repertoire_pages = _NOIZETIER_REPERTOIRE_PAGES;
332        } elseif (isset($GLOBALS['z_blocs'])) {
333                $premier_bloc = reset($GLOBALS['z_blocs']);
334                $repertoire_pages = "$premier_bloc/";
335        } else {
336                $repertoire_pages = 'contenu/';
337        }
338
339        return $repertoire_pages;
340}
341
342/**
343 * Détermine, pour une page donnée, la liste des blocs ayant des noisettes incluses et renvoie leur nombre.
344 *
345 * @api
346 *
347 * @param string $page
348 *            L'identifiant de la page ou de la composition.
349 *
350 * @return array
351 *             Tableau des nombre de noisettes incluses par bloc de la forme [bloc] = nombre de noisettes.
352 */
353function noizetier_page_compter_noisettes($page) {
354
355        static $blocs_compteur = array();
356
357        if (!isset($blocs_compteur[$page])) {
358                // Initialisation des compteurs par bloc
359                $nb_noisettes = array();
360
361                // Le nombre de noisettes par bloc doit être calculé par une lecture de la table spip_noisettes.
362                $from = array('spip_noisettes');
363                $select = array('bloc', "count(type_noisette) as 'noisettes'");
364                // -- Construction du where identifiant précisément le type et la composition de la page
365                $where = array(
366                        'plugin=' . sql_quote('noizetier'),
367                        'type=' . sql_quote(noizetier_page_extraire_type($page)),
368                        'composition=' . sql_quote(noizetier_page_extraire_composition($page))
369                );
370                $group = array('bloc');
371                $blocs_non_vides = sql_allfetsel($select, $from, $where, $group);
372                if ($blocs_non_vides) {
373                        // On formate le tableau [bloc] = nb noisettes
374                        $nb_noisettes = array_column($blocs_non_vides, 'noisettes', 'bloc');
375                }
376
377                // Sauvegarde des compteurs pour les blocs concernés.
378                $blocs_compteur[$page] = $nb_noisettes;
379        }
380
381        return $blocs_compteur[$page];
382}
383
384/**
385 * Phrase le fichier XML ou YAML des pages et compositions configurables par le noiZetier et renvoie
386 * un tableau des caractéristiques complètes.
387 *
388 * @internal
389 *
390 * @uses noizetier_page_initialiser_dossier()
391 * @uses noizetier_bloc_lister_defaut()
392 *
393 * @param       $page
394 * @param array $options
395 *
396 * @return array
397 */
398function page_phraser_fichier($page, $options = array()) {
399
400        // Initialisation de la description
401        $description = array();
402
403        // Choisir le bon répertoire des pages
404        if (empty($options['repertoire_pages'])) {
405                $options['repertoire_pages'] = noizetier_page_initialiser_dossier();
406        }
407
408        // Initialiser les blocs par défaut
409        if (empty($options['blocs_defaut'])) {
410                include_spip('inc/noizetier_bloc');
411                $options['blocs_defaut'] = noizetier_bloc_lister_defaut();
412        }
413
414        // Initialiser le contexte de chargement
415        if (!isset($options['recharger'])) {
416                $options['recharger'] = false;
417        }
418        if (!isset($options['md5']) or $options['recharger']) {
419                $options['md5'] = '';
420        }
421
422        // Initialiser les composants de l'identifiant de la page:
423        // - type-composition si la page est une composition
424        // - type sinon
425        // On gère aussi le cas de Zpip v1 où page-xxxx désigne une page et non une composition.
426        // Dans ce cas, on doit donc obtenir type = xxxx et composition vide.
427        $identifiants = explode('-', $page);
428        if (!isset($identifiants[1])) {
429                $identifiants[1] = '';
430        } elseif ($identifiants[0] == 'page') {
431                $identifiants[0] = $identifiants[1];
432                $identifiants[1] = '';
433        }
434
435        // Initialisation de la description par défaut de la page
436        $description_defaut = array(
437                'page'           => $page,
438                'type'           => $identifiants[0],
439                'composition'    => $identifiants[1],
440                'nom'            => $page,
441                'description'    => '',
442                'icon'           => 'page-24.png',
443                'blocs_exclus'   => array(),
444                'necessite'      => array(),
445                'est_active'     => 'oui',
446                'branche'        => array(),
447                'est_virtuelle'  => 'non',
448                'est_page_objet' => 'non',
449                'signature'      => '',
450        );
451
452        // Recherche des pages ou compositions explicites suivant le processus :
453        // a- Le fichier YAML est recherché en premier,
454        // b- ensuite le fichier XML pour compatibilité ascendante.
455        // c- enfin, si il n'y a ni YAML, ni XML et que le mode le permet, on renvoie une description standard minimale
456        //    basée sur le fichier HTML uniquement
457        $md5 = '';
458        if ($fichier = find_in_path("{$options['repertoire_pages']}${page}.yaml")) {
459                // 1a- il y a un fichier YAML de configuration, on vérifie le md5 avant de charger le contenu.
460                //     Un YAML de page ne peut pas contenir d'inclusion YAML.
461                $md5 = md5_file($fichier);
462                if ($md5 != $options['md5']) {
463                        include_spip('inc/yaml');
464                        $description = yaml_decode_file($fichier);
465                }
466        } elseif ($fichier = find_in_path("{$options['repertoire_pages']}${page}.xml")) {
467                // 1b- il y a un fichier XML de configuration, on vérifie le md5 avant de charger le contenu.
468                //     on extrait et on parse le XML de configuration en tenant compte que ce peut être
469                //     celui d'une page ou d'une composition, ce qui change la balise englobante.
470                $md5 = md5_file($fichier);
471                if ($md5 != $options['md5']) {
472                        include_spip('inc/xml');
473                        if ($xml = spip_xml_load($fichier, false)
474                        and (isset($xml['page']) or isset($xml['composition']))) {
475                                $xml = isset($xml['page']) ? reset($xml['page']) : reset($xml['composition']);
476                                // Titre (nom), description et icone
477                                if (isset($xml['nom'])) {
478                                        $description['nom'] = spip_xml_aplatit($xml['nom']);
479                                }
480                                if (isset($xml['description'])) {
481                                        $description['description'] = spip_xml_aplatit($xml['description']);
482                                }
483                                if (isset($xml['icon'])) {
484                                        $description['icon'] = reset($xml['icon']);
485                                }
486
487                                // Liste des blocs autorisés pour la page. On vérifie que les blocs configurés sont bien dans
488                                // la liste des blocs par défaut et on calcule les blocs exclus qui sont les seuls insérés en base.
489                                $blocs_inclus = array();
490                                if (spip_xml_match_nodes(',^bloc,', $xml, $blocs)) {
491                                        foreach (array_keys($blocs) as $_bloc) {
492                                                list(, $attributs) = spip_xml_decompose_tag($_bloc);
493                                                $blocs_inclus[] = $attributs['id'];
494                                        }
495                                }
496                                if ($blocs_inclus) {
497                                        $description['blocs_exclus'] = array_diff($options['blocs_defaut'], array_intersect($options['blocs_defaut'], $blocs_inclus));
498                                }
499
500                                // Liste des plugins nécessaires pour utiliser la page
501                                if (spip_xml_match_nodes(',^necessite,', $xml, $necessites)) {
502                                        $description['necessite'] = array();
503                                        foreach (array_keys($necessites) as $_necessite) {
504                                                list(, $attributs) = spip_xml_decompose_tag($_necessite);
505                                                $description['necessite'][] = $attributs['id'];
506                                        }
507                                }
508
509                                // Liste des héritages
510                                if (spip_xml_match_nodes(',^branche,', $xml, $branches)) {
511                                        $description['branche'] = array();
512                                        foreach (array_keys($branches) as $_branche) {
513                                                list(, $attributs) = spip_xml_decompose_tag($_branche);
514                                                $description['branche'][$attributs['type']] = $attributs['composition'];
515                                        }
516                                }
517                        }
518                }
519        } elseif (defined('_NOIZETIER_LISTER_PAGES_SANS_XML') ? _NOIZETIER_LISTER_PAGES_SANS_XML : true) {
520                // 1c- il est autorisé de ne pas avoir de fichier XML de configuration.
521                // Ces pages sans XML ne sont chargées qu'une fois, la première. Ensuite, aucune mise à jour n'est nécessaire.
522                if (!$options['md5']) {
523                        $description['icon'] = 'page_noxml-24.png';
524                        $md5 = md5('_NOIZETIER_LISTER_PAGES_SANS_XML');
525                }
526        }
527
528        // Si la description est remplie c'est que le chargement a correctement eu lieu.
529        // Sinon, si la page n'a pas changée on renvoie une description limitée à un indicateur d'identité pour
530        // distinguer ce cas avec une erreur de chargement qui renvoie une description vide.
531        if ($description) {
532                // Mise à jour du md5
533                $description['signature'] = $md5;
534                // Identifie si la page est celle d'un objet SPIP
535                include_spip('base/objets');
536                $tables_objets = array_keys(lister_tables_objets_sql());
537                $description['est_page_objet'] = in_array(table_objet_sql($description_defaut['type']), $tables_objets) ? 'oui' : 'non';
538                // Complétude de la description avec les valeurs par défaut
539                $description = array_merge($description_defaut, $description);
540                // Traitement des necessite pour identifier l'activité de la page
541                $description['est_active'] = 'oui';
542                if ($description['necessite']) {
543                        foreach ($description['necessite'] as $_plugin_necessite) {
544                                if (!defined('_DIR_PLUGIN_' . strtoupper($_plugin_necessite))) {
545                                        $description['est_active'] = 'non';
546                                        break;
547                                }
548                        }
549                }
550                // Sérialisation des champs blocs_exclus, necessite et branche qui sont des tableaux
551                $description['blocs_exclus'] = serialize($description['blocs_exclus']);
552                $description['necessite'] = serialize($description['necessite']);
553                $description['branche'] = serialize($description['branche']);
554        } elseif ($md5 == $options['md5']) {
555                $description['identique'] = true;
556        }
557
558        return $description;
559}
Note: See TracBrowser for help on using the repository browser.