3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
6 * Copyright (c) 2001-2019 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
14 * Ce fichier regroupe les fonctions permettant de calculer la page et les entĂȘtes
16 * Determine le contexte donne par l'URL (en tenant compte des reecritures)
17 * grace a la fonction de passage d'URL a id (reciproque dans urls/*php)
19 * @package SPIP\Core\Compilateur\Assembler
22 if (!defined('_ECRIRE_INC_VERSION')) {
26 if (!defined('_CONTEXTE_IGNORE_VARIABLES')) {
27 define('_CONTEXTE_IGNORE_VARIABLES', "/(^var_|^PHPSESSID$|^fbclid$|^utm_)/");
30 // http://code.spip.net/@assembler
31 function assembler($fond, $connect = '') {
33 // flag_preserver est modifie ici, et utilise en globale
34 // use_cache sert a informer le bouton d'admin pr savoir s'il met un *
35 // contexte est utilise en globale dans le formulaire d'admin
37 $GLOBALS['contexte'] = calculer_contexte();
38 $page = array('contexte_implicite' => calculer_contexte_implicite());
39 $page['contexte_implicite']['cache'] = $fond . preg_replace(',\.[a-zA-Z0-9]*$,', '',
40 preg_replace('/[?].*$/', '', $GLOBALS['REQUEST_URI']));
41 // Cette fonction est utilisee deux fois
42 $cacher = charger_fonction('cacher', 'public', true);
43 // Les quatre derniers parametres sont modifies par la fonction:
44 // emplacement, validite, et, s'il est valide, contenu & age
46 $res = $cacher($GLOBALS['contexte'], $GLOBALS['use_cache'], $chemin_cache, $page, $lastmodified);
48 $GLOBALS['use_cache'] = -1;
50 // Si un resultat est retourne, c'est un message d'impossibilite
52 return array('texte' => $res);
55 if (!$chemin_cache ||
!$lastmodified) {
56 $lastmodified = time();
59 $headers_only = ($_SERVER['REQUEST_METHOD'] == 'HEAD');
60 $calculer_page = true;
62 // Pour les pages non-dynamiques (indiquees par #CACHE{duree,cache-client})
63 // une perennite valide a meme reponse qu'une requete HEAD (par defaut les
64 // pages sont dynamiques)
65 if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
66 and (!defined('_VAR_MODE') or !_VAR_MODE
)
68 and isset($page['entetes'])
69 and isset($page['entetes']['Cache-Control'])
70 and strstr($page['entetes']['Cache-Control'], 'max-age=')
71 and !strstr($_SERVER['SERVER_SOFTWARE'], 'IIS/')
73 $since = preg_replace('/;.*/', '',
74 $_SERVER['HTTP_IF_MODIFIED_SINCE']);
75 $since = str_replace('GMT', '', $since);
76 if (trim($since) == gmdate("D, d M Y H:i:s", $lastmodified)) {
77 $page['status'] = 304;
79 $calculer_page = false;
83 // Si requete HEAD ou Last-modified compatible, ignorer le texte
84 // et pas de content-type (pour contrer le bouton admin de inc-public)
85 if (!$calculer_page) {
88 // si la page est prise dans le cache
89 if (!$GLOBALS['use_cache']) {
90 // Informer les boutons d'admin du contexte
91 // (fourni par urls_decoder_url ci-dessous lors de la mise en cache)
92 $GLOBALS['contexte'] = $page['contexte'];
94 // vider les globales url propres qui ne doivent plus etre utilisees en cas
95 // d'inversion url => objet
96 // plus necessaire si on utilise bien la fonction urls_decoder_url
97 #unset($_SERVER['REDIRECT_url_propre']);
98 #unset($_ENV['url_propre']);
100 // Compat ascendante :
101 // 1. $contexte est global
102 // (a evacuer car urls_decoder_url gere ce probleme ?)
103 // et calculer la page
104 if (!test_espace_prive()) {
105 include_spip('inc/urls');
106 list($fond, $GLOBALS['contexte'], $url_redirect) = urls_decoder_url(nettoyer_uri(), $fond, $GLOBALS['contexte'],
109 // squelette par defaut
110 if (!strlen($fond)) {
114 // produire la page : peut mettre a jour $lastmodified
115 $produire_page = charger_fonction('produire_page', 'public');
116 $page = $produire_page($fond, $GLOBALS['contexte'], $GLOBALS['use_cache'], $chemin_cache, null, $page,
117 $lastmodified, $connect);
119 $erreur = _T('info_erreur_squelette2',
120 array('fichier' => spip_htmlspecialchars($fond) . '.' . _EXTENSION_SQUELETTES
));
121 erreur_squelette($erreur);
122 // eviter des erreurs strictes ensuite sur $page['cle'] en PHP >= 5.4
123 $page = array('texte' => '', 'erreur' => $erreur);
127 if ($page and $chemin_cache) {
128 $page['cache'] = $chemin_cache;
131 auto_content_type($page);
133 $GLOBALS['flag_preserver'] |
= headers_sent();
135 // Definir les entetes si ce n'est fait
136 if (!$GLOBALS['flag_preserver']) {
137 if ($GLOBALS['flag_ob']) {
138 // Si la page est vide, produire l'erreur 404 ou message d'erreur pour les inclusions
139 if (trim($page['texte']) === ''
140 and _VAR_MODE
!= 'debug'
141 and !isset($page['entetes']['Location']) // cette page realise une redirection, donc pas d'erreur
143 $GLOBALS['contexte']['fond_erreur'] = $fond;
144 $page = message_page_indisponible($page, $GLOBALS['contexte']);
146 // pas de cache client en mode 'observation'
147 if (defined('_VAR_MODE') and _VAR_MODE
) {
148 $page['entetes']["Cache-Control"] = "no-cache,must-revalidate";
149 $page['entetes']["Pragma"] = "no-cache";
155 // Entete Last-Modified:
156 // eviter d'etre incoherent en envoyant un lastmodified identique
157 // a celui qu'on a refuse d'honorer plus haut (cf. #655)
159 and !isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
160 and !isset($page['entetes']["Last-Modified"])
162 $page['entetes']["Last-Modified"] = gmdate("D, d M Y H:i:s", $lastmodified) . " GMT";
165 // fermer la connexion apres les headers si requete HEAD
167 $page['entetes']["Connection"] = "close";
174 * Calcul le contexte de la page
176 * lors du calcul d'une page spip etablit le contexte a partir
177 * des variables $_GET et $_POST, purgees des fausses variables var_*
179 * Note : pour hacker le contexte depuis le fichier d'appel (page.php),
180 * il est recommande de modifier $_GET['toto'] (meme si la page est
181 * appelee avec la methode POST).
183 * http://code.spip.net/@calculer_contexte
185 * @return array Un tableau du contexte de la page
187 function calculer_contexte() {
190 foreach ($_GET as $var => $val) {
191 if (!preg_match(_CONTEXTE_IGNORE_VARIABLES
, $var)) {
192 $contexte[$var] = $val;
195 foreach ($_POST as $var => $val) {
196 if (!preg_match(_CONTEXTE_IGNORE_VARIABLES
, $var)) {
197 $contexte[$var] = $val;
205 * Calculer le contexte implicite, qui n'apparait pas dans le ENV d'un cache
206 * mais est utilise pour distinguer deux caches differents
208 * @staticvar string $notes
211 function calculer_contexte_implicite() {
212 static $notes = null;
213 if (is_null($notes)) {
214 $notes = charger_fonction('notes', 'inc', true);
216 $contexte_implicite = array(
217 'squelettes' => $GLOBALS['dossier_squelettes'], // devrait etre 'chemin' => $GLOBALS['path_sig'], ?
218 'host' => (isset($_SERVER['HTTP_HOST']) ?
$_SERVER['HTTP_HOST'] : null),
219 'https' => (isset($_SERVER['HTTPS']) ?
$_SERVER['HTTPS'] : ''),
220 'espace' => test_espace_prive(),
221 'marqueur' => (isset($GLOBALS['marqueur']) ?
$GLOBALS['marqueur'] : ''),
222 'marqueur_skel' => (isset($GLOBALS['marqueur_skel']) ?
$GLOBALS['marqueur_skel'] : ''),
223 'notes' => $notes ?
$notes('', 'contexter_cache') : '',
224 'spip_version_code' => $GLOBALS['spip_version_code'],
226 if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
227 $contexte_implicite['host'] .= "|" . $_SERVER['HTTP_X_FORWARDED_HOST'];
230 return $contexte_implicite;
234 // fonction pour compatibilite arriere, probablement superflue
237 // http://code.spip.net/@auto_content_type
238 function auto_content_type($page) {
240 if (!isset($GLOBALS['flag_preserver'])) {
241 $GLOBALS['flag_preserver'] = ($page && preg_match("/header\s*\(\s*.content\-type:/isx",
242 $page['texte']) ||
(isset($page['entetes']['Content-Type'])));
246 // http://code.spip.net/@inclure_page
247 function inclure_page($fond, $contexte, $connect = '') {
248 static $cacher, $produire_page;
250 // enlever le fond de contexte inclus car sinon il prend la main
251 // dans les sous inclusions -> boucle infinie d'inclusion identique
252 // (cette precaution n'est probablement plus utile)
253 unset($contexte['fond']);
254 $page = array('contexte_implicite' => calculer_contexte_implicite());
255 $page['contexte_implicite']['cache'] = $fond;
256 if (is_null($cacher)) {
257 $cacher = charger_fonction('cacher', 'public', true);
259 // Les quatre derniers parametres sont modifies par la fonction:
260 // emplacement, validite, et, s'il est valide, contenu & age
262 $res = $cacher($contexte, $use_cache, $chemin_cache, $page, $lastinclude);
266 // $res = message d'erreur : on sort de la
268 return array('texte' => $res);
271 // Si use_cache ne vaut pas 0, la page doit etre calculee
272 // produire la page : peut mettre a jour $lastinclude
273 // le contexte_cache envoye a cacher() a ete conserve et est passe a produire
275 if (is_null($produire_page)) {
276 $produire_page = charger_fonction('produire_page', 'public');
278 $page = $produire_page($fond, $contexte, $use_cache, $chemin_cache, $contexte, $page, $lastinclude, $connect);
280 // dans tous les cas, mettre a jour $GLOBALS['lastmodified']
281 $GLOBALS['lastmodified'] = max((isset($GLOBALS['lastmodified']) ?
$GLOBALS['lastmodified'] : 0), $lastinclude);
287 * Produire la page et la mettre en cache
288 * lorsque c'est necessaire
290 * @param string $fond
291 * @param array $contexte
292 * @param int $use_cache
293 * @param string $chemin_cache
294 * @param array $contexte_cache
296 * @param int $lastinclude
297 * @param string $connect
300 function public_produire_page_dist(
310 static $parametrer, $cacher;
312 $parametrer = charger_fonction('parametrer', 'public');
314 $page = $parametrer($fond, $contexte, $chemin_cache, $connect);
315 // et on l'enregistre sur le disque
320 and isset($page['entetes']['X-Spip-Cache'])
321 and $page['entetes']['X-Spip-Cache'] > 0
323 if (is_null($cacher)) {
324 $cacher = charger_fonction('cacher', 'public', true);
326 $lastinclude = time();
328 $cacher($contexte_cache, $use_cache, $chemin_cache, $page, $lastinclude);
337 // Fonction inseree par le compilateur dans le code compile.
338 // Elle recoit un contexte pour inclure un squelette,
339 // et les valeurs du contexte de compil prepare par memoriser_contexte_compil
340 // elle-meme appelee par calculer_balise_dynamique dans references.php:
347 function inserer_balise_dynamique($contexte_exec, $contexte_compil) {
348 if (!is_array($contexte_exec)) {
350 } // message d'erreur etc
352 inclure_balise_dynamique($contexte_exec, true, $contexte_compil);
357 * Inclusion de balise dynamique
358 * Attention, un appel explicite a cette fonction suppose certains include
360 * http://code.spip.net/@inclure_balise_dynamique
362 * @param string|array $texte
363 * @param bool $echo Faut-il faire echo ou return
364 * @param array $contexte_compil Contexte de la compilation
367 function inclure_balise_dynamique($texte, $echo = true, $contexte_compil = array()) {
368 if (is_array($texte)) {
370 list($fond, $delainc, $contexte_inclus) = $texte;
372 // delais a l'ancienne, c'est pratiquement mort
373 $d = isset($GLOBALS['delais']) ?
$GLOBALS['delais'] : null;
374 $GLOBALS['delais'] = $delainc;
376 $page = recuperer_fond($fond, $contexte_inclus,
377 array('trim' => false, 'raw' => true, 'compil' => $contexte_compil));
379 $texte = $page['texte'];
381 $GLOBALS['delais'] = $d;
382 // Faire remonter les entetes
383 if (is_array($page['entetes'])) {
385 unset($page['entetes']['X-Spip-Cache']);
386 unset($page['entetes']['Content-Type']);
387 if (isset($GLOBALS['page']) and is_array($GLOBALS['page'])) {
388 if (!is_array($GLOBALS['page']['entetes'])) {
389 $GLOBALS['page']['entetes'] = array();
391 $GLOBALS['page']['entetes'] =
392 array_merge($GLOBALS['page']['entetes'], $page['entetes']);
395 // _pipelines au pluriel array('nom_pipeline' => $args...) avec une syntaxe permettant plusieurs pipelines
396 if (isset($page['contexte']['_pipelines'])
397 and is_array($page['contexte']['_pipelines'])
398 and count($page['contexte']['_pipelines'])
400 foreach ($page['contexte']['_pipelines'] as $pipe => $args) {
401 $args['contexte'] = $page['contexte'];
402 unset($args['contexte']['_pipelines']); // par precaution, meme si le risque de boucle infinie est a priori nul
415 if (defined('_VAR_MODE') and _VAR_MODE
== 'debug') {
416 // compatibilite : avant on donnait le numero de ligne ou rien.
417 $ligne = intval(isset($contexte_compil[3]) ?
$contexte_compil[3] : $contexte_compil);
418 $GLOBALS['debug_objets']['resultat'][$ligne] = $texte;
427 // http://code.spip.net/@message_page_indisponible
428 function message_page_indisponible($page, $contexte) {
429 static $deja = false;
434 '404' => '404 Not Found',
435 '503' => '503 Service Unavailable',
438 $contexte['status'] = ($page !== false) ?
'404' : '503';
439 $contexte['code'] = $codes[$contexte['status']];
440 $contexte['fond'] = '404'; // gere les 2 erreurs
441 if (!isset($contexte['lang'])) {
442 $contexte['lang'] = $GLOBALS['spip_lang'];
446 // passer aux plugins qui peuvent decider d'une page d'erreur plus pertinent
447 // ex restriction d'acces => 401
448 $contexte = pipeline('page_indisponible', $contexte);
450 // produire la page d'erreur
451 $page = inclure_page($contexte['fond'], $contexte);
453 $page = inclure_page('404', $contexte);
455 $page['status'] = $contexte['status'];
460 // temporairement ici : a mettre dans le futur inc/modeles
461 // creer_contexte_de_modele('left', 'autostart=true', ...) renvoie un array()
462 // http://code.spip.net/@creer_contexte_de_modele
463 function creer_contexte_de_modele($args) {
465 foreach ($args as $var => $val) {
466 if (is_int($var)) { // argument pas formate
467 if (in_array($val, array('left', 'right', 'center'))) {
469 $contexte[$var] = $val;
471 $args = explode('=', $val);
472 if (count($args) >= 2) // Flashvars=arg1=machin&arg2=truc genere plus de deux args
474 $contexte[trim($args[0])] = substr($val, strlen($args[0]) +
1);
475 } else // notation abregee
477 $contexte[trim($val)] = trim($val);
481 $contexte[$var] = $val;
489 * Calcule le modele et retourne la mini-page ainsi calculee
491 * http://code.spip.net/@inclure_modele
493 * @param $type string Nom du modele
495 * @param $params array ParamĂštres du modĂšle
496 * @param $lien array Informations du lien entourant l'appel du modÚle en base de données
497 * @param $connect string
499 * @staticvar string $compteur
502 function inclure_modele($type, $id, $params, $lien, $connect = '', $env = array()) {
505 if (++
$compteur > 10) {
507 } # ne pas boucler indefiniment
509 $type = strtolower($type);
513 $params = array_filter(explode('|', $params));
515 list(, $soustype) = each($params);
516 $soustype = strtolower(trim($soustype));
517 if (in_array($soustype,
518 array('left', 'right', 'center', 'ajax'))) {
519 list(, $soustype) = each($params);
520 $soustype = strtolower($soustype);
523 if (preg_match(',^[a-z0-9_]+$,', $soustype)) {
524 if (!trouve_modele($fond = ($type . '_' . $soustype))) {
528 // enlever le sous type des params
529 $params = array_diff($params, array($soustype));
533 // Si ca marche pas en precisant le sous-type, prendre le type
534 if (!$fond and !trouve_modele($fond = $type)) {
535 spip_log("Modele $type introuvable", _LOG_INFO_IMPORTANTE
);
539 $fond = 'modeles/' . $fond;
542 $contexte['dir_racine'] = _DIR_RACINE
; # eviter de mixer un cache racine et un cache ecrire (meme si pour l'instant les modeles ne sont pas caches, le resultat etant different il faut que le contexte en tienne compte
544 // Le numero du modele est mis dans l'environnement
545 // d'une part sous l'identifiant "id"
546 // et d'autre part sous l'identifiant de la cle primaire
547 // par la fonction id_table_objet,
548 // (<article1> =>> article =>> id_article =>> id_article=1)
549 $_id = id_table_objet($type);
550 $contexte['id'] = $contexte[$_id] = $id;
553 $contexte['class'] = $class;
556 // Si un lien a ete passe en parametre, ex: [<modele1>->url] ou [<modele1|title_du_lien{hreflang}->url]
558 # un eventuel guillemet (") sera reechappe par #ENV
559 $contexte['lien'] = str_replace(""", '"', $lien['href']);
560 $contexte['lien_class'] = $lien['class'];
561 $contexte['lien_mime'] = $lien['mime'];
562 $contexte['lien_title'] = $lien['title'];
563 $contexte['lien_hreflang'] = $lien['hreflang'];
566 // Traiter les parametres
567 // par exemple : <img1|center>, <emb12|autostart=true> ou <doc1|lang=en>
568 $arg_list = creer_contexte_de_modele($params);
569 $contexte['args'] = $arg_list; // on passe la liste des arguments du modeles dans une variable args
570 $contexte = array_merge($contexte, $arg_list);
572 // Appliquer le modele avec le contexte
573 $retour = recuperer_fond($fond, $contexte, array(), $connect);
575 // Regarder si le modele tient compte des liens (il *doit* alors indiquer
576 // spip_lien_ok dans les classes de son conteneur de premier niveau ;
577 // sinon, s'il y a un lien, on l'ajoute classiquement
578 if (strstr(' ' . ($classes = extraire_attribut($retour, 'class')) . ' ',
580 $retour = inserer_attribut($retour, 'class',
581 trim(str_replace(' spip_lien_ok ', ' ', " $classes ")));
584 $retour = "<a href='" . $lien['href'] . "' class='" . $lien['class'] . "'>" . $retour . "</a>";
590 return (isset($arg_list['ajax']) and $arg_list['ajax'] == 'ajax')
591 ?
encoder_contexte_ajax($contexte, '', $retour)
595 // Un inclure_page qui marche aussi pour l'espace prive
596 // fonction interne a spip, ne pas appeler directement
597 // pour recuperer $page complet, utiliser:
598 // recuperer_fond($fond,$contexte,array('raw'=>true))
599 // http://code.spip.net/@evaluer_fond
600 function evaluer_fond($fond, $contexte = array(), $connect = null) {
602 $page = inclure_page($fond, $contexte, $connect);
607 // eval $page et affecte $res
608 include _ROOT_RESTREINT
. "public/evaluer_page.php";
610 // Lever un drapeau (global) si le fond utilise #SESSION
611 // a destination de public/parametrer
612 // pour remonter vers les inclusions appelantes
613 // il faut bien lever ce drapeau apres avoir evalue le fond
614 // pour ne pas faire descendre le flag vers les inclusions appelees
615 if (isset($page['invalideurs'])
616 and isset($page['invalideurs']['session'])
618 $GLOBALS['cache_utilise_session'] = $page['invalideurs']['session'];
625 // http://code.spip.net/@page_base_href
626 function page_base_href(&$texte) {
627 static $set_html_base = null;
628 if (is_null($set_html_base)) {
629 if (!defined('_SET_HTML_BASE'))
630 // si la profondeur est superieure a 1
631 // est que ce n'est pas une url page ni une url action
632 // activer par defaut
635 $GLOBALS['profondeur_url'] >= (_DIR_RESTREINT ?
1 : 2)
636 and _request(_SPIP_PAGE
) !== 'login'
637 and !_request('action')) ?
true : false);
639 $set_html_base = _SET_HTML_BASE
;
644 and isset($GLOBALS['html']) and $GLOBALS['html']
645 and $GLOBALS['profondeur_url'] > 0
646 and ($poshead = strpos($texte, '</head>')) !== false
648 $head = substr($texte, 0, $poshead);
650 if (strpos($head, '<base') === false) {
653 // si aucun <base ...> n'a de href c'est bon quand meme !
655 include_spip('inc/filtres');
656 $bases = extraire_balises($head, 'base');
657 foreach ($bases as $base) {
658 if (extraire_attribut($base, 'href')) {
664 include_spip('inc/filtres_mini');
665 // ajouter un base qui reglera tous les liens relatifs
666 $base = url_absolue('./');
667 $bbase = "\n<base href=\"$base\" />";
668 if (($pos = strpos($head, '<head>')) !== false) {
669 $head = substr_replace($head, $bbase, $pos +
6, 0);
670 } elseif (preg_match(",<head[^>]*>,i", $head, $r)) {
671 $head = str_replace($r[0], $r[0] . $bbase, $head);
673 $texte = $head . substr($texte, $poshead);
675 $base = $_SERVER['REQUEST_URI'];
676 // pas de guillemets ni < dans l'URL qu'on insere dans le HTML
677 if (strpos($base,"'") or strpos($base,'"') or strpos($base,'<')) {
678 $base = str_replace(array("'",'"','<'),array("%27",'%22','%3C'), $base);
680 if (strpos($texte, "href='#") !== false) {
681 $texte = str_replace("href='#", "href='$base#", $texte);
683 if (strpos($texte, "href=\"#") !== false) {
684 $texte = str_replace("href=\"#", "href=\"$base#", $texte);
691 // Envoyer les entetes, en retenant ceux qui sont a usage interne
692 // et demarrent par X-Spip-...
693 // http://code.spip.net/@envoyer_entetes
694 function envoyer_entetes($entetes) {
695 foreach ($entetes as $k => $v) # if (strncmp($k, 'X-Spip-', 7))
697 @header
(strlen($v) ?
"$k: $v" : $k);