1 | <?php |
---|
2 | |
---|
3 | /***************************************************************************\ |
---|
4 | * SPIP, Systeme de publication pour l'internet * |
---|
5 | * * |
---|
6 | * Copyright (c) 2001-2010 * |
---|
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")) return; |
---|
14 | |
---|
15 | // par defaut pas de progressivite d'invalidation du cache : duree=0 |
---|
16 | if (!defined("_DUREE_INVALIDATION_PROGRESSIVE_CACHE")) |
---|
17 | define("_DUREE_INVALIDATION_PROGRESSIVE_CACHE",0); |
---|
18 | |
---|
19 | /* compat SPIP 1.9 */ |
---|
20 | if(!function_exists('test_espace_prive')) { |
---|
21 | function test_espace_prive() { |
---|
22 | return !!_DIR_RACINE; |
---|
23 | } |
---|
24 | } |
---|
25 | /* compat SPIP 2.1 */ |
---|
26 | if(defined('_LOG_INFO_IMPORTANTE')) { |
---|
27 | function memoization_log($log) { spip_log($log, _LOG_INFO_IMPORTANTE); } |
---|
28 | } else { |
---|
29 | function memoization_log($log) { spip_log($log); } |
---|
30 | } |
---|
31 | |
---|
32 | // https://code.spip.net/@generer_nom_fichier_cache |
---|
33 | function generer_nom_fichier_cache($contexte, $page) { |
---|
34 | // l'indicateur sert a savoir un peu de quoi il s'agit |
---|
35 | // quand on regarde dans le cache ; on le met a la fin |
---|
36 | // du nom pour que ca "melange" mieux sous memcache |
---|
37 | $indicateur = is_array($page) |
---|
38 | ? $page['contexte_implicite']['cache'] # SPIP 2.1 |
---|
39 | : strval($page); # SPIP 2.0 ou autre |
---|
40 | |
---|
41 | return |
---|
42 | md5(var_export(array($contexte, $page),true).'-'.$GLOBALS['dossier_squelettes'].'-'.(isset($GLOBALS['marqueur'])?$GLOBALS['marqueur']:'')) |
---|
43 | . '-'.$indicateur; |
---|
44 | } |
---|
45 | |
---|
46 | // Parano : on signe le cache, afin d'interdire un hack d'injection |
---|
47 | // dans notre memcache |
---|
48 | function cache_signature(&$page) { |
---|
49 | if (!isset($GLOBALS['meta']['cache_signature'])){ |
---|
50 | include_spip('inc/acces'); |
---|
51 | include_spip('auth/sha256.inc'); |
---|
52 | $sigfunc = function_exists('_nano_sha256') ? '_nano_sha256' : 'md5'; |
---|
53 | ecrire_meta('cache_signature', $sigfunc($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SERVER_SIGNATURE"] . creer_uniqid()), 'non'); |
---|
54 | } |
---|
55 | return crc32($GLOBALS['meta']['cache_signature'].$page['texte']); |
---|
56 | } |
---|
57 | |
---|
58 | /** |
---|
59 | * gestion des delais d'expiration du cache... |
---|
60 | * $page passee par reference pour accelerer |
---|
61 | * |
---|
62 | * La fonction retourne |
---|
63 | * 1 si il faut mettre le cache a jour |
---|
64 | * 0 si le cache est valide |
---|
65 | * -1 si il faut calculer sans stocker en cache |
---|
66 | * |
---|
67 | * @param array $page |
---|
68 | * @param int $date |
---|
69 | * @return int -1|0|1 |
---|
70 | */ |
---|
71 | /// https://code.spip.net/@cache_valide |
---|
72 | function cache_valide(&$page, $date) { |
---|
73 | $now = $_SERVER['REQUEST_TIME']; |
---|
74 | |
---|
75 | // Apparition d'un nouvel article post-date ? |
---|
76 | if ($GLOBALS['meta']['post_dates'] == 'non' |
---|
77 | AND isset($GLOBALS['meta']['date_prochain_postdate']) |
---|
78 | AND $now > $GLOBALS['meta']['date_prochain_postdate']) { |
---|
79 | spip_log('Un article post-date invalide le cache'); |
---|
80 | include_spip('inc/rubriques'); |
---|
81 | calculer_prochain_postdate(true); |
---|
82 | } |
---|
83 | |
---|
84 | if (defined('_VAR_NOCACHE') AND _VAR_NOCACHE) return -1; |
---|
85 | if (isset($GLOBALS['meta']['cache_inhib']) AND $_SERVER['REQUEST_TIME'] AND $_SERVER['REQUEST_TIME']<$GLOBALS['meta']['cache_inhib']) return -1; |
---|
86 | if (isset($GLOBALS['var_nocache']) AND $GLOBALS['var_nocache']) return -1; |
---|
87 | if (defined('_NO_CACHE')) return (_NO_CACHE==0 AND !isset($page['texte']))?1:_NO_CACHE; |
---|
88 | |
---|
89 | // pas de cache ? on le met a jour, sauf pour les bots (on leur calcule la page sans mise en cache) |
---|
90 | if (!$page OR !isset($page['texte']) OR !isset($page['entetes']['X-Spip-Cache'])) return _IS_BOT?-1:1; |
---|
91 | |
---|
92 | // controle de la signature |
---|
93 | if ($page['sig'] !== cache_signature($page)) |
---|
94 | return _IS_BOT?-1:1; |
---|
95 | |
---|
96 | // #CACHE{n,statique} => on n'invalide pas avec derniere_modif |
---|
97 | // cf. ecrire/public/balises.php, balise_CACHE_dist() |
---|
98 | if (!isset($page['entetes']['X-Spip-Statique']) OR $page['entetes']['X-Spip-Statique'] !== 'oui') { |
---|
99 | |
---|
100 | // Cache invalide par la meta 'derniere_modif' |
---|
101 | // sauf pour les bots, qui utilisent toujours le cache |
---|
102 | if (!_IS_BOT |
---|
103 | AND $GLOBALS['derniere_modif_invalide'] |
---|
104 | AND $date < $GLOBALS['meta']['derniere_modif']){ |
---|
105 | // pour les admins on invalide tout de suite pour leur permettre de voir les modifs |
---|
106 | if (isset($GLOBALS['visiteur_session']['statut']) |
---|
107 | AND $GLOBALS['visiteur_session']['statut']=='0minirezo') |
---|
108 | return 1; |
---|
109 | |
---|
110 | // pour les autres on invalide progressivement pour repartir la charge |
---|
111 | // avec une fonction de probabilite lineaire qui vaut |
---|
112 | // 5% quand t=derniere_modif |
---|
113 | // 100% quand t=derniere_modif+_DUREE_INVALIDATION_PROGRESSIVE_CACHE |
---|
114 | static $refresh_ok = null; |
---|
115 | if (is_null($refresh_ok)){ |
---|
116 | $dt = $_SERVER['REQUEST_TIME']-$GLOBALS['meta']['derniere_modif']; |
---|
117 | if ($dt>=_DUREE_INVALIDATION_PROGRESSIVE_CACHE){ |
---|
118 | $refresh_ok = 1; |
---|
119 | #spip_log("Cache refresh systematique : REFRESH", "dbgcache"); |
---|
120 | } |
---|
121 | else { |
---|
122 | if (_DUREE_INVALIDATION_PROGRESSIVE_CACHE){ |
---|
123 | $coeff = (1-$dt/_DUREE_INVALIDATION_PROGRESSIVE_CACHE); |
---|
124 | $seuil = 15; // 15% de probabilite au depart |
---|
125 | $prob = mt_rand(1, $seuil+(100-$seuil)*$coeff); |
---|
126 | $refresh_ok = ($prob<$seuil ? 1 : 0); |
---|
127 | #spip_log("Cache refresh progresif dt=$dt coeff=$coeff p=$prob" . ($refresh_ok ? " : REFRESH" : ""), "dbgcache"); |
---|
128 | } |
---|
129 | else { |
---|
130 | $refresh_ok = 1; |
---|
131 | # spip_log("Cache refresh PAS progresif REQUEST_TIME=".$_SERVER['REQUEST_TIME'].", derniere_modif=".$GLOBALS['meta']['derniere_modif']." dt=$dt", "erreur_memoization"); |
---|
132 | }; |
---|
133 | } |
---|
134 | } |
---|
135 | |
---|
136 | // si pas de refresh force on laisse la main a la comparaison de date |
---|
137 | // selon duree de cache |
---|
138 | if ($refresh_ok) |
---|
139 | return 1; |
---|
140 | } |
---|
141 | |
---|
142 | } |
---|
143 | |
---|
144 | // Sinon comparer l'age du fichier a sa duree de cache |
---|
145 | $duree = intval($page['entetes']['X-Spip-Cache']); |
---|
146 | $cache_mark = (isset($GLOBALS['meta']['cache_mark'])?$GLOBALS['meta']['cache_mark']:0); |
---|
147 | if ($duree == 0) #CACHE{0} |
---|
148 | return -1; |
---|
149 | // sauf pour les bots, qui utilisent toujours le cache |
---|
150 | else if ((!_IS_BOT AND $date + $duree < $now) |
---|
151 | # le cache est anterieur a la derniere purge : l'ignorer, meme pour les bots |
---|
152 | OR $date<$cache_mark) { |
---|
153 | if (_IS_BOT) return -1; |
---|
154 | |
---|
155 | // si la charge est trop elevee on accepte de prendre un vieux cache |
---|
156 | $load = function_exists('sys_getloadavg') ? sys_getloadavg() : array(0); |
---|
157 | if ($load[0]>20) { |
---|
158 | spip_log('load eleve ('. intval($load[0]).'), utilisation du cache pour '.var_export($page['source'],true).' sur '.self(), 'debug'); |
---|
159 | return 0; |
---|
160 | } |
---|
161 | |
---|
162 | // sinon on calcule |
---|
163 | return 1; |
---|
164 | } else { |
---|
165 | return 0; |
---|
166 | } |
---|
167 | } |
---|
168 | |
---|
169 | // Creer le fichier cache |
---|
170 | # Passage par reference de $page par souci d'economie |
---|
171 | // https://code.spip.net/@creer_cache |
---|
172 | function creer_cache(&$page, &$chemin_cache, &$memo) { |
---|
173 | |
---|
174 | // Ne rien faire si on est en preview, debug, ou si une erreur |
---|
175 | // grave s'est presentee (compilation du squelette, MySQL, etc) |
---|
176 | // le cas var_nocache ne devrait jamais arriver ici (securite) |
---|
177 | // le cas spip_interdire_cache correspond a une ereur SQL grave non anticipable |
---|
178 | if ((defined('_VAR_NOCACHE') AND _VAR_NOCACHE) |
---|
179 | OR (isset($GLOBALS['var_nocache']) AND $GLOBALS['var_nocache']) // compat SPIP 2.x |
---|
180 | OR defined('spip_interdire_cache')) |
---|
181 | return; |
---|
182 | |
---|
183 | // Si la page c1234 a un invalideur de session 'zz', sauver dans |
---|
184 | // 'tmp/cache/MD5(chemin_cache)_zz' |
---|
185 | if (isset($page['invalideurs']) |
---|
186 | AND isset($page['invalideurs']['session'])) { |
---|
187 | // on verifie que le contenu du chemin cache indique seulement |
---|
188 | // "cache sessionne" ; sa date indique la date de validite |
---|
189 | // des caches sessionnes |
---|
190 | if (!is_array($tmp = $memo->get($chemin_cache))) { |
---|
191 | $tmp = array( |
---|
192 | 'invalideurs' => array('session' => ''), |
---|
193 | 'lastmodified' => $_SERVER['REQUEST_TIME'] |
---|
194 | ); |
---|
195 | $ok = $memo->set($chemin_cache, $tmp); |
---|
196 | spip_log((_IS_BOT?"Bot:":"")."Creation du cache sessionne $chemin_cache ". $memo->methode ." pour " |
---|
197 | . $page['entetes']['X-Spip-Cache']." secondes". ($ok?'':' (erreur!)')); |
---|
198 | } |
---|
199 | $chemin_cache .= '_'.$page['invalideurs']['session']; |
---|
200 | } |
---|
201 | |
---|
202 | // ajouter la date de production dans le cache lui meme |
---|
203 | // (qui contient deja sa duree de validite) |
---|
204 | $page['lastmodified'] = $_SERVER['REQUEST_TIME']; |
---|
205 | |
---|
206 | // signer le contenu |
---|
207 | $page['sig']= cache_signature($page); |
---|
208 | |
---|
209 | // compresser si elle est > 16 ko |
---|
210 | if (strlen($page['texte']) > 16384 |
---|
211 | AND function_exists('gzcompress')) { |
---|
212 | $page['texte'] = gzcompress($z = $page['texte']); |
---|
213 | $page['gz'] = true; |
---|
214 | } |
---|
215 | |
---|
216 | // memoizer... |
---|
217 | // on ajoute une heure histoire de pouvoir tourner |
---|
218 | // sur le cache quand la base de donnees est plantee (a tester) |
---|
219 | $ok = $memo->set($chemin_cache, $page, 3600+$page['entetes']['X-Spip-Cache']); |
---|
220 | |
---|
221 | // retablir le texte pour l'afficher |
---|
222 | if (isset($z)) { |
---|
223 | $page['texte'] = $z; |
---|
224 | unset($page['gz']); |
---|
225 | } |
---|
226 | |
---|
227 | spip_log((_IS_BOT?"Bot:":"")."Creation du cache $chemin_cache ". $memo->methode ." pour " |
---|
228 | . $page['entetes']['X-Spip-Cache']." secondes". ($ok?'':' (erreur!)')); |
---|
229 | |
---|
230 | // Inserer ses invalideurs |
---|
231 | /* compat SPIP 1.9 : ne pas appeler les invalideurs du tout */ |
---|
232 | if (!(isset($GLOBALS['spip_version']) AND $GLOBALS['spip_version']<2)) { |
---|
233 | include_spip('inc/invalideur'); |
---|
234 | maj_invalideurs($chemin_cache, $page); |
---|
235 | } |
---|
236 | } |
---|
237 | |
---|
238 | |
---|
239 | // purger un petit cache (tidy ou recherche) qui ne doit pas contenir de |
---|
240 | // vieux fichiers ; (cette fonction ne sert que dans des plugins obsoletes) |
---|
241 | // https://code.spip.net/@nettoyer_petit_cache |
---|
242 | function nettoyer_petit_cache($prefix, $duree = 300) { |
---|
243 | // determiner le repertoire a purger : 'tmp/CACHE/rech/' |
---|
244 | $dircache = sous_repertoire(_DIR_CACHE,$prefix); |
---|
245 | if (spip_touch($dircache.'purger_'.$prefix, $duree, true)) { |
---|
246 | foreach (preg_files($dircache,'[.]txt$') as $f) { |
---|
247 | if ($_SERVER['REQUEST_TIME'] - (@file_exists($f)?@filemtime($f):0) > $duree) |
---|
248 | spip_unlink($f); |
---|
249 | } |
---|
250 | } |
---|
251 | } |
---|
252 | |
---|
253 | |
---|
254 | // Interface du gestionnaire de cache |
---|
255 | // Si son 3e argument est non vide, elle passe la main a creer_cache |
---|
256 | // Sinon, elle recoit un contexte (ou le construit a partir de REQUEST_URI) |
---|
257 | // et affecte les 4 autres parametres recus par reference: |
---|
258 | // - use_cache qui vaut |
---|
259 | // -1 s'il faut calculer la page sans la mettre en cache |
---|
260 | // 0 si on peut utiliser un cache existant |
---|
261 | // 1 s'il faut calculer la page et la mettre en cache |
---|
262 | // - chemin_cache qui est le chemin d'acces au fichier ou vide si pas cachable |
---|
263 | // - page qui est le tableau decrivant la page, si le cache la contenait |
---|
264 | // - lastmodified qui vaut la date de derniere modif du fichier. |
---|
265 | // Elle retourne '' si tout va bien |
---|
266 | // un message d'erreur si le calcul de la page est totalement impossible |
---|
267 | |
---|
268 | // https://code.spip.net/@public_cacher_dist |
---|
269 | function public_cacher($contexte, &$use_cache, &$chemin_cache, &$page, &$lastmodified) { |
---|
270 | $chemin_cache_session = false; |
---|
271 | |
---|
272 | /* compat SPIP 1.9 */ |
---|
273 | if (is_null($contexte) AND function_exists('nettoyer_uri')) |
---|
274 | $contexte = array('uri' => nettoyer_uri()); |
---|
275 | |
---|
276 | static $memo; |
---|
277 | if (!isset($memo)) { |
---|
278 | // cas d'un appel depuis un fichier options charge avant celui de memoization |
---|
279 | if (!class_exists("MCache")){ |
---|
280 | include_spip("memoization_options"); |
---|
281 | } |
---|
282 | $cfg = @unserialize($GLOBALS['meta']['memoization']); |
---|
283 | $memo = new MCache((isset($cfg['pages']) AND $cfg['pages'])? $cfg['pages'] : $cfg['methode']); |
---|
284 | } |
---|
285 | |
---|
286 | /* compat SPIP 1.9 */ |
---|
287 | if (is_array($page) AND !isset($page['entetes']['X-Spip-Cache'])) |
---|
288 | $page['duree'] = $page['entetes']['X-Spip-Cache'] = isset($GLOBALS['delais']) ? $GLOBALS['delais'] : null; |
---|
289 | |
---|
290 | // Second appel, destine a l'enregistrement du cache sur le disque |
---|
291 | if (isset($chemin_cache)) return creer_cache($page, $chemin_cache, $memo); |
---|
292 | |
---|
293 | // Toute la suite correspond au premier appel |
---|
294 | $contexte_implicite = $page['contexte_implicite']; |
---|
295 | |
---|
296 | // Cas ignorant le cache car completement dynamique |
---|
297 | if ($_SERVER['REQUEST_METHOD'] == 'POST' |
---|
298 | OR (substr($contexte_implicite['cache'],0,8)=='modeles/') |
---|
299 | OR (_request('connect')) |
---|
300 | // Mode auteur authentifie appelant de ecrire/ : il ne faut rien lire du cache |
---|
301 | // et n'y ecrire que la compilation des squelettes (pas les pages produites) |
---|
302 | // car les references aux repertoires ne sont pas relatifs a l'espace public |
---|
303 | OR test_espace_prive()) { |
---|
304 | $use_cache = -1; |
---|
305 | $lastmodified = 0; |
---|
306 | $chemin_cache = ""; |
---|
307 | $page = array(); |
---|
308 | return; |
---|
309 | } |
---|
310 | |
---|
311 | // Controler l'existence d'un cache nous correspondant |
---|
312 | $chemin_cache = generer_nom_fichier_cache($contexte, $page); |
---|
313 | $lastmodified = 0; |
---|
314 | |
---|
315 | // charger le cache s'il existe |
---|
316 | if (!is_array($page = $memo->get($chemin_cache))) |
---|
317 | $page = array(); |
---|
318 | |
---|
319 | // s'il est sessionne, charger celui correspondant a notre session |
---|
320 | if (isset($page['invalideurs']) |
---|
321 | AND isset($page['invalideurs']['session'])) { |
---|
322 | $chemin_cache_session = $chemin_cache . '_' . spip_session(); |
---|
323 | if (is_array($page_session = $memo->get($chemin_cache_session)) |
---|
324 | AND $page_session['lastmodified'] >= $page['lastmodified']) |
---|
325 | $page = $page_session; |
---|
326 | else |
---|
327 | $page = array(); |
---|
328 | } |
---|
329 | |
---|
330 | // dezip si on l'a zipe |
---|
331 | if (isset($page['gz'])) { |
---|
332 | $page['texte'] = gzuncompress($page['texte']); |
---|
333 | unset($page['gz']); |
---|
334 | } |
---|
335 | |
---|
336 | if (intval($GLOBALS['spip_version_branche'])<3){ |
---|
337 | // HEAD : cas sans jamais de calcul pour raisons de performance |
---|
338 | // supprime en SPIP 3 par https://core.spip.net/projects/spip/repository/revisions/19959 |
---|
339 | if ($_SERVER['REQUEST_METHOD'] == 'HEAD') { |
---|
340 | $use_cache = 0; |
---|
341 | $page = array('contexte_implicite'=>$contexte_implicite); |
---|
342 | return; |
---|
343 | } |
---|
344 | } |
---|
345 | |
---|
346 | // Si un calcul, recalcul [ou preview, mais c'est recalcul] est demande, |
---|
347 | // on supprime le cache |
---|
348 | if (((isset($GLOBALS['var_mode']) && $GLOBALS['var_mode']) OR (defined('_VAR_MODE') && _VAR_MODE)) && |
---|
349 | (isset($_COOKIE['spip_session']) |
---|
350 | || isset($_COOKIE['spip_admin']) |
---|
351 | || @file_exists(_ACCESS_FILE_NAME)) |
---|
352 | ) { |
---|
353 | $page = array('contexte_implicite'=>$contexte_implicite); // ignorer le cache deja lu |
---|
354 | include_spip('inc/invalideur'); |
---|
355 | if (function_exists('retire_caches')) retire_caches($chemin_cache); # API invalideur inutile |
---|
356 | $memo->del($chemin_cache); |
---|
357 | if ($chemin_cache_session) |
---|
358 | $memo->del($chemin_cache_session); |
---|
359 | } |
---|
360 | |
---|
361 | // $delais par defaut (pour toutes les pages sans #CACHE{}) |
---|
362 | if (!isset($GLOBALS['delais'])) { |
---|
363 | if (!defined('_DUREE_CACHE_DEFAUT')) { |
---|
364 | define('_DUREE_CACHE_DEFAUT', 24*3600); |
---|
365 | } |
---|
366 | $GLOBALS['delais'] = _DUREE_CACHE_DEFAUT; |
---|
367 | } |
---|
368 | |
---|
369 | // determiner la validite de la page |
---|
370 | if ($page) { |
---|
371 | $use_cache = cache_valide($page, isset($page['lastmodified']) ? $page['lastmodified']:null); |
---|
372 | // le contexte implicite n'est pas stocke dans le cache, mais il y a equivalence |
---|
373 | // par le nom du cache. On le reinjecte donc ici pour utilisation eventuelle au calcul |
---|
374 | $page['contexte_implicite'] = $contexte_implicite; |
---|
375 | if (!$use_cache) |
---|
376 | return; |
---|
377 | } else { |
---|
378 | $page = array('contexte_implicite'=>$contexte_implicite); |
---|
379 | $use_cache = cache_valide($page,0); // fichier cache absent : provoque le calcul |
---|
380 | } |
---|
381 | |
---|
382 | // Si pas valide mais pas de connexion a la base, le garder quand meme |
---|
383 | if (!spip_connect()) { |
---|
384 | if (isset($page['texte'])) { |
---|
385 | $use_cache = 0; |
---|
386 | } |
---|
387 | else { |
---|
388 | include_spip('inc/config'); |
---|
389 | spip_log("Erreur base de donnees, impossible utiliser " . lire_config('memoization/methode') . " $chemin_cache"); |
---|
390 | include_spip('inc/minipres'); |
---|
391 | return minipres(_T('info_travaux_titre'), _T('titre_probleme_technique')); |
---|
392 | } |
---|
393 | } |
---|
394 | |
---|
395 | if ($use_cache < 0) $chemin_cache = ''; |
---|
396 | return; |
---|
397 | } |
---|
398 | |
---|
399 | // Faut-il decompresser ce cache ? |
---|
400 | // (passage par reference pour alleger) |
---|
401 | // https://code.spip.net/@gunzip_page |
---|
402 | function gunzip_page(&$page) { |
---|
403 | if ($page['gz']) { |
---|
404 | $page['texte'] = gzuncompress($page['texte']); |
---|
405 | $page['gz'] = false; // ne pas gzuncompress deux fois une meme page |
---|
406 | } |
---|
407 | } |
---|
408 | |
---|
409 | ?> |
---|