e3cf0c5a1b1c4d40194e94fb88583ee0606e330a
[lhc/web/www.git] / www / ecrire / inc / rubriques.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 /**
14 * Fichier gérant l'actualisation et le suivi des rubriques, et de leurs branches
15 *
16 * @package SPIP\Core\Rubriques
17 */
18
19 if (!defined('_ECRIRE_INC_VERSION')) {
20 return;
21 }
22
23
24 /**
25 * Recalcule les statuts d'une rubrique
26 *
27 * Fonction à appeler lorsque le statut d'un objet change dans une rubrique
28 * ou que la rubrique est deplacée.
29 *
30 * Si le statut passe a "publie", la rubrique et ses parents y passent aussi
31 * et les langues utilisees sont recalculées.
32 * Conséquences symétriques s'il est depublié.
33 *
34 * S'il est deplacé alors qu'il était publiée, double conséquence.
35 *
36 * Tout cela devrait passer en SQL, sous forme de Cascade SQL.
37 *
38 * @uses depublier_branche_rubrique_if()
39 * @uses calculer_prochain_postdate()
40 * @uses publier_branche_rubrique()
41 *
42 * @param int $id_rubrique
43 * Identifiant de la rubrique
44 * @param array $modifs
45 * Tableau de description des modifications.
46 * Peut avoir 2 index, 'statut' étant obligatoire :
47 * - statut : indique le nouveau statut de la rubrique
48 * - id_rubrique : indiquer la rubrique dans laquelle on déplace la rubrique (son nouveau parent donc)
49 * @param string $statut_ancien
50 * Ancien statut de la rubrique
51 * @param bool $postdate
52 * true pour recalculer aussi la date du prochain article post-daté
53 * @return bool
54 * true si le statut change effectivement
55 **/
56 function calculer_rubriques_if($id_rubrique, $modifs, $statut_ancien = '', $postdate = false) {
57 $neuf = false;
58 if ($statut_ancien == 'publie') {
59 if (isset($modifs['statut'])
60 or isset($modifs['id_rubrique'])
61 or ($postdate and strtotime($postdate) > time())
62 ) {
63 $neuf |= depublier_branche_rubrique_if($id_rubrique);
64 }
65 // ne publier que si c'est pas un postdate, ou si la date n'est pas dans le futur
66 if ($postdate) {
67 calculer_prochain_postdate(true);
68 $neuf |= (strtotime($postdate) <= time()); // par securite
69 } elseif (isset($modifs['id_rubrique'])) {
70 $neuf |= publier_branche_rubrique($modifs['id_rubrique']);
71 }
72 } elseif (isset($modifs['statut']) and $modifs['statut'] == 'publie') {
73 if ($postdate) {
74 calculer_prochain_postdate(true);
75 $neuf |= (strtotime($postdate) <= time()); // par securite
76 } else {
77 $neuf |= publier_branche_rubrique($id_rubrique);
78 }
79 }
80
81 if ($neuf) // Sauver la date de la derniere mise a jour (pour menu_rubriques)
82 {
83 ecrire_meta("date_calcul_rubriques", date("U"));
84 }
85
86 $langues = calculer_langues_utilisees();
87 ecrire_meta('langues_utilisees', $langues);
88 }
89
90
91 /**
92 * Publie une rubrique et sa hiérarchie de rubriques
93 *
94 * Fonction à appeler lorsqu'on dépublie ou supprime quelque chose
95 * dans une rubrique.
96 *
97 * @todo Le nom de la fonction est trompeur, vu que la fonction remonte dans la hierarchie !
98 *
99 * @param int $id_rubrique
100 * Identifiant de la rubrique
101 * @return bool
102 * true si le statut change effectivement
103 */
104 function publier_branche_rubrique($id_rubrique) {
105 $id_pred = $id_rubrique;
106 while (true) {
107 sql_updateq('spip_rubriques', array('statut' => 'publie', 'date' => date('Y-m-d H:i:s')),
108 "id_rubrique=" . intval($id_rubrique));
109 $id_parent = sql_getfetsel('id_parent', 'spip_rubriques AS R', "R.id_rubrique=" . intval($id_rubrique));
110 if (!$id_parent) {
111 break;
112 }
113 $id_rubrique = $id_parent;
114 }
115
116 # spip_log(" publier_branche_rubrique($id_rubrique $id_pred");
117 return $id_pred != $id_rubrique;
118 }
119
120 /**
121 * Dépublie si nécessaire des éléments d'une hiérarchie de rubriques
122 *
123 * Fonction à appeler lorsqu'on dépublie ou supprime quelque chose
124 * dans une rubrique.
125 *
126 * @uses depublier_rubrique_if()
127 * @todo Le nom de la fonction est trompeur, vu que la fonction remonte dans la hierarchie !
128 *
129 * @param int $id_rubrique
130 * Identifiant de la rubrique
131 * @return bool
132 * true si le statut change effectivement
133 */
134 function depublier_branche_rubrique_if($id_rubrique) {
135 $date = date('Y-m-d H:i:s'); // figer la date
136
137 # spip_log("depublier_branche_rubrique($id_rubrique ?");
138 $id_pred = $id_rubrique;
139 while ($id_pred) {
140
141 if (!depublier_rubrique_if($id_pred, $date)) {
142 return $id_pred != $id_rubrique;
143 }
144 // passer au parent si on a depublie
145 $r = sql_fetsel("id_parent", "spip_rubriques", "id_rubrique=" . intval($id_pred));
146 $id_pred = $r['id_parent'];
147 }
148
149 return $id_pred != $id_rubrique;
150 }
151
152 /**
153 * Dépublier une rubrique si aucun contenu publié connu n'est trouvé dedans
154 *
155 * @pipeline_appel objet_compte_enfants
156 *
157 * @param int $id_rubrique
158 * Identifiant de la rubrique à tester
159 * @param string|null $date
160 * Date pour le calcul des éléments post-datés.
161 * null = date actuelle.
162 * @return bool
163 * true si la rubrique a été dépubliée
164 */
165 function depublier_rubrique_if($id_rubrique, $date = null) {
166 if (is_null($date)) {
167 $date = date('Y-m-d H:i:s');
168 }
169 $postdates = ($GLOBALS['meta']["post_dates"] == "non") ?
170 " AND date <= " . sql_quote($date) : '';
171
172 if (!$id_rubrique = intval($id_rubrique)) {
173 return false;
174 }
175
176 // verifier qu'elle existe et est bien publiee
177 $r = sql_fetsel('id_rubrique,statut', 'spip_rubriques', "id_rubrique=" . intval($id_rubrique));
178 if (!$r or $r['statut'] !== 'publie') {
179 return false;
180 }
181
182 // On met le nombre de chaque type d'enfants dans un tableau
183 // Le type de l'objet est au pluriel
184 $compte = array(
185 'articles' => sql_countsel("spip_articles",
186 "id_rubrique=" . intval($id_rubrique) . " AND statut='publie'$postdates"),
187 'rubriques' => sql_countsel("spip_rubriques", "id_parent=" . intval($id_rubrique) . " AND statut='publie'"),
188 'documents' => sql_countsel("spip_documents_liens", "id_objet=" . intval($id_rubrique) . " AND objet='rubrique'")
189 );
190
191 // On passe le tableau des comptes dans un pipeline pour que les plugins puissent ajouter (ou retirer) des enfants
192 $compte = pipeline('objet_compte_enfants',
193 array(
194 'args' => array(
195 'objet' => 'rubrique',
196 'id_objet' => $id_rubrique,
197 'statut' => 'publie',
198 'date' => $date
199 ),
200 'data' => $compte
201 )
202 );
203
204 // S'il y a au moins un enfant de n'importe quoi, on ne dépublie pas
205 foreach ($compte as $objet => $n) {
206 if ($n) {
207 return false;
208 }
209 }
210
211 sql_updateq("spip_rubriques", array("statut" => 'prepa'), "id_rubrique=" . intval($id_rubrique));
212
213 # spip_log("depublier_rubrique $id_pred");
214 return true;
215 }
216
217
218 /**
219 * Recalcule des héritages de rubriques
220 *
221 * Recalcule le statut des rubriques, les langues héritées et la date
222 * du prochain article post-daté
223 *
224 * Cette fonction est appelée après importation: elle calcule les meta-donnes
225 * resultantes et remet de la coherence au cas où la base importée en manquait
226 *
227 * Cette fonction doit etre invoquée sans processus concurrent potentiel.
228 *
229 * @uses calculer_rubriques_publiees()
230 * @uses calculer_langues_utilisees()
231 * @uses calculer_prochain_postdate()
232 *
233 * @return void
234 **/
235 function calculer_rubriques() {
236
237 calculer_rubriques_publiees();
238
239 // Apres chaque (de)publication
240 // recalculer les langues utilisees sur le site
241 $langues = calculer_langues_utilisees();
242 ecrire_meta('langues_utilisees', $langues);
243
244 // Sauver la date de la derniere mise a jour (pour menu_rubriques)
245 ecrire_meta("date_calcul_rubriques", date("U"));
246
247 // on calcule la date du prochain article post-date
248 calculer_prochain_postdate();
249 }
250
251
252 /**
253 * Recalcule l'ensemble des données associées à l'arborescence des rubriques
254 *
255 * Attention, faute de SQL transactionnel on travaille sur
256 * des champs temporaires afin de ne pas casser la base
257 * pendant la demi seconde de recalculs
258 *
259 * @pipeline_appel calculer_rubriques
260 *
261 * @return void
262 **/
263 function calculer_rubriques_publiees() {
264
265 // Mettre les compteurs a zero
266 sql_updateq('spip_rubriques', array('date_tmp' => '0000-00-00 00:00:00', 'statut_tmp' => 'prepa'));
267
268 //
269 // Publier et dater les rubriques qui ont un article publie
270 //
271
272 // Afficher les articles post-dates ?
273 $postdates = ($GLOBALS['meta']["post_dates"] == "non") ?
274 "AND A.date <= " . sql_quote(date('Y-m-d H:i:s')) : '';
275
276 $r = sql_select(
277 "R.id_rubrique AS id, max(A.date) AS date_h",
278 "spip_rubriques AS R JOIN spip_articles AS A ON R.id_rubrique = A.id_rubrique",
279 "A.date>R.date_tmp AND A.statut='publie' $postdates ", "R.id_rubrique");
280 while ($row = sql_fetch($r)) {
281 sql_updateq("spip_rubriques", array("statut_tmp" => 'publie', "date_tmp" => $row['date_h']),
282 "id_rubrique=" . intval($row['id']));
283 }
284
285 // point d'entree pour permettre a des plugins de gerer le statut
286 // autrement (par ex: toute rubrique est publiee des sa creation)
287 // Ce pipeline fait ce qu'il veut, mais s'il touche aux statuts/dates
288 // c'est statut_tmp/date_tmp qu'il doit modifier
289 // [C'est un trigger... a renommer en trig_calculer_rubriques ?]
290 pipeline('calculer_rubriques', null);
291
292
293 // Les rubriques qui ont une rubrique fille plus recente
294 // on tourne tant que les donnees remontent vers la racine.
295 do {
296 $continuer = false;
297 $r = sql_select(
298 "R.id_rubrique AS id, max(SR.date_tmp) AS date_h",
299 "spip_rubriques AS R JOIN spip_rubriques AS SR ON R.id_rubrique = SR.id_parent",
300 "(SR.date_tmp>R.date_tmp OR R.statut_tmp<>'publie') AND SR.statut_tmp='publie' ", "R.id_rubrique");
301 while ($row = sql_fetch($r)) {
302 sql_updateq('spip_rubriques', array('statut_tmp' => 'publie', 'date_tmp' => $row['date_h']),
303 "id_rubrique=" . intval($row['id']));
304 $continuer = true;
305 }
306 } while ($continuer);
307
308 // Enregistrement des modifs
309 sql_update('spip_rubriques', array('date' => 'date_tmp', 'statut' => 'statut_tmp'));
310 }
311
312 /**
313 * Recalcule les secteurs et les profondeurs des rubriques (et articles)
314 *
315 * Cherche les rubriques ayant des id_secteur ou profondeurs ne correspondant pas
316 * avec leur parent, et les met à jour. De même avec les articles et leur id_secteur
317 * On procede en iterant la profondeur de 1 en 1 pour ne pas risquer une boucle infinie sur reference circulaire
318 *
319 * @pipeline_appel trig_propager_les_secteurs
320 *
321 * @return void
322 **/
323 function propager_les_secteurs() {
324 // Profondeur 0
325 // Toutes les rubriques racines sont de profondeur 0
326 // et fixer les id_secteur des rubriques racines
327 sql_update('spip_rubriques', array('id_secteur' => 'id_rubrique', 'profondeur' => 0), "id_parent=0");
328 // Toute rubrique non racine est de profondeur >0
329 sql_updateq('spip_rubriques', array('profondeur' => 1), "id_parent<>0 AND profondeur=0");
330
331 // securite : pas plus d'iteration que de rubriques dans la base
332 $maxiter = sql_countsel("spip_rubriques");
333
334 // reparer les rubriques qui n'ont pas l'id_secteur de leur parent
335 // on fait profondeur par profondeur
336
337 $prof = 0;
338 do {
339 $continuer = false;
340
341 // Par recursivite : si toutes les rubriques de profondeur $prof sont bonnes
342 // on fixe le profondeur $prof+1
343
344 // Toutes les rubriques dont le parent est de profondeur $prof ont une profondeur $prof+1
345 // on teste A.profondeur > $prof+1 car :
346 // - toutes les rubriques de profondeur 0 à $prof sont bonnes
347 // - si A.profondeur = $prof+1 c'est bon
348 // - cela nous protege de la boucle infinie en cas de reference circulaire dans les rubriques
349 $maxiter2 = $maxiter;
350 while ($maxiter2--
351 and $rows = sql_allfetsel(
352 "A.id_rubrique AS id, R.id_secteur AS id_secteur, R.profondeur+1 as profondeur",
353 "spip_rubriques AS A JOIN spip_rubriques AS R ON A.id_parent = R.id_rubrique",
354 "R.profondeur=" . intval($prof) . " AND (A.id_secteur <> R.id_secteur OR A.profondeur > R.profondeur+1)",
355 "", "R.id_secteur", "0,100")) {
356
357 $id_secteur = null;
358 $ids = array();
359 while ($row = array_shift($rows)) {
360 if ($row['id_secteur'] !== $id_secteur) {
361 if (count($ids)) {
362 sql_updateq("spip_rubriques", array("id_secteur" => $id_secteur, 'profondeur' => $prof + 1),
363 sql_in('id_rubrique', $ids));
364 }
365 $id_secteur = $row['id_secteur'];
366 $ids = array();
367 }
368 $ids[] = $row['id'];
369 }
370 if (count($ids)) {
371 sql_updateq("spip_rubriques", array("id_secteur" => $id_secteur, 'profondeur' => $prof + 1),
372 sql_in('id_rubrique', $ids));
373 }
374 }
375
376
377 // Toutes les rubriques de profondeur $prof+1 qui n'ont pas un parent de profondeur $prof sont decalees
378 $maxiter2 = $maxiter;
379 while ($maxiter2--
380 and $rows = sql_allfetsel(
381 "id_rubrique as id",
382 "spip_rubriques",
383 "profondeur=" . intval($prof + 1) . " AND id_parent NOT IN (" . sql_get_select("zzz.id_rubrique",
384 "spip_rubriques AS zzz", "zzz.profondeur=" . intval($prof)) . ")", '', '', '0,100')) {
385 $rows = array_map('reset', $rows);
386 sql_updateq("spip_rubriques", array('profondeur' => $prof + 2), sql_in("id_rubrique", $rows));
387 }
388
389 // ici on a fini de valider $prof+1, toutes les rubriques de prondeur 0 a $prof+1 sont OK
390 // si pas de rubrique a profondeur $prof+1 pas la peine de continuer
391 // si il reste des rubriques non vues, c'est une branche morte ou reference circulaire (base foireuse)
392 // on arrete les frais
393 if (sql_countsel("spip_rubriques", "profondeur=" . intval($prof + 1))) {
394 $prof++;
395 $continuer = true;
396 }
397 } while ($continuer and $maxiter--);
398
399 // loger si la table des rubriques semble foireuse
400 // et mettre un id_secteur=0 sur ces rubriques pour eviter toute selection par les boucles
401 if (sql_countsel("spip_rubriques", "profondeur>" . intval($prof + 1))) {
402 spip_log("Les rubriques de profondeur>" . ($prof + 1) . " semblent suspectes (branches morte ou reference circulaire dans les parents)",
403 _LOG_CRITIQUE);
404 sql_update("spip_rubriques", array('id_secteur' => 0), "profondeur>" . intval($prof + 1));
405 }
406
407 // reparer les articles
408 $r = sql_select("A.id_article AS id, R.id_secteur AS secteur", "spip_articles AS A, spip_rubriques AS R",
409 "A.id_rubrique = R.id_rubrique AND A.id_secteur <> R.id_secteur");
410
411 while ($row = sql_fetch($r)) {
412 sql_update("spip_articles", array("id_secteur" => $row['secteur']), "id_article=" . intval($row['id']));
413 }
414
415 // avertir les plugins qui peuvent faire leur mises a jour egalement
416 pipeline('trig_propager_les_secteurs', '');
417 }
418
419
420 /**
421 * Recalcule les langues héritées des sous-rubriques
422 *
423 * Cherche les langues incorrectes de sous rubriques, qui doivent hériter
424 * de la rubrique parente lorsque langue_choisie est différent de oui,
425 * et les corrige.
426 *
427 * @return bool
428 * true si un changement a eu lieu
429 **/
430 function calculer_langues_rubriques_etape() {
431 $s = sql_select("A.id_rubrique AS id_rubrique, R.lang AS lang", "spip_rubriques AS A, spip_rubriques AS R",
432 "A.id_parent = R.id_rubrique AND A.langue_choisie != 'oui' AND R.lang<>'' AND R.lang<>A.lang");
433
434 $t = false;
435 while ($row = sql_fetch($s)) {
436 $id_rubrique = $row['id_rubrique'];
437 $t = sql_updateq('spip_rubriques', array('lang' => $row['lang'], 'langue_choisie' => 'non'),
438 "id_rubrique=" . intval($id_rubrique));
439 }
440
441 return $t;
442 }
443
444 /**
445 * Recalcule les langues des rubriques et articles
446 *
447 * Redéfinit la langue du site sur les rubriques sans langue spécifiée
448 * (langue_choisie différent de 'oui')
449 *
450 * Redéfinit les langues des articles sans langue spécifiée
451 * (langue_choisie différent de 'oui') en les rebasant sur la langue
452 * de la rubrique parente lorsque ce n'est pas le cas.
453 *
454 * @uses calculer_langues_rubriques_etape()
455 * @pipeline_appel trig_calculer_langues_rubriques
456 *
457 * @return void
458 **/
459 function calculer_langues_rubriques() {
460
461 // rubriques (recursivite)
462 sql_updateq("spip_rubriques", array("lang" => $GLOBALS['meta']['langue_site'], "langue_choisie" => 'non'),
463 "id_parent=0 AND langue_choisie != 'oui'");
464 while (calculer_langues_rubriques_etape()) {
465 ;
466 }
467
468 // articles
469 $s = sql_select("A.id_article AS id_article, R.lang AS lang", "spip_articles AS A, spip_rubriques AS R",
470 "A.id_rubrique = R.id_rubrique AND A.langue_choisie != 'oui' AND (length(A.lang)=0 OR length(R.lang)>0) AND R.lang<>A.lang");
471 while ($row = sql_fetch($s)) {
472 $id_article = $row['id_article'];
473 sql_updateq('spip_articles', array("lang" => $row['lang'], 'langue_choisie' => 'non'),
474 "id_article=" . intval($id_article));
475 }
476
477 if ($GLOBALS['meta']['multi_rubriques'] == 'oui') {
478
479 $langues = calculer_langues_utilisees();
480 ecrire_meta('langues_utilisees', $langues);
481 }
482
483 // avertir les plugins qui peuvent faire leur mises a jour egalement
484 pipeline('trig_calculer_langues_rubriques', '');
485 }
486
487
488 /**
489 * Calcule la liste des langues réellement utilisées dans le site public
490 *
491 * La recherche de langue est effectuée en recréant une boucle pour chaque
492 * objet éditorial gérant des langues de sorte que les éléments non publiés
493 * ne sont pas pris en compte.
494 *
495 * @param string $serveur
496 * Nom du connecteur à la base de données
497 * @return string
498 * Liste des langues utilisées séparées par des virgules
499 **/
500 function calculer_langues_utilisees($serveur = '') {
501 include_spip('public/interfaces');
502 include_spip('public/compiler');
503 include_spip('public/composer');
504 include_spip('public/phraser_html');
505 $langues = array();
506
507 $langues[$GLOBALS['meta']['langue_site']] = 1;
508
509 include_spip('base/objets');
510 $tables = lister_tables_objets_sql();
511 $trouver_table = charger_fonction('trouver_table', 'base');
512
513 foreach (array_keys($tables) as $t) {
514 $desc = $trouver_table($t, $serveur);
515 // c'est une table avec des langues
516 if ($desc['exist']
517 and isset($desc['field']['lang'])
518 and isset($desc['field']['langue_choisie'])
519 ) {
520
521 $boucle = new Boucle();
522 $boucle->show = $desc;
523 $boucle->nom = 'calculer_langues_utilisees';
524 $boucle->id_boucle = $desc['table_objet'];
525 $boucle->id_table = $desc['table_objet'];
526 $boucle->sql_serveur = $serveur;
527 $boucle->select[] = "DISTINCT lang";
528 $boucle->from[$desc['table_objet']] = $t;
529 $boucle->separateur[] = ',';
530 $boucle->return = '$Pile[$SP][\'lang\']';
531 $boucle->iterateur = 'sql';
532
533 $boucle->descr['nom'] = 'objet_test_si_publie'; // eviter notice php
534 $boucle->descr['sourcefile'] = 'internal';
535
536 $boucle = pipeline('pre_boucle', $boucle);
537
538 if (isset($desc['statut'])
539 and $desc['statut']
540 ) {
541 $boucles = array(
542 'calculer_langues_utilisees' => $boucle,
543 );
544 // generer un nom de fonction "anonyme" unique
545 do {
546 $functionname = 'f_calculer_langues_utilisees_' . $boucle->id_table . '_' . time() . '_' . rand();
547 } while (function_exists($functionname));
548 $code = calculer_boucle('calculer_langues_utilisees', $boucles);
549 $code = '$SP=0; $command=array();$command["connect"] = $connect = "' . $serveur . '"; $Pile=array(0=>array());' . "\n" . $code;
550 $code = 'function ' . $functionname . '(){' . $code . '};$res = ' . $functionname . '();';
551 $res = '';
552 eval($code);
553 $res = explode(',', $res);
554 foreach ($res as $lang) {
555 $langues[$lang] = 1;
556 }
557 } else {
558 $res = sql_select(implode(',', $boucle->select), $boucle->from);
559 while ($row = sql_fetch($res)) {
560 $langues[$row['lang']] = 1;
561 }
562 }
563 }
564 }
565
566 $langues = array_filter(array_keys($langues));
567 sort($langues);
568 $langues = join(',', $langues);
569 spip_log("langues utilisees: $langues");
570
571 return $langues;
572 }
573
574 /**
575 * Calcule une branche de rubriques
576 *
577 * Dépréciée, pour compatibilité
578 *
579 * @deprecated
580 * @see calcul_branche_in()
581 *
582 * @param string|int|array $generation
583 * @return string
584 */
585 function calcul_branche($generation) { return calcul_branche_in($generation); }
586
587 /**
588 * Calcul d'une branche de rubrique
589 *
590 * Liste des id_rubrique contenues dans une rubrique donnée
591 *
592 * @see inc_calcul_branche_in_dist()
593 *
594 * @param string|int|array $id
595 * Identifiant de la, ou des rubriques noeuds
596 * @return string
597 * Liste des identifiants séparés par des virgules,
598 * incluant les rubriques noeuds et toutes leurs descendances
599 */
600 function calcul_branche_in($id) {
601 $calcul_branche_in = charger_fonction('calcul_branche_in', 'inc');
602
603 return $calcul_branche_in($id);
604 }
605
606 /**
607 * Calcul d'une hiérarchie
608 *
609 * Liste des id_rubrique contenant une rubrique donnée
610 *
611 * @see inc_calcul_hierarchie_in_dist()
612 * @param string|int|array $id
613 * Identifiant de la, ou des rubriques dont on veut obtenir les hierarchies
614 * @param bool $tout
615 * inclure la rubrique de depart dans la hierarchie ou non
616 * @return string
617 * Liste des identifiants séparés par des virgules,
618 * incluant les rubriques transmises et toutes leurs parentées
619 */
620 function calcul_hierarchie_in($id, $tout = true) {
621 $calcul_hierarchie_in = charger_fonction('calcul_hierarchie_in', 'inc');
622
623 return $calcul_hierarchie_in($id, $tout);
624 }
625
626
627 /**
628 * Calcul d'une branche de rubriques
629 *
630 * Liste des id_rubrique contenues dans une rubrique donnée
631 * pour le critere {branche}
632 *
633 * Fonction surchargeable pour optimisation
634 *
635 * @see inc_calcul_hierarchie_in_dist() pour la hierarchie
636 *
637 * @param string|int|array $id
638 * Identifiant de la, ou des rubriques noeuds
639 * @return string
640 * Liste des identifiants séparés par des virgules,
641 * incluant les rubriques noeuds et toutes leurs descendances
642 */
643 function inc_calcul_branche_in_dist($id) {
644 static $b = array();
645
646 // normaliser $id qui a pu arriver comme un array, comme un entier, ou comme une chaine NN,NN,NN
647 if (!is_array($id)) {
648 $id = explode(',', $id);
649 }
650 $id = join(',', array_map('intval', $id));
651 if (isset($b[$id])) {
652 return $b[$id];
653 }
654
655 // Notre branche commence par la rubrique de depart
656 $branche = $r = $id;
657
658 // On ajoute une generation (les filles de la generation precedente)
659 // jusqu'a epuisement, en se protegeant des references circulaires
660 $maxiter = 10000;
661 while ($maxiter-- and $filles = sql_allfetsel(
662 'id_rubrique',
663 'spip_rubriques',
664 sql_in('id_parent', $r) . " AND " . sql_in('id_rubrique', $r, 'NOT')
665 )) {
666 $r = join(',', array_map('reset', $filles));
667 $branche .= ',' . $r;
668 }
669
670 # securite pour ne pas plomber la conso memoire sur les sites prolifiques
671 if (strlen($branche) < 10000) {
672 $b[$id] = $branche;
673 }
674
675 return $branche;
676 }
677
678
679 /**
680 * Calcul d'une hiérarchie
681 *
682 * Liste des id_rubrique contenant une rubrique donnée,
683 * contrairement à la fonction calcul_branche_in() qui calcule les
684 * rubriques contenues
685 *
686 * @see inc_calcul_branche_in_dist() pour la descendence
687 *
688 * @param string|int|array $id
689 * Identifiant de la, ou des rubriques dont on veut obtenir les hierarchies
690 * @param bool $tout
691 * inclure la rubrique de depart dans la hierarchie ou non
692 * @return string
693 * Liste des identifiants séparés par des virgules,
694 * incluant les rubriques transmises et toutes leurs parentées
695 */
696 function inc_calcul_hierarchie_in_dist($id, $tout = true) {
697 static $b = array();
698
699 // normaliser $id qui a pu arriver comme un array, comme un entier, ou comme une chaine NN,NN,NN
700 if (!is_array($id)) {
701 $id = explode(',', $id);
702 }
703 $id = join(',', array_map('intval', $id));
704
705 if (isset($b[$id])) {
706 // Notre branche commence par la rubrique de depart si $tout=true
707 return $tout ? (strlen($b[$id]) ? $b[$id] . ",$id" : $id) : $b[$id];
708 }
709
710 $hier = "";
711
712 // On ajoute une generation (les filles de la generation precedente)
713 // jusqu'a epuisement, en se protegeant des references circulaires
714 $ids_nouveaux_parents = $id;
715 $maxiter = 10000;
716 while ($maxiter-- and $parents = sql_allfetsel(
717 'id_parent',
718 'spip_rubriques',
719 sql_in('id_rubrique', $ids_nouveaux_parents) . " AND " . sql_in('id_parent', $hier, 'NOT')
720 )) {
721 $ids_nouveaux_parents = join(',', array_map('reset', $parents));
722 $hier = $ids_nouveaux_parents . (strlen($hier) ? ',' . $hier : '');
723 }
724
725 # securite pour ne pas plomber la conso memoire sur les sites prolifiques
726 if (strlen($hier) < 10000) {
727 $b[$id] = $hier;
728 }
729
730 // Notre branche commence par la rubrique de depart si $tout=true
731 $hier = $tout ? (strlen($hier) ? "$hier,$id" : $id) : $hier;
732
733 return $hier;
734 }
735
736
737 /**
738 * Calcule la date du prochain article post-daté
739 *
740 * Appelée lorsqu'un (ou plusieurs) article post-daté arrive à terme
741 * ou est redaté
742 *
743 * @uses publier_branche_rubrique()
744 * @pipeline_appel trig_calculer_prochain_postdate
745 *
746 * @param bool $check
747 * true pour affecter le statut des rubriques concernées.
748 * @return void
749 **/
750 function calculer_prochain_postdate($check = false) {
751 include_spip('base/abstract_sql');
752 if ($check) {
753 $postdates = ($GLOBALS['meta']["post_dates"] == "non") ?
754 "AND A.date <= " . sql_quote(date('Y-m-d H:i:s')) : '';
755
756 $r = sql_select("DISTINCT A.id_rubrique AS id",
757 "spip_articles AS A LEFT JOIN spip_rubriques AS R ON A.id_rubrique=R.id_rubrique",
758 "R.statut != 'publie' AND A.statut='publie'$postdates");
759 while ($row = sql_fetch($r)) {
760 publier_branche_rubrique($row['id']);
761 }
762
763 pipeline('trig_calculer_prochain_postdate', '');
764 }
765
766 $t = sql_fetsel("date", "spip_articles", "statut='publie' AND date > " . sql_quote(date('Y-m-d H:i:s')), "", "date",
767 "1");
768
769 if ($t) {
770 $t = $t['date'];
771 if (!isset($GLOBALS['meta']['date_prochain_postdate'])
772 or $t <> $GLOBALS['meta']['date_prochain_postdate']
773 ) {
774 ecrire_meta('date_prochain_postdate', strtotime($t));
775 ecrire_meta('derniere_modif', time());
776 }
777 } else {
778 effacer_meta('date_prochain_postdate');
779 ecrire_meta('derniere_modif', time());
780 }
781
782 spip_log("prochain postdate: $t");
783 }
784
785 /**
786 * Crée une arborescence de rubrique
787 *
788 * creer_rubrique_nommee('truc/machin/chose') va créer
789 * une rubrique truc, une sous-rubrique machin, et une sous-sous-rubrique
790 * chose, sans créer de rubrique si elle existe déjà
791 * à partir de $id_parent (par défaut, à partir de la racine)
792 *
793 * NB: cette fonction est très pratique, mais pas utilisée dans le core
794 * pour rester légère elle n'appelle pas calculer_rubriques()
795 *
796 * @param string $titre
797 * Titre des rubriques, séparés par des /
798 * @param int $id_parent
799 * Identifiant de la rubrique parente
800 * @param string $serveur
801 * Nom du connecteur à la base de données
802 * @return int
803 * Identifiant de la rubrique la plus profonde.
804 */
805 function creer_rubrique_nommee($titre, $id_parent = 0, $serveur = '') {
806
807 // eclater l'arborescence demandee
808 // echapper les </multi> et autres balises fermantes html
809 $titre = preg_replace(",</([a-z][^>]*)>,ims", "<@\\1>", $titre);
810 $arbo = explode('/', preg_replace(',^/,', '', $titre));
811 include_spip('base/abstract_sql');
812 foreach ($arbo as $titre) {
813 // retablir les </multi> et autres balises fermantes html
814 $titre = preg_replace(",<@([a-z][^>]*)>,ims", "</\\1>", $titre);
815 $r = sql_getfetsel("id_rubrique", "spip_rubriques",
816 "titre = " . sql_quote($titre) . " AND id_parent=" . intval($id_parent),
817 $groupby = array(), $orderby = array(), $limit = '', $having = array(), $serveur);
818 if ($r !== null) {
819 $id_parent = $r;
820 } else {
821 $id_rubrique = sql_insertq('spip_rubriques', array(
822 'titre' => $titre,
823 'id_parent' => $id_parent,
824 'statut' => 'prepa'
825 ), $desc = array(), $serveur);
826 if ($id_parent > 0) {
827 $data = sql_fetsel("id_secteur,lang", "spip_rubriques", "id_rubrique=$id_parent",
828 $groupby = array(), $orderby = array(), $limit = '', $having = array(), $serveur);
829 $id_secteur = $data['id_secteur'];
830 $lang = $data['lang'];
831 } else {
832 $id_secteur = $id_rubrique;
833 $lang = $GLOBALS['meta']['langue_site'];
834 }
835
836 sql_updateq('spip_rubriques', array('id_secteur' => $id_secteur, "lang" => $lang),
837 "id_rubrique=" . intval($id_rubrique), $desc = '', $serveur);
838
839 // pour la recursion
840 $id_parent = $id_rubrique;
841 }
842 }
843
844 return intval($id_parent);
845 }