You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

476 lines
16 KiB

<?php
if (!defined('_ECRIRE_INC_VERSION')) {
return;
}
include_spip('inc/cachelab_utils');
/**
*
* Applique une action sur un cache donné et renvoie éventuellement une donnée
* Nécessite Mémoization (toutes méthodes OK).
*
* @param string $action del, pass, list, clean, list_html, get, get_html ou user defined
* @param string $cle clé du cache ciblé
* @param null|array $data valeur du cache pour cette clé (pas forcément fourni)
* @param $options pour les fonctions cachelab_cibler_{action}
* @param &$return résultat éventuellement fourni, pour les actions list et get
* @return bool indique si l'action a pu être appliquée ou non
*/
function cachelab_appliquer(string $action, string $cle, $data = null, $options = '', &$return = null) : bool {
$Memoization = memoization();
if (!isset($Memoization) or !$Memoization) {
spip_log("cachelab_appliquer ($action, $cle...) : Memoization n'est pas activé", 'ERREUR_cachelab');
return false;
}
static $len_prefix;
if (!$len_prefix) {
$len_prefix = strlen(_CACHE_NAMESPACE);
}
$joliecle = substr($cle, $len_prefix);
switch ($action) {
case 'del':
$del = $Memoization->del($joliecle);
if (!$del) {
spip_log("Échec 'del' $joliecle", 'ERREUR_cachelab');
return false;
}
break;
// entièrement gérés par cachelab_cibler
case 'pass': // passe
case 'list': // renvoie les clés
case 'clean': // nettoie
break;
case 'list_html': // renvoie les contenus indexés par les clés sans préfixes
// attention ça peut grossir !
if (!is_array($return)) {
$return = array();
}
$return[$joliecle] = $data['texte'];
break;
case 'get': // renvoie le 1er cache ciblé
if (!$data) {
$data = $Memoization->get($joliecle);
}
$return = $data;
break;
case 'get_html': // renvoie le contenu du 1er cache
if (!$data) {
$data = $Memoization->get($joliecle);
}
/* Dans les usages actuels, get_html DOIT retourner une chaine.
Mais certains squelettes calculent des #ARRAY par exemple
TODO : une commande 'get_contenu' qui renverrait le contenu d'un cache même si c'est pas une chaine */
if (!$data or !is_array($data) or !isset($data['texte']) or !is_string ($data['texte'])) {
return '';
}
$return = $data['texte'];
break;
default:
$f = 'cachelab_appliquer_'.$action;
if (function_exists($f)) {
return $f($action, $cle, $data, $options, $return);
} else {
spip_log("L'action '$action' n'est pas définie pour cachelab_applique", 'ERREUR_cachelab');
return false;
}
}
return true;
}
/**
* extrait du tableau les différentes conditions précodées
* @param array $conditions
* @return array ($session, $chemin, $chemins, $cle_objet, $id_objet, $plusfunc)
*/
function cachelab_prepare_conditions(array $conditions) {
// filtrage
$session = (isset($conditions['session']) ? $conditions['session'] : null);
if ($session=='courante') {
$session = spip_session();
}
$chemin = (isset($conditions['chemin']) ? $conditions['chemin'] : null);
$chemins = ($chemin ? explode('|', $chemin) : null); // sert seulement pour methode_chemin == strpos
$cle_objet = (isset($conditions['cle_objet']) ? $conditions['cle_objet'] : null);
$id_objet = (isset($conditions['id_objet']) ? $conditions['id_objet'] : null);
if ($cle_objet and !$id_objet) {
spip_log("cachelab_cibler : $cle_objet inconnu\n".print_r(debug_backtrace(), 1), 'ERREUR_cachelab');
$cle_objet=null;
}
$plus_args=[];
// pour 'contexte' on simule un 'plus' pour donner un exemple d'extension
if (isset($conditions['contexte']) and $conditions['contexte'] and !isset($conditions['plus'])) {
$conditions['plus'] = 'contexte';
$plus_args = $conditions['contexte'];
}
$plus = (isset($conditions['plus']) ? (string)$conditions['plus'] : '');
if ($plus) {
$plusfunc='cachelab_ciblercache_'.$plus;
// Signature nécessaire : $plusfunc ($action, $conditions, $options, &$stats)
if (!function_exists($plusfunc)) {
spip_log("La fonction '$plusfunc' n'est pas définie", 'ERREUR_cachelab');
return null;
}
if(!$plus_args and isset($conditions['plus_args'])) {
$plus_args = $conditions['plus_args'];
}
} else {
$plusfunc = '';
}
return [
'session' => $session,
'chemin' => $chemin,
'chemins' => $chemins,
'cle_objet' => $cle_objet,
'id_objet' => $id_objet,
'plusfunc' => $plusfunc,
'plus_args' => $plus_args
];
}
/**
*
* Applique une action donnée à tous les caches vérifiant certaines conditions
*
* @uses apcu_cache_info() et donc nécessite que Memoization soit activé avec APC ou APCu
*
* @param string $action l'action à appliquer : del, pass, list, clean, list_html, get, get_html ou user defined
* @param array $conditions les conditions définissant la cible
* @param array $options options de l'action et/ou des conditions
* @return array|null
* le résultat si c'est une action 'get' ou 'get_...'
* la liste des stats sinon,
* avec éventuellement la liste des résultats, pour action 'list' ou 'list_html' ou option 'list'
*
*/
function cachelab_cibler(string $action, array $conditions = array(), array $options = array()) {
$Memoization = memoization();
if (!isset($Memoization) or !$Memoization or !in_array($Memoization->methode(), array('apc', 'apcu'))) {
spip_log("cachelab_cibler($action...) : Mémoization n'est pas activé avec APC ou APCu", 'ERREUR_cachelab');
die("cachelab_cibler($action...) : le plugin Mémoization doit être activé avec APC ou APCu");
}
$return = $session = $chemin = $chemins = $cle_objet = $id_objet = $plusfunc = null;
// Prise en compte des 'OU' ('OR', alternatives de conditions)
$l_conditions = (isset($conditions['ou']) ? $conditions['ou'] : '') or (isset($conditions['or']) ? $conditions['or'] : '');
if (!$l_conditions) {
$l_conditions = array($conditions);
}
elseif (!is_array($l_conditions)) {
spip_log ("La condition OU ou OR pour cachelab_cibler($action,...) n'est pas un tableau : " . print_r ($conditions, 1), 'ERREUR_cachelab');
return null;
}
else {
spip_log("OU liste operandes = ".print_r($l_conditions,1), "cachelab_ou");
}
// $l_conditions est un tableau de conditions élémentaires
// La condition globale est satisfaite si l'une des condition est satisfaite (OR)
// Chaque condition élémentaire est satisfaite si chacune de ses composantes est satisfaite (AND)
$l_conditions = array_map ('cachelab_prepare_conditions', $l_conditions);
// prend la forme d'un tableau de conditions précalculées [$session, $chemin, $chemins, $cle_objet, $id_objet, $plusfunc]
if (_request('exec')=='xray' and _request('debug')=='cachelab') echo "<h3>Les conditions préparées</h3><xmp>".print_r($l_conditions,1)."</xmp>";
// options
// explode+strpos par défaut pour les chemins
$methode_chemin = (isset($options['methode_chemin']) ? $options['methode_chemin'] : 'strpos');
$partie_chemin = (isset($options['partie_chemin']) ? $options['partie_chemin'] : 'tout');
// clean par défaut
$do_clean = (isset($options['clean']) ? $options['clean'] : (!defined('CACHELAB_CLEAN') or CACHELAB_CLEAN));
// pas de listes par défaut
$do_lists = ($action == 'list') or (isset($options['list']) and $options['list']);
include_spip('lib/microtime.inc');
microtime_do('begin');
// retours
$stats=array();
$stats['nb_alien']=$stats['nb_candidats']=$stats['nb_clean']=$stats['nb_cible']=0;
$stats['l_cible'] = array();
// On y va
$cache = apcu_cache_info();
$meta_derniere_modif = $GLOBALS['meta']['derniere_modif'];
$len_prefix = strlen(_CACHE_NAMESPACE);
$n_cache = 0;
$time = time ();
$msg_mystere = '';
foreach ($cache['cache_list'] as $d) {
$n_cache++;
// on "continue=passe au suivant" dés qu'on sait que le cache n'est pas cible
$cle = $d['info'];
$mdata=null; // donnée mémoization
// on passe les caches non concernés car d'autres origines
// (et les caches d'un autre _CACHE_NAMESPACE pour ce même site)
if (strpos($cle, _CACHE_NAMESPACE) !== 0) {
$stats['nb_alien']++;
continue;
}
// on ne veut examiner que les caches de squelettes SPIP
if (substr($cle, $len_prefix-1, 7) != ':cache:') {
continue;
}
// effacer ou sauter les caches invalidés par une invalidation totale
// ou que apcu ne suit plus
if (
(!($a_exists = apcu_exists($cle))) // cache apcu disparu
or $meta_derniere_modif > $d['creation_time'] // invalidation spip
or ($d['creation_time'] + $d['ttl'] <= $time) // cache APCU périmé
) {
if ($do_clean) {
$memoiz_cle = substr($cle, $len_prefix);
$ok_del = false;
// Avant ce test il arrivait parfois des salves de 10 à 50 logs d'échec du clean cache simultanés (mm t, mm pid)
if ($m_exists = $Memoization->exists($memoiz_cle)) {
$ok_del = $Memoization->del ($memoiz_cle);
}
if (!$ok_del) {
$ok_del = apcu_delete($cle); // bourrin
}
if ($ok_del) {
$stats['nb_clean']++;
}
else {
spip_log ('Echec clean pour '.print_r([
'cle'=>$cle, 'apcu_exists'=>$a_exists, 'M_exists'=>$m_exists,
'meta_derniere_modif'=>$meta_derniere_modif, 'creation'=>$d['creation_time'], 'ttl'=>$d['ttl']],
1), "cachelab_mystere");
}
}
continue;
}
// Il reste les caches SPIP véritablement candidats
$stats['nb_candidats']++;
$n_condition=0;
$cible = false;
// La premiere condition élémentaire composée entièrement satisfaite
foreach ($l_conditions as $i_conditions) {
$n_condition++;
$session = $i_conditions['session'];
$chemin = $i_conditions['chemin'];
$chemins = $i_conditions['chemins'];
$cle_objet = $i_conditions['cle_objet'];
$id_objet = $i_conditions['id_objet'];
$plusfunc = $i_conditions['plusfunc'];
// $plus_args = $plusfunc['plus_args']; utilisé seulement dans la plusfunc
if (($n_cache==1) and (_request('exec')=='xray') and (_request('debug')=='cachelab')) {
echo "<b>Condition $n_condition</b> : chemin=$chemin, plusfunc=$plusfunc<br><xmp>".print_r($i_conditions,1)."</xmp>";
}
// 1er filtrage : par la session
if ($session) {
if (substr ($cle, -9) != "_$session") {
continue;
// sur chaque échec on passe à la condition suivante dans le cas d'un OU ou d'un OR
}
}
// 2eme filtrage : par le chemin
if ($chemins) {
switch ($partie_chemin) {
case 'tout':
case 'chemin':
$partie_cle = $cle;
break;
case 'fichier':
$parties = explode ('/', $cle);
$partie_cle = array_pop ($parties);
break;
case 'dossier':
$parties = explode ('/', $cle);
$parties = array_pop ($parties);
$partie_cle = array_pop ($parties);
break;
default:
spip_log ("Option partie_chemin incorrecte : '$partie_chemin'", 'ERREUR_cachelab');
return null;
}
// mémo php : « continue resumes execution just before the closing curly bracket },
// and break resumes execution just after the closing curly bracket } »
switch ($methode_chemin) {
case 'strpos':
foreach ($chemins as $unchemin) {
if ($unchemin and (strpos ($partie_cle, $unchemin) !== false)) {
break 2; // trouvé : sort du foreach et du switch et poursuit le test des autres conditions
}
}
continue 2; // échec : passe à la $cle suivante
case '==' :
case 'egal' :
case 'equal':
foreach ($chemins as $unchemin) {
if ($unchemin == $partie_cle) {
break 2; // trouvé : sort du foreach et du switch et poursuit le test des autres conditions
}
}
continue 2; // échec : passe à la $cle suivante
case 'regexp':
if ($chemin and ($danslechemin = preg_match (",$chemin,i", $partie_cle))) {
break; // trouvé : poursuit le test des autres conditions
}
continue 2; // échec : passe à la clé suivante
default:
spip_log ("Méthode '$methode_chemin' pas prévue pour le filtrage par le chemin", 'ERREUR_cachelab');
return null;
}
}
// pour les filtres suivants on a besoin du contenu du cache
if ($cle_objet or $plusfunc) {
$mdata = $Memoization->get (substr ($cle, $len_prefix));
if (!$mdata or !is_array ($mdata)) {
spip_log ("clé=$cle : mdata est vide ou n'est pas un tableau : " . print_r ($mdata, 1), 'ERREUR_cachelab');
continue;
}
// 3eme filtre : par une valeur dans l'environnement
if ($cle_objet
and (!isset($mdata['contexte'][$cle_objet])
or ($mdata['contexte'][$cle_objet] != $id_objet))) {
continue;
}
// 4eme filtre : par une extension
if ($plusfunc
and !$plusfunc($action, $i_conditions, $options, $cle, $mdata, $stats)) {
continue;
}
}
// Dès qu'une des conditions composée est satisfaite on sort de la boucle
$cible = true;
break;
}
if (!$cible) {
// si on est sorti suite sans avoir trouvé on passe au cache suivant
continue;
}
// restent les cibles atteintes
$stats['nb_cible']++;
if ($do_lists) {
$stats['l_cible'][] = $cle;
}
if (_request('exec')=='xray' and _request('debug')=='cachelab') {
echo "Avec condition $n_condition trouvé $cle, nb_cible devient ".$stats['nb_cible']."<br>";
}
cachelab_appliquer($action, $cle, $mdata, $options, $return);
if ($return
and (($action=='get')
or (substr($action, 0, 4)=='get_'))) {
return $return; // TODO chrono aussi dans ce cas
}
}
if ($msg_mystere) {
spip_log ($msg_mystere, 'cachelab_mystere');
}
$stats['chrono'] = microtime_do('end', 'ms');
$msg = "cachelab_cibler($action) en {$stats['chrono']} ({$stats['nb_cible']} caches sur {$stats['nb_candidats']})"
."\n".print_r($conditions, 1);
if (count($options)) {
$msg .= "\noptions = ".print_r($options, 1);
}
if (defined('LOG_CACHELAB_CHRONO') and LOG_CACHELAB_CHRONO) {
if (function_exists ('debug_log')) {
debug_log ($msg, 'cachelab_chrono');
}
else {
spip_log ($msg, 'cachelab_chrono.' . _LOG_INFO);
}
}
if (defined('LOG_CACHELAB_SLOW') and ($stats['chrono'] > LOG_CACHELAB_SLOW)) {
if (function_exists ('debug_log')) {
debug_log ($msg, 'cachelab_slow', true);
}
else {
spip_log ($msg, 'cachelab_slow.' . _LOG_INFO_IMPORTANTE);
}
}
if (($action=='ok_del') and defined('LOG_CACHELAB_TOOMANY_DEL') and ($stats['nb_cible'] > LOG_CACHELAB_TOOMANY_DEL)) {
if (function_exists ('debug_log')) {
debug_log ($msg, 'cachelab_toomany_del', true);
}
else {
spip_log($msg, 'cachelab_toomany_del.'._LOG_INFO_IMPORTANTE);
}
}
if ($return) {
$stats['val'] = $return;
}
return $stats;
}
/**
* @param string $action
* @param array $objets_invalidants
*/
function controler_invalideur(string $action, array $objets_invalidants = array()) {
static $prev_derniere_modif_invalide;
switch ($action) {
case 'stop':
$objets_invalidants = array();
// nobreak;
case 'select':
$prev_derniere_modif_invalide = $GLOBALS['derniere_modif_invalide'];
if (is_array($objets_invalidants)) {
$GLOBALS['derniere_modif_invalide'] = $objets_invalidants;
}
break;
case 'go':
$GLOBALS['derniere_modif_invalide'] = $prev_derniere_modif_invalide;
break;
}
}
//
// Exemple d'extension utilisable avec 'plus'=>'contexte'
// Filtrer non sur une seule valeur de l'environnement comme avec 'cle_objet'
// mais sur un ensemble de valeurs spécifié par $conditions['contexte']
// qui est un tableau de (clé, valeur)
// Toutes les valeurs doivent être vérifiées dans l'environnement.
//
/**
* @param string $action
* @param array $conditions
* @param array $options
* @param string $cle
* @param array $data
* @param array $stats
* @return bool
*/
function cachelab_ciblercache_contexte(string $action, array $conditions, $options, $cle, array &$data, &$stats) {
if (!isset($data['contexte'])) {
return false;
}
if (!isset($conditions['plus_args']) or !is_array($conditions['plus_args'])) {
// debug_assert(false, "cachelab_ciblercache_contexte sans contexte dans plus_args($action, conditions=".print_r($conditions,1));
// la phpstack fait planter donc pas d'assert
$m = "cachelab_ciblercache_contexte sans contexte dans plus_args ($action, conditions=<xmp>".print_r($conditions,1)."</xmp>";
echo "ERREUR $m";
spip_log($m, "ERREUR_cachelab_contexte");
return false;
}
$diff = array_diff_assoc($conditions['plus_args'], $data['contexte']);
return empty($diff);
}