[SPIP][PLUGINS] v3.0-->v3.2
[lhc/web/www.git] / www / ecrire / public / cacher.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2017 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
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 \***************************************************************************/
12
13 if (!defined('_ECRIRE_INC_VERSION')) {
14 return;
15 }
16
17 /**
18 * Le format souhaite : tmp/cache/ab/cd
19 * soit au maximum 16^4 fichiers dans 256 repertoires
20 * Attention a modifier simultanement le sanity check de
21 * la fonction retire_cache() de inc/invalideur
22 *
23 * http://code.spip.net/@generer_nom_fichier_cache
24 *
25 * @param array $contexte
26 * @param array $page
27 * @return string
28 */
29 function generer_nom_fichier_cache($contexte, $page) {
30 $u = md5(var_export(array($contexte, $page), true));
31
32 return $u . ".cache";
33 }
34
35 /**
36 * ecrire le cache dans un casier
37 *
38 * @param string $nom_cache
39 * @param $valeur
40 * @return bool
41 */
42 function ecrire_cache($nom_cache, $valeur) {
43 $d = substr($nom_cache, 0, 2);
44 $u = substr($nom_cache, 2, 2);
45 $rep = _DIR_CACHE;
46 $rep = sous_repertoire($rep, '', false, true);
47 $rep = sous_repertoire($rep, $d, false, true);
48
49 return ecrire_fichier($rep . $u . ".cache", serialize(array("nom_cache" => $nom_cache, "valeur" => $valeur)));
50 }
51
52 /**
53 * lire le cache depuis un casier
54 *
55 * @param string $nom_cache
56 * @return mixed
57 */
58 function lire_cache($nom_cache) {
59 $d = substr($nom_cache, 0, 2);
60 $u = substr($nom_cache, 2, 2);
61 if (file_exists($f = _DIR_CACHE . "$d/$u.cache")
62 and lire_fichier($f, $tmp)
63 and $tmp = unserialize($tmp)
64 and $tmp['nom_cache'] == $nom_cache
65 and isset($tmp['valeur'])
66 ) {
67 return $tmp['valeur'];
68 }
69
70 return false;
71 }
72
73 // Parano : on signe le cache, afin d'interdire un hack d'injection
74 // dans notre memcache
75 function cache_signature(&$page) {
76 if (!isset($GLOBALS['meta']['cache_signature'])) {
77 include_spip('inc/acces');
78 include_spip('auth/sha256.inc');
79 ecrire_meta('cache_signature',
80 _nano_sha256($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SERVER_SIGNATURE"] . creer_uniqid()), 'non');
81 }
82
83 return crc32($GLOBALS['meta']['cache_signature'] . $page['texte']);
84 }
85
86 /**
87 * Faut-il compresser ce cache ? A partir de 16ko ca vaut le coup
88 * (pas de passage par reference car on veut conserver la version non compressee
89 * pour l'afficher)
90 * on positionne un flag gz si on comprime, pour savoir si on doit decompresser ou pas
91 * http://code.spip.net/@gzip_page
92 *
93 * @param array $page
94 * @return array
95 */
96 function gzip_page($page) {
97 if (function_exists('gzcompress') and strlen($page['texte']) > 16 * 1024) {
98 $page['gz'] = true;
99 $page['texte'] = gzcompress($page['texte']);
100 } else {
101 $page['gz'] = false;
102 }
103
104 return $page;
105 }
106
107 /**
108 * Faut-il decompresser ce cache ?
109 * (passage par reference pour alleger)
110 * on met a jour le flag gz quand on decompresse, pour ne pas risquer
111 * de decompresser deux fois de suite un cache (ce qui echoue)
112 *
113 * http://code.spip.net/@gunzip_page
114 *
115 * @param array $page
116 * @return void
117 */
118 function gunzip_page(&$page) {
119 if ($page['gz']) {
120 $page['texte'] = gzuncompress($page['texte']);
121 $page['gz'] = false; // ne pas gzuncompress deux fois une meme page
122 }
123 }
124
125 /**
126 * gestion des delais d'expiration du cache...
127 * $page passee par reference pour accelerer
128 *
129 * @param array $page
130 * @param int $date
131 * @return int
132 * 1 si il faut mettre le cache a jour
133 * 0 si le cache est valide
134 * -1 si il faut calculer sans stocker en cache
135 */
136 /// http://code.spip.net/@cache_valide
137 function cache_valide(&$page, $date) {
138 $now = $_SERVER['REQUEST_TIME'];
139
140 // Apparition d'un nouvel article post-date ?
141 if (isset($GLOBALS['meta']['post_dates'])
142 and $GLOBALS['meta']['post_dates'] == 'non'
143 and isset($GLOBALS['meta']['date_prochain_postdate'])
144 and $now > $GLOBALS['meta']['date_prochain_postdate']
145 ) {
146 spip_log('Un article post-date invalide le cache');
147 include_spip('inc/rubriques');
148 calculer_prochain_postdate(true);
149 }
150
151 if (defined('_VAR_NOCACHE') and _VAR_NOCACHE) {
152 return -1;
153 }
154 if (isset($GLOBALS['meta']['cache_inhib']) and $_SERVER['REQUEST_TIME'] < $GLOBALS['meta']['cache_inhib']) {
155 return -1;
156 }
157 if (defined('_NO_CACHE')) {
158 return (_NO_CACHE == 0 and !isset($page['texte'])) ? 1 : _NO_CACHE;
159 }
160
161 // pas de cache ? on le met a jour, sauf pour les bots (on leur calcule la page sans mise en cache)
162 if (!$page or !isset($page['texte']) or !isset($page['entetes']['X-Spip-Cache'])) {
163 return _IS_BOT ? -1 : 1;
164 }
165
166 // controle de la signature
167 if ($page['sig'] !== cache_signature($page)) {
168 return _IS_BOT ? -1 : 1;
169 }
170
171 // #CACHE{n,statique} => on n'invalide pas avec derniere_modif
172 // cf. ecrire/public/balises.php, balise_CACHE_dist()
173 if (!isset($page['entetes']['X-Spip-Statique']) or $page['entetes']['X-Spip-Statique'] !== 'oui') {
174
175 // Cache invalide par la meta 'derniere_modif'
176 // sauf pour les bots, qui utilisent toujours le cache
177 if (!_IS_BOT
178 and $GLOBALS['derniere_modif_invalide']
179 and isset($GLOBALS['meta']['derniere_modif'])
180 and $date < $GLOBALS['meta']['derniere_modif']
181 ) {
182 return 1;
183 }
184
185 }
186
187 // Sinon comparer l'age du fichier a sa duree de cache
188 $duree = intval($page['entetes']['X-Spip-Cache']);
189 $cache_mark = (isset($GLOBALS['meta']['cache_mark']) ? $GLOBALS['meta']['cache_mark'] : 0);
190 if ($duree == 0) #CACHE{0}
191 {
192 return -1;
193 } // sauf pour les bots, qui utilisent toujours le cache
194 else {
195 if ((!_IS_BOT and $date + $duree < $now)
196 # le cache est anterieur a la derniere purge : l'ignorer, meme pour les bots
197 or $date < $cache_mark
198 ) {
199 return _IS_BOT ? -1 : 1;
200 } else {
201 return 0;
202 }
203 }
204 }
205
206 /**
207 * Creer le fichier cache
208 * Passage par reference de $page par souci d'economie
209 *
210 * http://code.spip.net/@creer_cache
211 *
212 * @param array $page
213 * @param string $chemin_cache
214 * @return void
215 */
216 function creer_cache(&$page, &$chemin_cache) {
217
218 // Ne rien faire si on est en preview, debug, ou si une erreur
219 // grave s'est presentee (compilation du squelette, MySQL, etc)
220 // le cas var_nocache ne devrait jamais arriver ici (securite)
221 // le cas spip_interdire_cache correspond a une ereur SQL grave non anticipable
222 if ((defined('_VAR_NOCACHE') and _VAR_NOCACHE)
223 or defined('spip_interdire_cache')
224 ) {
225 return;
226 }
227
228 // Si la page c1234 a un invalideur de session 'zz', sauver dans
229 // 'tmp/cache/MD5(chemin_cache)_zz'
230 if (isset($page['invalideurs'])
231 and isset($page['invalideurs']['session'])
232 ) {
233 // on verifie que le contenu du chemin cache indique seulement
234 // "cache sessionne" ; sa date indique la date de validite
235 // des caches sessionnes
236 if (!$tmp = lire_cache($chemin_cache)) {
237 spip_log('Creation cache sessionne ' . $chemin_cache);
238 $tmp = array(
239 'invalideurs' => array('session' => ''),
240 'lastmodified' => $_SERVER['REQUEST_TIME']
241 );
242 ecrire_cache($chemin_cache, $tmp);
243 }
244 $chemin_cache = generer_nom_fichier_cache(array("chemin_cache" => $chemin_cache),
245 array("session" => $page['invalideurs']['session']));
246 }
247
248 // ajouter la date de production dans le cache lui meme
249 // (qui contient deja sa duree de validite)
250 $page['lastmodified'] = $_SERVER['REQUEST_TIME'];
251
252 // compresser le contenu si besoin
253 $pagez = gzip_page($page);
254
255 // signer le contenu
256 $pagez['sig'] = cache_signature($pagez);
257
258 // l'enregistrer, compresse ou non...
259 $ok = ecrire_cache($chemin_cache, $pagez);
260
261 spip_log((_IS_BOT ? "Bot:" : "") . "Creation du cache $chemin_cache pour "
262 . $page['entetes']['X-Spip-Cache'] . " secondes" . ($ok ? '' : ' (erreur!)'), _LOG_INFO_IMPORTANTE);
263
264 // Inserer ses invalideurs
265 include_spip('inc/invalideur');
266 maj_invalideurs($chemin_cache, $page);
267
268 }
269
270
271 /**
272 * purger un petit cache (tidy ou recherche) qui ne doit pas contenir de
273 * vieux fichiers ; (cette fonction ne sert que dans des plugins obsoletes)
274 *
275 * http://code.spip.net/@nettoyer_petit_cache
276 *
277 * @param string $prefix
278 * @param int $duree
279 * @return void
280 */
281 function nettoyer_petit_cache($prefix, $duree = 300) {
282 // determiner le repertoire a purger : 'tmp/CACHE/rech/'
283 $dircache = sous_repertoire(_DIR_CACHE, $prefix);
284 if (spip_touch($dircache . 'purger_' . $prefix, $duree, true)) {
285 foreach (preg_files($dircache, '[.]txt$') as $f) {
286 if ($_SERVER['REQUEST_TIME'] - (@file_exists($f) ? @filemtime($f) : 0) > $duree) {
287 spip_unlink($f);
288 }
289 }
290 }
291 }
292
293
294 /**
295 * Interface du gestionnaire de cache
296 * Si son 3e argument est non vide, elle passe la main a creer_cache
297 * Sinon, elle recoit un contexte (ou le construit a partir de REQUEST_URI)
298 * et affecte les 4 autres parametres recus par reference:
299 * - use_cache qui vaut
300 * -1 s'il faut calculer la page sans la mettre en cache
301 * 0 si on peut utiliser un cache existant
302 * 1 s'il faut calculer la page et la mettre en cache
303 * - chemin_cache qui est le chemin d'acces au fichier ou vide si pas cachable
304 * - page qui est le tableau decrivant la page, si le cache la contenait
305 * - lastmodified qui vaut la date de derniere modif du fichier.
306 * Elle retourne '' si tout va bien
307 * un message d'erreur si le calcul de la page est totalement impossible
308 *
309 * http://code.spip.net/@public_cacher_dist
310 *
311 * @param array $contexte
312 * @param int $use_cache
313 * @param string $chemin_cache
314 * @param array $page
315 * @param int $lastmodified
316 * @return string|void
317 */
318 function public_cacher_dist($contexte, &$use_cache, &$chemin_cache, &$page, &$lastmodified) {
319
320 # fonction de cache minimale : dire "non on ne met rien en cache"
321 # $use_cache = -1; return;
322
323 // Second appel, destine a l'enregistrement du cache sur le disque
324 if (isset($chemin_cache)) {
325 return creer_cache($page, $chemin_cache);
326 }
327
328 // Toute la suite correspond au premier appel
329 $contexte_implicite = $page['contexte_implicite'];
330
331 // Cas ignorant le cache car completement dynamique
332 if ($_SERVER['REQUEST_METHOD'] == 'POST'
333 or _request('connect')
334 ) {
335 $use_cache = -1;
336 $lastmodified = 0;
337 $chemin_cache = "";
338 $page = array();
339
340 return;
341 }
342
343 // Controler l'existence d'un cache nous correspondant
344 $chemin_cache = generer_nom_fichier_cache($contexte, $page);
345 $lastmodified = 0;
346
347 // charger le cache s'il existe (et si il a bien le bon hash = anticollision)
348 if (!$page = lire_cache($chemin_cache)) {
349 $page = array();
350 }
351
352 // s'il est sessionne, charger celui correspondant a notre session
353 if (isset($page['invalideurs'])
354 and isset($page['invalideurs']['session'])
355 ) {
356 $chemin_cache_session = generer_nom_fichier_cache(array("chemin_cache" => $chemin_cache),
357 array("session" => spip_session()));
358 if ($page_session = lire_cache($chemin_cache_session)
359 and $page_session['lastmodified'] >= $page['lastmodified']
360 ) {
361 $page = $page_session;
362 } else {
363 $page = array();
364 }
365 }
366
367
368 // Faut-il effacer des pages invalidees (en particulier ce cache-ci) ?
369 if (isset($GLOBALS['meta']['invalider'])) {
370 // ne le faire que si la base est disponible
371 if (spip_connect()) {
372 include_spip('inc/invalideur');
373 retire_caches($chemin_cache); # API invalideur inutile
374 supprimer_fichier(_DIR_CACHE . $chemin_cache);
375 if (isset($chemin_cache_session) and $chemin_cache_session) {
376 supprimer_fichier(_DIR_CACHE . $chemin_cache_session);
377 }
378 }
379 }
380
381 // Si un calcul, recalcul [ou preview, mais c'est recalcul] est demande,
382 // on supprime le cache
383 if (defined('_VAR_MODE') && _VAR_MODE &&
384 (isset($_COOKIE['spip_session'])
385 || isset($_COOKIE['spip_admin'])
386 || @file_exists(_ACCESS_FILE_NAME))
387 ) {
388 $page = array('contexte_implicite' => $contexte_implicite); // ignorer le cache deja lu
389 include_spip('inc/invalideur');
390 retire_caches($chemin_cache); # API invalideur inutile
391 supprimer_fichier(_DIR_CACHE . $chemin_cache);
392 if (isset($chemin_cache_session) and $chemin_cache_session) {
393 supprimer_fichier(_DIR_CACHE . $chemin_cache_session);
394 }
395 }
396
397 // $delais par defaut
398 // pour toutes les pages sans #CACHE{} hors modeles/ et espace privé
399 // qui sont a cache nul par defaut
400 if (!isset($GLOBALS['delais'])) {
401 if (!defined('_DUREE_CACHE_DEFAUT')) {
402 define('_DUREE_CACHE_DEFAUT', 24 * 3600);
403 }
404 $GLOBALS['delais'] = _DUREE_CACHE_DEFAUT;
405 }
406
407 // determiner la validite de la page
408 if ($page) {
409 $use_cache = cache_valide($page, isset($page['lastmodified']) ? $page['lastmodified'] : 0);
410 // le contexte implicite n'est pas stocke dans le cache, mais il y a equivalence
411 // par le nom du cache. On le reinjecte donc ici pour utilisation eventuelle au calcul
412 $page['contexte_implicite'] = $contexte_implicite;
413 if (!$use_cache) {
414 // $page est un cache utilisable
415 gunzip_page($page);
416
417 return;
418 }
419 } else {
420 $page = array('contexte_implicite' => $contexte_implicite);
421 $use_cache = cache_valide($page, 0); // fichier cache absent : provoque le calcul
422 }
423
424 // Si pas valide mais pas de connexion a la base, le garder quand meme
425 if (!spip_connect()) {
426 if (isset($page['texte'])) {
427 gunzip_page($page);
428 $use_cache = 0;
429 } else {
430 spip_log("Erreur base de donnees, impossible utiliser $chemin_cache");
431 include_spip('inc/minipres');
432
433 return minipres(_T('info_travaux_titre'), _T('titre_probleme_technique'), array('status' => 503));
434 }
435 }
436
437 if ($use_cache < 0) {
438 $chemin_cache = '';
439 }
440
441 return;
442 }