source: spip-zone/_plugins_/memoization/trunk/public/cacher.php @ 104337

Last change on this file since 104337 was 104337, checked in by spip.franck@…, 4 years ago
File size: 15.1 KB
Line 
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
13if (!defined("_ECRIRE_INC_VERSION")) return;
14
15// par defaut pas de progressivite d'invalidation du cache : duree=0
16if (!defined("_DUREE_INVALIDATION_PROGRESSIVE_CACHE"))
17        define("_DUREE_INVALIDATION_PROGRESSIVE_CACHE",0);
18
19/* compat SPIP 1.9 */
20if(!function_exists('test_espace_prive')) {
21        function test_espace_prive() {
22                return !!_DIR_RACINE;
23        }
24}
25/* compat SPIP 2.1 */
26if(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
33function 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
48function 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
72function 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
172function 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
242function 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
269function 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
402function 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?>
Note: See TracBrowser for help on using the repository browser.