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 arguments_balise_dyn_depuis_modele(null, 'reset');
350 if (!is_array($contexte_exec)) {
352 } // message d'erreur etc
354 inclure_balise_dynamique($contexte_exec, true, $contexte_compil);
359 * Inclusion de balise dynamique
360 * Attention, un appel explicite a cette fonction suppose certains include
362 * http://code.spip.net/@inclure_balise_dynamique
364 * @param string|array $texte
365 * @param bool $echo Faut-il faire echo ou return
366 * @param array $contexte_compil Contexte de la compilation
369 function inclure_balise_dynamique($texte, $echo = true, $contexte_compil = array()) {
370 if (is_array($texte)) {
372 list($fond, $delainc, $contexte_inclus) = $texte;
374 // delais a l'ancienne, c'est pratiquement mort
375 $d = isset($GLOBALS['delais']) ?
$GLOBALS['delais'] : null;
376 $GLOBALS['delais'] = $delainc;
378 $page = recuperer_fond($fond, $contexte_inclus,
379 array('trim' => false, 'raw' => true, 'compil' => $contexte_compil));
381 $texte = $page['texte'];
383 $GLOBALS['delais'] = $d;
384 // Faire remonter les entetes
385 if (is_array($page['entetes'])) {
387 unset($page['entetes']['X-Spip-Cache']);
388 unset($page['entetes']['Content-Type']);
389 if (isset($GLOBALS['page']) and is_array($GLOBALS['page'])) {
390 if (!is_array($GLOBALS['page']['entetes'])) {
391 $GLOBALS['page']['entetes'] = array();
393 $GLOBALS['page']['entetes'] =
394 array_merge($GLOBALS['page']['entetes'], $page['entetes']);
397 // _pipelines au pluriel array('nom_pipeline' => $args...) avec une syntaxe permettant plusieurs pipelines
398 if (isset($page['contexte']['_pipelines'])
399 and is_array($page['contexte']['_pipelines'])
400 and count($page['contexte']['_pipelines'])
402 foreach ($page['contexte']['_pipelines'] as $pipe => $args) {
403 $args['contexte'] = $page['contexte'];
404 unset($args['contexte']['_pipelines']); // par precaution, meme si le risque de boucle infinie est a priori nul
417 if (defined('_VAR_MODE') and _VAR_MODE
== 'debug') {
418 // compatibilite : avant on donnait le numero de ligne ou rien.
419 $ligne = intval(isset($contexte_compil[3]) ?
$contexte_compil[3] : $contexte_compil);
420 $GLOBALS['debug_objets']['resultat'][$ligne] = $texte;
429 // http://code.spip.net/@message_page_indisponible
430 function message_page_indisponible($page, $contexte) {
431 static $deja = false;
436 '404' => '404 Not Found',
437 '503' => '503 Service Unavailable',
440 $contexte['status'] = ($page !== false) ?
'404' : '503';
441 $contexte['code'] = $codes[$contexte['status']];
442 $contexte['fond'] = '404'; // gere les 2 erreurs
443 if (!isset($contexte['lang'])) {
444 $contexte['lang'] = $GLOBALS['spip_lang'];
448 // passer aux plugins qui peuvent decider d'une page d'erreur plus pertinent
449 // ex restriction d'acces => 401
450 $contexte = pipeline('page_indisponible', $contexte);
452 // produire la page d'erreur
453 $page = inclure_page($contexte['fond'], $contexte);
455 $page = inclure_page('404', $contexte);
457 $page['status'] = $contexte['status'];
463 * gerer le flag qui permet de reperer qu'une balise dynamique a ete inseree depuis un modele
464 * utilisee dans les #FORMULAIRE_xx
466 * @param string|null $arg
467 * @param string $operation
470 function arguments_balise_dyn_depuis_modele($arg, $operation = 'set') {
471 static $balise_dyn_appellee_par_modele = null;
472 switch ($operation) {
474 return $balise_dyn_appellee_par_modele;
476 $balise_dyn_appellee_par_modele = null;
480 $balise_dyn_appellee_par_modele = $arg;
485 // temporairement ici : a mettre dans le futur inc/modeles
486 // creer_contexte_de_modele('left', 'autostart=true', ...) renvoie un array()
487 // http://code.spip.net/@creer_contexte_de_modele
488 function creer_contexte_de_modele($args) {
490 foreach ($args as $var => $val) {
491 if (is_int($var)) { // argument pas formate
492 if (in_array($val, array('left', 'right', 'center'))) {
494 $contexte[$var] = $val;
496 $args = explode('=', $val);
497 if (count($args) >= 2) // Flashvars=arg1=machin&arg2=truc genere plus de deux args
499 $contexte[trim($args[0])] = substr($val, strlen($args[0]) +
1);
500 } else // notation abregee
502 $contexte[trim($val)] = trim($val);
506 $contexte[$var] = $val;
514 * Calcule le modele et retourne la mini-page ainsi calculee
516 * http://code.spip.net/@inclure_modele
518 * @param $type string Nom du modele
520 * @param $params array ParamĂštres du modĂšle
521 * @param $lien array Informations du lien entourant l'appel du modÚle en base de données
522 * @param $connect string
524 * @staticvar string $compteur
527 function inclure_modele($type, $id, $params, $lien, $connect = '', $env = array()) {
530 if (++
$compteur > 10) {
532 } # ne pas boucler indefiniment
534 $type = strtolower($type);
538 $params = array_filter(explode('|', $params));
540 $soustype = current($params);
541 $soustype = strtolower(trim($soustype));
542 if (in_array($soustype, array('left', 'right', 'center', 'ajax'))) {
543 $soustype = next($params);
544 $soustype = strtolower($soustype);
547 if (preg_match(',^[a-z0-9_]+$,', $soustype)) {
548 if (!trouve_modele($fond = ($type . '_' . $soustype))) {
552 // enlever le sous type des params
553 $params = array_diff($params, array($soustype));
557 // Si ca marche pas en precisant le sous-type, prendre le type
558 if (!$fond and !trouve_modele($fond = $type)) {
559 spip_log("Modele $type introuvable", _LOG_INFO_IMPORTANTE
);
563 $fond = 'modeles/' . $fond;
566 $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
568 // Le numero du modele est mis dans l'environnement
569 // d'une part sous l'identifiant "id"
570 // et d'autre part sous l'identifiant de la cle primaire
571 // par la fonction id_table_objet,
572 // (<article1> =>> article =>> id_article =>> id_article=1)
573 $_id = id_table_objet($type);
574 $contexte['id'] = $contexte[$_id] = $id;
577 $contexte['class'] = $class;
580 // Si un lien a ete passe en parametre, ex: [<modele1>->url] ou [<modele1|title_du_lien{hreflang}->url]
582 # un eventuel guillemet (") sera reechappe par #ENV
583 $contexte['lien'] = str_replace(""", '"', $lien['href']);
584 $contexte['lien_class'] = $lien['class'];
585 $contexte['lien_mime'] = $lien['mime'];
586 $contexte['lien_title'] = $lien['title'];
587 $contexte['lien_hreflang'] = $lien['hreflang'];
590 // Traiter les parametres
591 // par exemple : <img1|center>, <emb12|autostart=true> ou <doc1|lang=en>
592 $arg_list = creer_contexte_de_modele($params);
593 $contexte['args'] = $arg_list; // on passe la liste des arguments du modeles dans une variable args
594 $contexte = array_merge($contexte, $arg_list);
596 // Appliquer le modele avec le contexte
597 $retour = recuperer_fond($fond, $contexte, array(), $connect);
599 // Regarder si le modele tient compte des liens (il *doit* alors indiquer
600 // spip_lien_ok dans les classes de son conteneur de premier niveau ;
601 // sinon, s'il y a un lien, on l'ajoute classiquement
602 if (strstr(' ' . ($classes = extraire_attribut($retour, 'class')) . ' ',
604 $retour = inserer_attribut($retour, 'class',
605 trim(str_replace(' spip_lien_ok ', ' ', " $classes ")));
608 $retour = "<a href='" . $lien['href'] . "' class='" . $lien['class'] . "'>" . $retour . "</a>";
614 return (isset($arg_list['ajax']) and $arg_list['ajax'] == 'ajax')
615 ?
encoder_contexte_ajax($contexte, '', $retour)
619 // Un inclure_page qui marche aussi pour l'espace prive
620 // fonction interne a spip, ne pas appeler directement
621 // pour recuperer $page complet, utiliser:
622 // recuperer_fond($fond,$contexte,array('raw'=>true))
623 // http://code.spip.net/@evaluer_fond
624 function evaluer_fond($fond, $contexte = array(), $connect = null) {
626 $page = inclure_page($fond, $contexte, $connect);
631 // eval $page et affecte $res
632 include _ROOT_RESTREINT
. "public/evaluer_page.php";
634 // Lever un drapeau (global) si le fond utilise #SESSION
635 // a destination de public/parametrer
636 // pour remonter vers les inclusions appelantes
637 // il faut bien lever ce drapeau apres avoir evalue le fond
638 // pour ne pas faire descendre le flag vers les inclusions appelees
639 if (isset($page['invalideurs'])
640 and isset($page['invalideurs']['session'])
642 $GLOBALS['cache_utilise_session'] = $page['invalideurs']['session'];
649 // http://code.spip.net/@page_base_href
650 function page_base_href(&$texte) {
651 static $set_html_base = null;
652 if (is_null($set_html_base)) {
653 if (!defined('_SET_HTML_BASE'))
654 // si la profondeur est superieure a 1
655 // est que ce n'est pas une url page ni une url action
656 // activer par defaut
659 $GLOBALS['profondeur_url'] >= (_DIR_RESTREINT ?
1 : 2)
660 and _request(_SPIP_PAGE
) !== 'login'
661 and !_request('action')) ?
true : false);
663 $set_html_base = _SET_HTML_BASE
;
668 and isset($GLOBALS['html']) and $GLOBALS['html']
669 and $GLOBALS['profondeur_url'] > 0
670 and ($poshead = strpos($texte, '</head>')) !== false
672 $head = substr($texte, 0, $poshead);
674 if (strpos($head, '<base') === false) {
677 // si aucun <base ...> n'a de href c'est bon quand meme !
679 include_spip('inc/filtres');
680 $bases = extraire_balises($head, 'base');
681 foreach ($bases as $base) {
682 if (extraire_attribut($base, 'href')) {
688 include_spip('inc/filtres_mini');
689 // ajouter un base qui reglera tous les liens relatifs
690 $base = url_absolue('./');
691 $bbase = "\n<base href=\"$base\" />";
692 if (($pos = strpos($head, '<head>')) !== false) {
693 $head = substr_replace($head, $bbase, $pos +
6, 0);
694 } elseif (preg_match(",<head[^>]*>,i", $head, $r)) {
695 $head = str_replace($r[0], $r[0] . $bbase, $head);
697 $texte = $head . substr($texte, $poshead);
699 $base = $_SERVER['REQUEST_URI'];
700 // pas de guillemets ni < dans l'URL qu'on insere dans le HTML
701 if (strpos($base,"'") or strpos($base,'"') or strpos($base,'<')) {
702 $base = str_replace(array("'",'"','<'),array("%27",'%22','%3C'), $base);
704 if (strpos($texte, "href='#") !== false) {
705 $texte = str_replace("href='#", "href='$base#", $texte);
707 if (strpos($texte, "href=\"#") !== false) {
708 $texte = str_replace("href=\"#", "href=\"$base#", $texte);
715 // Envoyer les entetes, en retenant ceux qui sont a usage interne
716 // et demarrent par X-Spip-...
717 // http://code.spip.net/@envoyer_entetes
718 function envoyer_entetes($entetes) {
719 foreach ($entetes as $k => $v) # if (strncmp($k, 'X-Spip-', 7))
721 @header
(strlen($v) ?
"$k: $v" : $k);