3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
6 * Copyright (c) 2001-2016 *
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 * Action d'archivage des statistiques
16 * @plugin Statistiques pour SPIP
18 * @package SPIP\Stats\Actions
21 if (!defined("_ECRIRE_INC_VERSION")) {
26 if (!defined('STATISTIQUES_ARCHIVER_PAR_MOIS')) {
28 * Nombre d'années après quoi on permet de concaténer les statistiques de visites par mois
30 * Après ce nombre d'années, on peut concaténer les données de visites d'articles par mois
31 * pour prendre moins de place dans la base de données
33 * @var int Nombre d'années
35 define('STATISTIQUES_ARCHIVER_PAR_MOIS', 2);
38 if (!defined('STATISTIQUES_ARCHIVER_PAR_AN')) {
40 * Nombre d'années après quoi on permet de concaténer les statistiques de visites par an
42 * Après ce nombre d'années, on peut concaténer les données de visites d'articles par années
43 * pour prendre moins de place dans la base de données
45 * @var int Nombre d'années
47 define('STATISTIQUES_ARCHIVER_PAR_AN', 5);
52 * Archiver ou nettoyer des statistiques
56 function action_statistiques_archiver_dist($arg = null) {
58 $securiser_action = charger_fonction('securiser_action', 'inc');
59 $arg = $securiser_action();
62 if (!autoriser('webmestre')) {
63 include_spip('inc/minipres');
67 if (!in_array($arg, array(
68 'archiver_visites_articles',
69 'nettoyer_visites_articles',
70 'nettoyer_referers_articles'
73 include_spip('inc/minipres');
74 minipres("Argument non compris");
77 $func = 'statistiques_' . $arg;
83 * Logguer ces informations importantes.
86 * @param string $texte
88 function statistiques_archiver_log($texte) {
89 spip_log($texte, 'statistiques_archiver.' . _LOG_INFO_IMPORTANTE
);
93 * Nettoyer des lignes de visites d'articles incorrectes
95 * Supprime toutes les lignes qui ne font pas partie
96 * d'un article présent en base
98 function statistiques_nettoyer_visites_articles() {
99 statistiques_archiver_log("Supprimer les visites d'articles qui n'existent pas dans spip_articles.");
100 $i = sql_delete('spip_visites_articles', 'id_article NOT IN (SELECT id_article FROM spip_articles)');
101 statistiques_archiver_log("Fin de la suppression : $i lignes supprimées");
105 * Nettoyer des lignes de referers d'articles incorrectes
107 * Supprime toutes les lignes qui ne font pas partie
108 * d'un article présent en base
110 function statistiques_nettoyer_referers_articles() {
111 statistiques_archiver_log("Supprimer les referers d'articles qui n'existent pas dans spip_articles.");
112 $i = sql_delete('spip_referers_articles', 'id_article NOT IN (SELECT id_article FROM spip_articles)');
113 statistiques_archiver_log("Fin de la suppression : $i lignes supprimées");
117 * Archiver les visites d'articles
120 * Cela peut prendre beaucoup de temps.
122 * La base de test avait (en 2014) 12.500.000 d'entrées depuis 2005.
123 * Cet archivage réduit à 1.200.000 entrées en réduisant
124 * par mois jusqu'à 2012 inclu et par an jusqu'à 2009 inclu.
126 * Cela prenait 8 minutes sur ma machine locale
127 * (Intel Core i5-4258U CPU @ 2.40GHz × 4 avec disque SSD)
130 * On peut suivre l'avancement dans le fichier de log
131 * tail -f tmp/log/statistiques_archiver.log
134 * On ne peut pas vraiment avec le code actuel de la fonction
135 * appliquer les calculs sur l'ensemble d'un mois car cela
136 * peut facilement surcharger la mémoire de php.
138 * Du coup, on applique par petit bouts d'abord.
140 * @uses statistiques_concatener_visites_entre_jours()
141 * @uses statistiques_concatener_visites_par_mois()
142 * @uses statistiques_concatener_visites_par_an()
144 function statistiques_archiver_visites_articles() {
146 // Tenter de donner du temps au temps
147 @set_time_limit
(15 * 60); // 15mn
149 $annee_par_mois = date('Y') - STATISTIQUES_ARCHIVER_PAR_MOIS
;
150 $annee_par_an = date('Y') - STATISTIQUES_ARCHIVER_PAR_AN
;
152 $annee_minimum = statistiques_concatener_annee_minimum();
153 if (!$annee_minimum) {
157 if ($annee_minimum > $annee_par_mois) {
158 statistiques_archiver_log("Il n'y a pas de statistiques assez anciennes pour concaténer par mois !");
160 // en plusieurs temps pour éviter trop de mémoire !
161 statistiques_concatener_visites_entre_jours($annee_par_mois, 1, 10);
162 statistiques_concatener_visites_entre_jours($annee_par_mois, 11, 20);
163 statistiques_concatener_visites_entre_jours($annee_par_mois, 21, 31);
165 // et on regroupe tout en 1 seul morceau.
166 statistiques_concatener_visites_par_mois($annee_par_mois);
169 if ($annee_minimum > $annee_par_an) {
170 statistiques_archiver_log("Il n'y a pas de statistiques assez anciennes pour concaténer par an !");
172 // et les vieilles années, on regroupe par an directement.
173 statistiques_concatener_visites_par_an($annee_par_an);
176 statistiques_archiver_log("* Optimiser la table spip_visites_articles après les travaux.");
177 sql_optimize('spip_visites_articles');
181 * Concatène les statistiques de visites d'articles par mois
183 * @see statistiques_concatener_visites_entre_jours()
186 * On concatène ce qui est avant cette année là.
188 function statistiques_concatener_visites_par_mois($annee) {
189 return statistiques_concatener_visites_entre_jours($annee, 1, 31);
194 * Concatène les statistiques de visites d'articles par portion de mois (entre groupe de jours)
197 * On concatène ce qui est avant cette année là.
199 * Numéro de jour du début de la concaténation, exemple 1.
200 * Le total des visites concaténé sera mis dans ce jour là.
202 * Numéro de jour de fin de la concaténation, exemple 31.
203 * Toutes les entrées entre le jour $debut+1 et $fin seront supprimées
204 * et concaténées au jour $debut.
207 function statistiques_concatener_visites_entre_jours($annee, $debut, $fin) {
209 $annee_minimum = statistiques_concatener_annee_minimum();
210 if (!$annee_minimum) {
214 if ($annee_minimum > $annee) {
215 statistiques_archiver_log("Il n'y a pas de statistiques assez anciennes !");
220 // on a besoin pour le champ date d'une écriture sur 2 chiffres.
221 $debut = str_pad($debut, 2, '0', STR_PAD_LEFT
);
222 $fin = str_pad($fin, 2, '0', STR_PAD_LEFT
);
224 statistiques_archiver_log("\nConcaténer les visites d'articles (jours entre $debut et $fin)");
225 statistiques_archiver_log("===========================================================");
227 $annees = range($annee_minimum, $annee);
228 $mois = range(1, 12);
230 foreach ($annees as $a) {
231 statistiques_archiver_log("\n- Concaténer les visites de l'année : $a");
233 foreach ($mois as $m) {
234 $m = str_pad($m, 2, '0', STR_PAD_LEFT
);
235 statistiques_concatener_visites_entre_periode("$a-$m-$debut", "$a-$m-$fin");
242 * Retourne la plus petite année des visites d'articles
246 * - false : année non trouvée.
248 function statistiques_concatener_annee_minimum() {
249 static $annee_minimum = null;
251 // calcul de la plus petite année de statistiques
252 if (is_null($annee_minimum)) {
253 $annee_minimum = sql_getfetsel('YEAR(MIN(date))', 'spip_visites_articles', '', '', '', '0,1');
256 if (!$annee_minimum) {
257 statistiques_archiver_log("Erreur de calcul de la plus petite année de statistiques !");
262 return $annee_minimum;
267 * Concatène les statistiques de visites d'articles par an
270 * On concatène ce qui est avant cette année là.
273 function statistiques_concatener_visites_par_an($annee) {
275 $annee_minimum = statistiques_concatener_annee_minimum();
276 if (!$annee_minimum) {
280 if ($annee_minimum > $annee) {
281 statistiques_archiver_log("Il n'y a pas de statistiques assez anciennes !");
286 statistiques_archiver_log("\nConcaténer les visites d'articles (par an)");
287 statistiques_archiver_log("===========================================================");
289 $annees = range($annee_minimum, $annee);
291 foreach ($annees as $a) {
292 statistiques_archiver_log("\n- Concaténer les visites de l'année : $a");
293 statistiques_concatener_visites_entre_periode("$a-01-01", "$a-12-31");
299 * Concatène les statistiques de visites d'articles entre 2 périodes.
301 * @param string $date_debut
302 * Date de début tel que '2010-01-01'
303 * @param string $date_fin
304 * Date de fin tel que '2010-12-31'
306 * - false : aucune visite sur cette période
307 * - true : il y avait des visites, elles ont été concaténées (ou l'étaient déjà)
310 function statistiques_concatener_visites_entre_periode($date_debut, $date_fin) {
312 // récupérer toutes les visites de cette période (année, mois, entre jour début et fin)
313 $visites = sql_allfetsel('id_article, date, visites', 'spip_visites_articles', array(
314 "date >= " . sql_quote($date_debut),
315 "date <= " . sql_quote($date_fin),
322 $liste = $updates = array();
325 // - Crée un tableau plus simple (id_article => total des visites de la période) (permettant un array_diff_key facile).
326 // - Calcule au passage le total des visites de la période (pour le log)
327 // - Rempli un autre tableau ($updates) qui indique si cet article doit avoir ses visites concaténées sur cette période,
328 // c'est à dire, si il y a une date qui n'est pas le début de période.
329 // (évite de nombreuses requêtes si l'on exécute plusieurs fois le script)
330 foreach ($visites as $v) {
331 $id_article = $v['id_article'];
332 if (!isset($liste[$id_article])) {
333 $liste[$id_article] = 0;
335 $liste[$id_article] +
= $v['visites'];
336 $total +
= $v['visites'];
337 if ($v['date'] != $date_debut) {
338 $updates[$id_article] = true;
344 $nb_articles = count($liste);
346 // juste ceux qui nécessitent une mise à jour (date <> de $debut de période)
347 $liste = array_intersect_key($liste, $updates);
349 statistiques_archiver_log("-- du $date_debut au $date_fin : $total visites dans $nb_articles articles");
353 // formater pour l'insertion dans la base.
355 foreach ($liste as $id_article => $visites) {
357 'id_article' => $id_article,
358 'date' => $date_debut,
359 'visites' => $visites,
363 statistiques_archiver_log("--- concaténer les statistiques de " . count($liste) . " articles");
366 // Entre ces 2 requêtes, on peut perdre des données (si timeout ou autre)
367 // Transaction à faire ?
369 sql_delete('spip_visites_articles', array(
370 "date >= " . sql_quote($date_debut),
371 "date <= " . sql_quote($date_fin),
372 sql_in('id_article', array_keys($liste)),
375 sql_insertq_multi('spip_visites_articles', $inserts);
378 unset($liste, $inserts);