[SPIP] v3.2.12 -> v3.2.12 - Reinstallation avec le spip_loader
[lhc/web/www.git] / www / plugins-dist / svp / inc / svp_actionner.php
1 <?php
2
3 /**
4 * Gestion de l'actionneur : il effectue les actions sur les plugins
5 *
6 * @plugin SVP pour SPIP
7 * @license GPL
8 * @package SPIP\SVP\Actionneur
9 */
10
11 if (!defined("_ECRIRE_INC_VERSION")) {
12 return;
13 }
14
15
16 /**
17 * L'actionneur calcule l'ordre des actions, permet de les stocker
18 * dans un fichier cache et de les effectuer.
19 *
20 * @package SPIP\SVP\Actionner
21 **/
22 class Actionneur {
23
24 /**
25 * Instance du décideur
26 *
27 * @var Decideur
28 */
29 public $decideur;
30
31 /**
32 * Loguer les différents éléments
33 *
34 * Sa valeur sera initialisée par la configuration 'mode_log_verbeux' de SVP
35 *
36 * @var bool
37 */
38 public $log = false;
39
40 /**
41 * Liste des actions à faire
42 *
43 * @var array
44 * Tableau identifiant du paquet => type d'action
45 */
46 public $start = array();
47
48 /**
49 * Actions en cours d'analyse
50 *
51 * Lorsqu'on ajoute les actions à faire, elles sont réordonnées
52 * et classées dans ces quatre sous-tableaux
53 *
54 * Chaque sous-tableau est composé d'une description courte du paquet
55 * auquel est ajouté dans l'index 'todo' le type d'action à faire.
56 *
57 * @var array
58 * Index 'off' : les paquets à désactiver (ordre inverse des dépendances)
59 * Index 'lib' : les librairies à installer
60 * Index 'on' : les paquets à activer (ordre des dépendances)
61 * Index 'neutre' : autres actions dont l'ordre a peu d'importance.
62 */
63 public $middle = array(
64 'off' => array(),
65 'lib' => array(),
66 'on' => array(),
67 'neutre' => array(),
68 );
69
70 // actions à la fin (apres analyse, et dans l'ordre)
71
72 /**
73 * Liste des actions à faire
74 *
75 * Liste de description courtes des paquets + index 'todo' indiquant l'action
76 *
77 * @var array
78 */
79 public $end = array();
80
81 /**
82 * Liste des actions faites
83 * Liste de description courtes des paquets + index 'todo' indiquant l'action
84 *
85 * @var array
86 */
87 public $done = array(); // faites
88
89 /**
90 * Actions en cours
91 * Description courte du paquet + index 'todo' indiquant l'action
92 *
93 * @var array
94 */
95 public $work = array();
96
97 /**
98 * Liste des erreurs
99 *
100 * @var array Liste des erreurs
101 */
102 public $err = array();
103
104 /**
105 * Verrou.
106 * Le verrou est posé au moment de passer à l'action.
107 *
108 * @var array
109 * Index 'id_auteur' : Identifiant de l'auteur ayant déclenché des actions
110 * Indix 'time' : timestamp de l'heure de déclenchement de l'action */
111 public $lock = array('id_auteur' => 0, 'time' => '');
112
113 /**
114 * SVP (ce plugin) est-il à désactiver dans une des actions ?
115 *
116 * Dans ce cas, on tente de le désactiver après d'autres plugins à désactiver
117 * sinon l'ensemble des actions suivantes échoueraient.
118 *
119 * @var bool
120 * false si SVP n'est pas à désactiver, true sinon */
121 public $svp_off = false;
122
123 /**
124 * Constructeur
125 *
126 * Détermine si les logs sont activés et instancie un décideur.
127 */
128 public function __construct() {
129 include_spip('inc/config');
130 $this->log = (lire_config('svp/mode_log_verbeux') == 'oui');
131
132 include_spip('inc/svp_decider');
133 $this->decideur = new Decideur();
134 #$this->decideur->start();
135
136 // pour denormaliser_version()
137 include_spip('svp_fonctions');
138 }
139
140
141 /**
142 * Ajoute un log
143 *
144 * Ajoute un log si la propriété $log l'autorise;
145 *
146 * @param mixed $quoi
147 * La chose à logguer (souvent un texte)
148 **/
149 public function log($quoi) {
150 if ($this->log) {
151 spip_log($quoi, 'actionneur');
152 }
153 }
154
155 /**
156 * Ajoute une erreur
157 *
158 * Ajoute une erreur à la liste des erreurs présentées au moment
159 * de traiter les actions.
160 *
161 * @param string $erreur
162 * Le texte de l'erreur
163 **/
164 public function err($erreur) {
165 if ($erreur) {
166 $this->err[] = $erreur;
167 }
168 }
169
170 /**
171 * Remet à zéro les tableaux d'actions
172 */
173 public function clear() {
174 $this->middle = array(
175 'off' => array(),
176 'lib' => array(),
177 'on' => array(),
178 'neutre' => array(),
179 );
180 $this->end = array();
181 $this->done = array();
182 $this->work = array();
183 }
184
185 /**
186 * Ajoute les actions à faire dans l'actionneur
187 *
188 * @param array $todo
189 * Tableau des actions à faire (identifiant de paquet => type d'action)
190 **/
191 public function ajouter_actions($todo) {
192 if ($todo) {
193 foreach ($todo as $id => $action) {
194 $this->start[$id] = $action;
195 }
196 }
197 $this->ordonner_actions();
198 }
199
200
201 /**
202 * Ajoute une librairie à installer
203 *
204 * Ajoute l'action de télécharger une librairie, si la libraire
205 * n'est pas déjà présente et si le répertoire de librairie est
206 * écrivable.
207 *
208 * @param string $nom Nom de la librairie
209 * @param string $source URL pour obtenir la librairie
210 * @return bool
211 */
212 public function add_lib($nom, $source) {
213 if (!$this->decideur->est_presente_lib($nom)) {
214 if (is_writable(_DIR_LIB)) {
215 $this->middle['lib'][$nom] = array(
216 'todo' => 'getlib',
217 'n' => $nom,
218 'p' => $nom,
219 'v' => $source,
220 's' => $source,
221 );
222 } else {
223 // erreur : impossible d'ecrire dans _DIR_LIB !
224 // TODO : message et retour d'erreur a gerer...
225 return false;
226 }
227 }
228
229 return true;
230 }
231
232 /**
233 * Ordonne les actions demandées
234 *
235 * La fonction définie quelles sont les actions graduellement réalisables.
236 * Si un plugin A dépend de B qui dépend de C
237 * - pour tout ce qui est à installer : ordre des dependances (d'abord C, puis B, puis A)
238 * - pour tout ce qui est à désinstaller : ordre inverse des dependances. (d'abord A, puis B, puis C)
239 *
240 * On commence donc par séparer
241 * - ce qui est à désinstaller,
242 * - ce qui est à installer,
243 * - les actions neutres (get, up sur non actif, kill)
244 *
245 * Dans les traitements, on commencera par faire
246 * - ce qui est à désinstaller (il est possible que certains plugins
247 * nécessitent la désinstallation d'autres présents - tel que : 1 seul
248 * service d'envoi de mail)
249 * - puis ce qui est a installer (à commencer par les librairies, puis paquets),
250 * - puis les actions neutres
251 */
252 public function ordonner_actions() {
253 $this->log("Ordonner les actions à réaliser");
254 // nettoyer le terrain
255 $this->clear();
256
257 // récupérer les descriptions de chaque paquet
258 $this->log("> Récupérer les descriptions des paquets concernés");
259 $infos = array();
260 foreach ($this->start as $id => $action) {
261 // seulement les identifiants de paquets (pas les librairies)
262 if (is_int($id)) {
263 $info = $this->decideur->infos_courtes_id($id);
264 $infos[$id] = $info['i'][$id];
265 }
266 }
267
268 // Calculer les dépendances (nécessite) profondes pour chaque paquet,
269 // si les plugins en questions sont parmis ceux actionnés
270 // (ie A dépend de B qui dépend de C => a dépend de B et C).
271 $infos = $this->calculer_necessites_complets($infos);
272
273 foreach ($this->start as $id => $action) {
274 // infos du paquet. Ne s'applique pas sur librairie ($id = md5)
275 $i = is_int($id) ? $infos[$id] : array();
276
277 switch ($action) {
278 case 'getlib':
279 // le plugin en ayant besoin le fera
280 // comme un grand...
281 break;
282 case 'geton':
283 case 'on':
284 $this->on($i, $action);
285 break;
286 case 'up':
287 // si le plugin est actif
288 if ($i['a'] == 'oui') {
289 $this->on($i, $action);
290 } else {
291 $this->neutre($i, $action);
292 }
293 break;
294 case 'upon':
295 $this->on($i, $action);
296 break;
297 case 'off':
298 case 'stop':
299 $this->off($i, $action);
300 break;
301 case 'get':
302 case 'kill':
303 $this->neutre($i, $action);
304 break;
305 }
306 }
307
308 // c'est termine, on passe tout dans la fin...
309 foreach ($this->middle as $acts) {
310 $this->end = array_merge($this->end, $acts);
311 }
312
313 // si on a vu une desactivation de SVP
314 // on le met comme derniere action...
315 // sinon on ne pourrait pas faire les suivantes !
316 if ($this->svp_off) {
317 $this->log("SVP a desactiver a la fin.");
318 foreach ($this->end as $c => $info) {
319 if ($info['p'] == 'SVP') {
320 unset($this->end[$c]);
321 $this->end[] = $info;
322 break;
323 }
324 }
325 }
326
327 $this->log("------------");
328 #$this->log("Fin du tri :");
329 #$this->log($this->end);
330 }
331
332
333 /**
334 * Complète les infos des paquets actionnés pour qu'ils contiennent
335 * en plus de leurs 'necessite' directs, tous les nécessite des
336 * plugins dont ils dépendent, si ceux ci sont aussi actionnés.
337 *
338 * Ie: si A indique dépendre de B, et B de C, la clé
339 * 'dp' (dépendances prefixes). indiquera les préfixes
340 * des plugins B et C
341 *
342 * On ne s'occupe pas des versions de compatibilité ici
343 *
344 * @param array $infos (identifiant => description courte du plugin)
345 * @return array $infos
346 **/
347 public function calculer_necessites_complets($infos) {
348 $this->log("> Calculer les dépendances nécessités sur paquets concernés");
349
350 // prefixe => array(prefixes)
351 $necessites = array();
352
353 // 1) déjà les préfixes directement nécessités
354 foreach ($infos as $i => $info) {
355 if (!empty($info['dn'])) {
356 # à remplacer par array_column($info['dn'], 'nom') un jour
357 $necessites[$info['p']] = array();
358 foreach ($info['dn'] as $n) {
359 $necessites[$info['p']][] = $n['nom'];
360 }
361 }
362 // préparer la clé dp (dépendances préfixes) et 'dmp' (dépendent de moi) vide
363 $infos[$i]['dp'] = array();
364 $infos[$i]['dmp'] = array();
365 }
366
367 if ($nb = count($necessites)) {
368 $this->log(">- $nb plugins ont des nécessités");
369 // 2) ensuite leurs dépendances, récursivement
370 $necessites = $this->calculer_necessites_complets_rec($necessites);
371
372 // 3) intégrer le résultat
373 foreach ($infos as $i => $info) {
374 if (!empty($necessites[$info['p']])) {
375 $infos[$i]['dp'] = $necessites[$info['p']];
376 }
377 }
378
379 // 4) calculer une clé 'dmp' : liste des paquets actionnés qui dépendent de moi
380 foreach ($infos as $i => $info) {
381 $dmp = array();
382 foreach ($necessites as $prefixe => $liste) {
383 if (in_array($info['p'], $liste)) {
384 $dmp[] = $prefixe;
385 }
386 }
387 $infos[$i]['dmp'] = $dmp;
388 }
389 }
390
391 # $this->log($infos);
392
393 return $infos;
394 }
395
396 /**
397 * Fonction récursive pour calculer la liste de tous les préfixes
398 * de plugins nécessités par un autre.
399 *
400 * Avec une liste fermée connue d'avance des possibilités de plugins
401 * (ceux qui seront actionnés)
402 *
403 * @param array prefixe => liste de prefixe dont il dépend
404 * @param bool $profondeur
405 * @return array prefixe => liste de prefixe dont il dépend
406 **/
407 public function calculer_necessites_complets_rec($necessites, $profondeur = 0) {
408 $changement = false;
409 foreach ($necessites as $prefixe => $liste) {
410 $n = count($liste);
411 foreach ($liste as $prefixe_necessite) {
412 // si un des plugins dépendants fait partie des plugins actionnés,
413 // il faut aussi lui ajouter ses dépendances…
414 if (isset($necessites[$prefixe_necessite])) {
415 $liste = array_unique(array_merge($liste, $necessites[$prefixe_necessite]));
416 }
417 }
418 $necessites[$prefixe] = $liste;
419 if ($n !== count($liste)) {
420 $changement = true;
421 }
422 }
423
424 // limiter à 10 les successions de dépendances !
425 if ($changement and $profondeur <= 10) {
426 $necessites = $this->calculer_necessites_complets_rec($necessites, $profondeur++);
427 }
428
429 if ($changement and $profondeur > 10) {
430 $this->log("! Problème de calcul de dépendances complètes : récursion probable. On stoppe.");
431 }
432
433 return $necessites;
434 }
435
436 /**
437 * Ajoute un paquet à activer
438 *
439 * À chaque fois qu'un nouveau paquet arrive ici, on le compare
440 * avec ceux déjà présents pour savoir si on doit le traiter avant
441 * ou après un des paquets à activer déjà présent.
442 *
443 * Si le paquet est une dépendance d'un autre plugin, il faut le mettre
444 * avant (pour l'activer avant celui qui en dépend).
445 *
446 * Si le paquet demande une librairie, celle-ci est ajoutée (les
447 * librairies seront téléchargées avant l'activation des plugins,
448 * le plugin aura donc sa librairie lorsqu'il sera activé)
449 *
450 *
451 * @param array $info
452 * Description du paquet
453 * @param string $action
454 * Action à réaliser (on, upon)
455 * @return void
456 **/
457 public function on($info, $action) {
458 $info['todo'] = $action;
459 $p = $info['p'];
460 $this->log("ON: $p $action");
461
462 // si dependance, il faut le mettre avant !
463 $in = $out = $prov = $deps = $deps_all = array();
464 // raz des cles pour avoir les memes que $out (utile reellement ?)
465 $this->middle['on'] = array_values($this->middle['on']);
466 // ajout des dependances
467 $in = $info['dp'];
468 // $info fourni ses procure
469 if (isset($info['procure']) and $info['procure']) {
470 $prov = array_keys($info['procure']);
471 }
472 // et se fournit lui meme evidemment
473 array_unshift($prov, $p);
474
475 // ajout des librairies
476 foreach ($info['dl'] as $lib) {
477 // il faudrait gerer un retour d'erreur eventuel !
478 $this->add_lib($lib['nom'], $lib['lien']);
479 }
480
481 // on recupere : tous les prefix de plugin a activer (out)
482 // ie. ce plugin peut dependre d'un de ceux la
483 //
484 // ainsi que les dependences de ces plugins (deps)
485 // ie. ces plugins peuvent dependre de ce nouveau a activer.
486 foreach ($this->middle['on'] as $k => $inf) {
487 $out["$k:0"] = $inf['p'];
488 if (isset($inf['procure']) and $inf['procure']) {
489 $i = 1;
490 foreach ($inf['procure'] as $procure => $v) {
491 $out["$k:$i"] = $inf['p'];
492 $i++;
493 }
494 }
495
496 $deps[$inf['p']] = $inf['dp'];
497 $deps_all = array_merge($deps_all, $inf['dp']);
498 }
499
500
501 if (!$in) {
502
503 // pas de dependance, on le met en premier !
504 $this->log("- placer $p tout en haut");
505 array_unshift($this->middle['on'], $info);
506
507 } else {
508
509 // intersection = dependance presente aussi
510 // on place notre action juste apres la derniere dependance
511 if ($diff = array_intersect($in, $out)) {
512 $key = array();
513 foreach ($diff as $d) {
514 $k = array_search($d, $out);
515 $k = explode(":", $k);
516 $key[] = reset($k);
517 }
518 $key = max($key);
519 $this->log("- placer $p apres " . $this->middle['on'][$key]['p']);
520 if ($key == count($this->middle['on'])) {
521 $this->middle['on'][] = $info;
522 } else {
523 array_splice($this->middle['on'], $key + 1, 0, array($info));
524 }
525
526 // intersection = plugin dependant de celui-ci
527 // on place notre plugin juste avant la premiere dependance a lui trouvee
528 } elseif (array_intersect($prov, $deps_all)) {
529 foreach ($deps as $prefix => $dep) {
530 if ($diff = array_intersect($prov, $deps_all)) {
531 foreach ($diff as $d) {
532 $k = array_search($d, $out);
533 $k = explode(":", $k);
534 $key[] = reset($k);
535 }
536 $key = min($key);
537 $this->log("- placer $p avant $prefix qui en depend ($key)");
538 if ($key == 0) {
539 array_unshift($this->middle['on'], $info);
540 } else {
541 array_splice($this->middle['on'], $key, 0, array($info));
542 }
543 break;
544 }
545 }
546
547 // rien de particulier, il a des dependances mais les plugins
548 // ne sont pas encore la ou les dependances sont deja actives
549 // donc on le place tout en bas
550 } else {
551 $this->log("- placer $p tout en bas");
552 $this->middle['on'][] = $info;
553 }
554 }
555 unset($diff, $in, $out);
556 }
557
558
559 /**
560 * Ajoute un paquet avec une action neutre
561 *
562 * Ces actions seront traitées en dernier, et peu importe leur
563 * ordre car elles n'entrent pas en conflit avec des dépendances.
564 *
565 * @param array $info
566 * Description du paquet
567 * @param string $action
568 * Action à réaliser (kill, get, up (sur plugin inactif))
569 * @return void
570 **/
571 public function neutre($info, $action) {
572 $info['todo'] = $action;
573 $this->log("NEUTRE: $info[p] $action");
574 $this->middle['neutre'][] = $info;
575 }
576
577 /**
578 * Ajoute un paquet à désactiver
579 *
580 * Ces actions seront traitées en premier.
581 *
582 * À chaque fois qu'un nouveau paquet arrive ici, on le compare
583 * avec ceux déjà présents pour savoir si on doit le traiter avant
584 * ou après un des paquets à désactiver déjà présent.
585 *
586 * Si le paquet est une dépendance d'un autre plugin, il faut le mettre
587 * après (pour désactiver avant celui qui en dépend).
588 *
589 * @param array $info
590 * Description du paquet
591 * @param string $action
592 * Action à réaliser (kill, get, up (sur plugin inactif))
593 * @return void
594 **/
595 public function off($info, $action) {
596 $info['todo'] = $action;
597 $p = $info['p'];
598 $this->log("OFF: $p $action");
599
600 // signaler la desactivation de SVP
601 if ($p == 'SVP') {
602 $this->svp_off = true;
603 }
604
605 // si dependance, il faut le mettre avant !
606 $in = $out = array();
607 // raz des cles pour avoir les memes que $out (utile reellement ?)
608 $this->middle['off'] = array_values($this->middle['off']);
609 // in : si un plugin en dépend, il faudra désactiver celui là avant.
610 $in = $info['dp'];
611
612 foreach ($this->middle['off'] as $inf) {
613 $out[] = $inf['p'];
614 }
615
616 if (!$info['dn']) {
617 // ce plugin n'a pas de dependance, on le met en dernier !
618 $this->log("- placer $p tout en bas");
619 $this->middle['off'][] = $info;
620 } else {
621 // ce plugin a des dependances,
622 // on le desactive juste avant elles.
623
624 // intersection = dependance presente aussi
625 // on place notre action juste avant la premiere dependance
626 if ($diff = array_intersect($in, $out)) {
627 $key = array();
628 foreach ($diff as $d) {
629 $key[] = array_search($d, $out);
630 }
631 $key = min($key);
632 $this->log("- placer $p avant " . $this->middle['off'][$key]['p']);
633 array_splice($this->middle['off'], $key, 0, array($info));
634 // inversement des plugins dépendants de ce plugin sont présents…
635 // on le met juste après le dernier
636 } elseif ($diff = array_intersect($info['dmp'], $out)) {
637 $key = array();
638 foreach ($diff as $d) {
639 $key[] = array_search($d, $out);
640 }
641 $key = max($key);
642 $this->log("- placer $p apres " . $this->middle['off'][$key]['p']);
643 if ($key == count($this->middle['off'])) {
644 $this->middle['off'][] = $info;
645 } else {
646 array_splice($this->middle['off'], $key + 1, 0, array($info));
647 }
648 } else {
649 // aucune des dependances n'est a desactiver
650 // (du moins à ce tour ci),
651 // on le met en premier !
652 $this->log("- placer $p tout en haut");
653 array_unshift($this->middle['off'], $info);
654 }
655 }
656 unset($diff, $in, $out);
657 }
658
659 /**
660 * Retourne le texte qui présente la dernière action qui vient d'être réalisée
661 *
662 * @return string
663 **/
664 public function presenter_derniere_action() {
665 $i = end($this->done);
666 reset($this->done);
667 $texte = '';
668 if ($i) {
669 $ok = ($i['done'] ? true : false);
670 $oks = &$ok;
671 $ok_texte = $ok ? 'ok' : 'fail';
672 $cle_t = 'svp:message_action_finale_' . $i['todo'] . '_' . $ok_texte;
673 $trads = array(
674 'plugin' => $i['n'],
675 'version' => denormaliser_version($i['v']),
676 );
677 if (isset($i['maj'])) {
678 $trads['version_maj'] = denormaliser_version($i['maj']);
679 }
680 $texte = _T($cle_t, $trads);
681 if (is_string($i['done'])) {
682 $texte .= " <span class='$ok_texte'>$i[done]</span>";
683 }
684 }
685 return $texte;
686 }
687
688 /**
689 * Retourne un bilan, texte HTML, des actions qui ont été faites
690 *
691 * Si c'est un affichage du bilan de fin, et qu'il reste des actions
692 * à faire, un lien est proposé pour faire supprimer ces actions restantes
693 * et le verrou qui va avec.
694 *
695 * @param bool $fin
696 * Est-ce un affichage intermédiaire (false) ou le tout dernier (true).
697 * @return string
698 * Bilan des actions au format HTML
699 **/
700 public function presenter_actions($fin = false) {
701 $affiche = "";
702
703 include_spip('inc/filtres_boites');
704
705 if (count($this->err)) {
706 $erreurs = "<ul>";
707 foreach ($this->err as $i) {
708 $erreurs .= "\t<li class='erreur'>" . $i . "</li>\n";
709 }
710 $erreurs .= "</ul>";
711 $affiche .= boite_ouvrir(_T('svp:actions_en_erreur'), 'error') . $erreurs . boite_fermer();
712 }
713
714 if (count($this->done)) {
715 $oks = true;
716 $done = "<ul>";
717 foreach ($this->done as $i) {
718 $ok = ($i['done'] ? true : false);
719 $oks = &$ok;
720 $ok_texte = $ok ? 'ok' : 'fail';
721 $cle_t = 'svp:message_action_finale_' . $i['todo'] . '_' . $ok_texte;
722 $trads = array(
723 'plugin' => $i['n'],
724 'version' => denormaliser_version($i['v']),
725 );
726 if (isset($i['maj'])) {
727 $trads['version_maj'] = denormaliser_version($i['maj']);
728 }
729 $texte = _T($cle_t, $trads);
730 if (is_string($i['done'])) {
731 $texte .= " <span class='$ok_texte'>$i[done]</span>";
732 }
733 // si le plugin a ete active dans le meme lot, on remplace le message 'active' par le message 'installe'
734 if ($i['todo'] == 'install' and $ok_texte == 'ok') {
735 $cle_t = 'svp:message_action_finale_' . 'on' . '_' . $ok_texte;
736 $texte_on = _T($cle_t, array(
737 'plugin' => $i['n'],
738 'version' => denormaliser_version($i['v']),
739 'version_maj' => denormaliser_version($i['maj'])
740 ));
741 if (strpos($done, $texte_on) !== false) {
742 $done = str_replace($texte_on, $texte, $done);
743 $texte = "";
744 }
745 }
746 if ($texte) {
747 $done .= "\t<li class='$ok_texte'>$texte</li>\n";
748 }
749 }
750 $done .= "</ul>";
751 $affiche .= boite_ouvrir(_T('svp:actions_realises'), ($oks ? 'success' : 'notice')) . $done . boite_fermer();
752 }
753
754 if (count($this->end)) {
755 $todo = "<ul>";
756 foreach ($this->end as $i) {
757 $todo .= "\t<li>" . _T('svp:message_action_' . $i['todo'], array(
758 'plugin' => $i['n'],
759 'version' => denormaliser_version($i['v']),
760 'version_maj' => denormaliser_version($i['maj'])
761 )) . "</li>\n";
762 }
763 $todo .= "</ul>\n";
764 $titre = ($fin ? _T('svp:actions_non_traitees') : _T('svp:actions_a_faire'));
765
766 // s'il reste des actions à faire alors que c'est la fin qui est affichée,
767 // on met un lien pour vider. C'est un cas anormal qui peut surgir :
768 // - en cas d'erreur sur une des actions bloquant l'espace privé
769 // - en cas d'appel d'admin_plugins concurrent par le même admin ou 2 admins...
770 if ($fin) {
771 include_spip('inc/filtres');
772 if ($this->lock['time']) {
773 $time = $this->lock['time'];
774 } else {
775 $time = time();
776 }
777 $date = date('Y-m-d H:i:s', $time);
778 $todo .= "<br />\n";
779 $todo .= "<p class='error'>" . _T('svp:erreur_actions_non_traitees', array(
780 'auteur' => sql_getfetsel('nom', 'spip_auteurs', 'id_auteur=' . sql_quote($this->lock['id_auteur'])),
781 'date' => affdate_heure($date)
782 )) . "</p>\n";
783 $todo .= "<a href='" . parametre_url(self(), 'nettoyer_actions',
784 '1') . "'>" . _T('svp:nettoyer_actions') . "</a>\n";
785 }
786 $affiche .= boite_ouvrir($titre, 'notice') . $todo . boite_fermer();
787 }
788
789 if ($affiche) {
790 include_spip('inc/filtres');
791 $affiche = wrap($affiche, "<div class='svp_retour'>");
792 }
793
794 return $affiche;
795 }
796
797 /**
798 * Retourne le pourcentage de progression des actions
799 * @return int|float
800 */
801 public function progression() {
802 if (count($this->done)) {
803 return count($this->done) / (count($this->done) + count($this->end));
804 }
805 return 0;
806 }
807
808 /**
809 * Teste l'existance d'un verrou par un auteur ?
810 *
811 * Si un id_auteur est transmis, teste que c'est cet auteur
812 * précis qui a posé le verrou.
813 *
814 * @see Actionneur::verrouiller()
815 *
816 * @param int|string $id_auteur
817 * Identifiant de l'auteur, ou vide
818 * @return bool
819 * true si un verrou est là, false sinon
820 **/
821 public function est_verrouille($id_auteur = '') {
822 if ($id_auteur == '') {
823 return ($this->lock['id_auteur'] ? true : false);
824 }
825
826 return ($this->lock['id_auteur'] == $id_auteur);
827 }
828
829 /**
830 * Pose un verrou
831 *
832 * Un verrou permet de garentir qu'une seule exécution d'actions
833 * est lancé à la fois, ce qui évite que deux administrateurs
834 * puissent demander en même temps des actions qui pourraient
835 * s'entrechoquer.
836 *
837 * Le verrou est signé par l'id_auteur de l'auteur actuellement identifié.
838 *
839 * Le verrou sera sauvegardé en fichier avec la liste des actions
840 *
841 * @see Actionneur::sauver_actions()
842 **/
843 public function verrouiller() {
844 $this->lock = array(
845 'id_auteur' => $GLOBALS['visiteur_session']['id_auteur'],
846 'time' => time(),
847 );
848 }
849
850 /**
851 * Enlève le verrou
852 **/
853 public function deverrouiller() {
854 $this->lock = array(
855 'id_auteur' => 0,
856 'time' => '',
857 );
858 }
859
860 /**
861 * Sauvegarde en fichier cache la liste des actions et le verrou
862 *
863 * Crée un tableau contenant les informations principales qui permettront
864 * de retrouver ce qui est à faire comme action, ce qui a été fait,
865 * les erreurs générées, et le verrouillage.
866 *
867 * Le cache peut être lu avec la méthode get_actions()
868 *
869 * @see Actionneur::get_actions()
870 **/
871 public function sauver_actions() {
872 $contenu = serialize(array(
873 'todo' => $this->end,
874 'done' => $this->done,
875 'work' => $this->work,
876 'err' => $this->err,
877 'lock' => $this->lock,
878 ));
879 ecrire_fichier(_DIR_TMP . 'stp_actions.txt', $contenu);
880 }
881
882 /**
883 * Lit le fichier cache de la liste des actions et verrou
884 *
885 * Restaure les informations contenues dans le fichier de cache
886 * et écrites avec la méthode sauver_actions().
887 *
888 * @see Actionneur::sauver_actions()
889 **/
890 public function get_actions() {
891 if (
892 lire_fichier(_DIR_TMP . 'stp_actions.txt', $contenu)
893 and $contenu
894 and $infos = unserialize($contenu)
895 and is_array($infos)
896 ) {
897 $this->end = $infos['todo'];
898 $this->work = $infos['work'];
899 $this->done = $infos['done'];
900 $this->err = $infos['err'];
901 $this->lock = $infos['lock'];
902 }
903 }
904
905 /**
906 * Nettoyage des actions et verrou
907 *
908 * Remet tout à zéro pour pouvoir repartir d'un bon pied.
909 **/
910 public function nettoyer_actions() {
911 $this->end = array();
912 $this->done = array();
913 $this->work = array();
914 $this->err = array();
915 $this->deverrouiller();
916 $this->sauver_actions();
917 }
918
919 /**
920 * Effectue une des actions qui reste à faire.
921 *
922 * Dépile une des actions à faire s'il n'y en a pas en cours
923 * au moment de l'appel et traite cette action
924 *
925 * @see Actionneur::do_action()
926 * @return bool|array
927 * False si aucune action à faire,
928 * sinon tableau de description courte du paquet + index 'todo' indiquant l'action
929 **/
930 public function one_action() {
931 // s'il reste des actions, on en prend une, et on la fait
932 // de meme si une action est en cours mais pas terminee (timeout)
933 // on tente de la refaire...
934 if (count($this->end) or $this->work) {
935 // on verrouille avec l'auteur en cours pour
936 // que seul lui puisse effectuer des actions a ce moment la
937 if (!$this->est_verrouille()) {
938 $this->verrouiller();
939 }
940 // si ce n'est pas verrouille par l'auteur en cours...
941 // ce n'est pas normal, donc on quitte sans rien faire.
942 elseif (!$this->est_verrouille($GLOBALS['visiteur_session']['id_auteur'])) {
943 return false;
944 }
945
946 // si pas d'action en cours
947 if (!$this->work) {
948 // on prend une des actions en attente
949 $this->work = array_shift($this->end);
950 }
951 $action = $this->work;
952 $this->sauver_actions();
953 // effectue l'action dans work
954 $this->do_action();
955
956 // si la liste des actions en attente est maintenant vide
957 // on deverrouille aussitot.
958 if (!count($this->end)) {
959 $this->deverrouiller();
960 $this->sauver_actions();
961 }
962
963 return $action;
964 } else {
965 // on ne devrait normalement plus tomber sur un cas de verrouillage ici
966 // mais sait-on jamais. Tester ne couter rien :)
967 if ($this->est_verrouille()) {
968 $this->deverrouiller();
969 $this->sauver_actions();
970 }
971 }
972
973 return false;
974 }
975
976 /**
977 * Effectue l'action en attente.
978 *
979 * Appelle une methode do_{todo} de l'Actionneur où todo
980 * est le type d'action à faire.
981 *
982 * Place dans la clé 'done' de description courte du paquet
983 * le résultat de l'action (un booléen indiquant si elle s'est bien
984 * déroulée).
985 **/
986 public function do_action() {
987 if ($do = $this->work) {
988 $todo = 'do_' . $do['todo'];
989 lire_metas(); // avoir les metas a jour
990 $this->log("Faire $todo avec $do[n]");
991 $do['done'] = $this->$todo($do);
992 $this->done[] = $do;
993 $this->work = array();
994 $this->sauver_actions();
995 }
996 }
997
998
999 /**
1000 * Attraper et activer un paquet
1001 *
1002 * @param array $info
1003 * Description courte du paquet
1004 * @return bool
1005 * false si erreur, true sinon.
1006 */
1007 public function do_geton($info) {
1008 if (!$this->tester_repertoire_plugins_auto()) {
1009 return false;
1010 }
1011 $i = sql_fetsel('*', 'spip_paquets', 'id_paquet=' . sql_quote($info['i']));
1012 if ($dirs = $this->get_paquet_id($i)) {
1013 $this->activer_plugin_dossier($dirs['dossier'], $i);
1014
1015 return true;
1016 }
1017
1018 $this->log("GetOn : Erreur de chargement du paquet " . $info['n']);
1019
1020 return false;
1021 }
1022
1023 /**
1024 * Activer un paquet
1025 *
1026 * Soit il est là... soit il est à télécharger...
1027 *
1028 * @param array $info
1029 * Description courte du paquet
1030 * @return bool
1031 * false si erreur, true sinon.
1032 */
1033 public function do_on($info) {
1034 $i = sql_fetsel('*', 'spip_paquets', 'id_paquet=' . sql_quote($info['i']));
1035 // à télécharger ?
1036 if (isset($i['id_zone']) and $i['id_zone'] > 0) {
1037 return $this->do_geton($info);
1038 }
1039
1040 // a activer uniquement
1041 // il faudra prendre en compte les autres _DIR_xx
1042 if (in_array($i['constante'], array('_DIR_PLUGINS', '_DIR_PLUGINS_SUPPL'))) {
1043 $dossier = rtrim($i['src_archive'], '/');
1044 $this->activer_plugin_dossier($dossier, $i, $i['constante']);
1045
1046 return true;
1047 }
1048
1049 return false;
1050 }
1051
1052
1053 /**
1054 * Mettre à jour un paquet
1055 *
1056 * @param array $info
1057 * Description courte du paquet
1058 * @return bool|array
1059 * false si erreur,
1060 * description courte du nouveau plugin sinon.
1061 */
1062 public function do_up($info) {
1063 // ecriture du nouveau
1064 // suppression de l'ancien (si dans auto, et pas au meme endroit)
1065 // OU suppression des anciens fichiers
1066 if (!$this->tester_repertoire_plugins_auto()) {
1067 return false;
1068 }
1069
1070 // $i est le paquet a mettre à jour (donc present)
1071 // $maj est le paquet a telecharger qui est a jour (donc distant)
1072
1073 $i = sql_fetsel('*', 'spip_paquets', 'id_paquet=' . sql_quote($info['i']));
1074
1075 // on cherche la mise a jour...
1076 // c'est a dire le paquet source que l'on met a jour.
1077 if ($maj = sql_fetsel('pa.*',
1078 array('spip_paquets AS pa', 'spip_plugins AS pl'),
1079 array(
1080 'pl.prefixe=' . sql_quote($info['p']),
1081 'pa.version=' . sql_quote($info['maj']),
1082 'pa.id_plugin = pl.id_plugin',
1083 'pa.id_depot>' . sql_quote(0)
1084 ),
1085 '', 'pa.etatnum DESC', '0,1')
1086 ) {
1087
1088 // si dans auto, on autorise à mettre à jour depuis auto pour les VCS
1089 $dir_actuel_dans_auto = '';
1090 if (substr($i['src_archive'], 0, 5) == 'auto/') {
1091 $dir_actuel_dans_auto = substr($i['src_archive'], 5);
1092 }
1093
1094 if ($dirs = $this->get_paquet_id($maj, $dir_actuel_dans_auto)) {
1095 // Si le plugin a jour n'est pas dans le meme dossier que l'ancien...
1096 // il faut :
1097 // - activer le plugin sur son nouvel emplacement (uniquement si l'ancien est actif)...
1098 // - supprimer l'ancien (si faisable)
1099 if (($dirs['dossier'] . '/') != $i['src_archive']) {
1100 if ($i['actif'] == 'oui') {
1101 $this->activer_plugin_dossier($dirs['dossier'], $maj);
1102 }
1103
1104 // l'ancien repertoire a supprimer pouvait etre auto/X
1105 // alors que le nouveau est auto/X/Y ...
1106 // il faut prendre en compte ce cas particulier et ne pas ecraser auto/X !
1107 if (substr($i['src_archive'], 0, 5) == 'auto/' and (false === strpos($dirs['dossier'], $i['src_archive']))) {
1108 if (supprimer_repertoire(constant($i['constante']) . $i['src_archive'])) {
1109 sql_delete('spip_paquets', 'id_paquet=' . sql_quote($info['i']));
1110 }
1111 }
1112 }
1113
1114 $this->ajouter_plugin_interessants_meta($dirs['dossier']);
1115
1116 return $dirs;
1117 }
1118 }
1119
1120 return false;
1121 }
1122
1123
1124 /**
1125 * Mettre à jour et activer un paquet
1126 *
1127 * @param array $info
1128 * Description courte du paquet
1129 * @return bool
1130 * false si erreur, true sinon
1131 */
1132 public function do_upon($info) {
1133 $i = sql_fetsel('*', 'spip_paquets', 'id_paquet=' . sql_quote($info['i']));
1134 if ($dirs = $this->do_up($info)) {
1135 $this->activer_plugin_dossier($dirs['dossier'], $i, $i['constante']);
1136
1137 return true;
1138 }
1139
1140 return false;
1141 }
1142
1143
1144 /**
1145 * Désactiver un paquet
1146 *
1147 * @param array $info
1148 * Description courte du paquet
1149 * @return bool
1150 * false si erreur, true sinon
1151 */
1152 public function do_off($info) {
1153 $i = sql_fetsel('*', 'spip_paquets', 'id_paquet=' . sql_quote($info['i']));
1154 // il faudra prendre en compte les autres _DIR_xx
1155 if (in_array($i['constante'], array('_DIR_PLUGINS', '_DIR_PLUGINS_SUPPL'))) {
1156 include_spip('inc/plugin');
1157 $dossier = rtrim($i['src_archive'], '/');
1158 ecrire_plugin_actifs(array(rtrim($dossier, '/')), false, 'enleve');
1159 sql_updateq('spip_paquets', array('actif' => 'non', 'installe' => 'non'), 'id_paquet=' . sql_quote($info['i']));
1160 $this->actualiser_plugin_interessants();
1161 // ce retour est un rien faux...
1162 // il faudrait que la fonction ecrire_plugin_actifs()
1163 // retourne au moins d'eventuels message d'erreur !
1164 return true;
1165 }
1166
1167 return false;
1168 }
1169
1170
1171 /**
1172 * Désinstaller un paquet
1173 *
1174 * @param array $info
1175 * Description courte du paquet
1176 * @return bool
1177 * false si erreur, true sinon
1178 */
1179 public function do_stop($info) {
1180 $i = sql_fetsel('*', 'spip_paquets', 'id_paquet=' . sql_quote($info['i']));
1181 // il faudra prendre en compte les autres _DIR_xx
1182 if (in_array($i['constante'], array('_DIR_PLUGINS', '_DIR_PLUGINS_SUPPL'))) {
1183 include_spip('inc/plugin');
1184 $dossier = rtrim($i['src_archive'], '/');
1185
1186 $installer_plugins = charger_fonction('installer', 'plugins');
1187 // retourne :
1188 // - false : pas de procedure d'install/desinstalle
1189 // - true : operation deja faite
1190 // - tableau : operation faite ce tour ci.
1191 $infos = $installer_plugins($dossier, 'uninstall', $i['constante']);
1192 if (is_bool($infos) or !$infos['install_test'][0]) {
1193 include_spip('inc/plugin');
1194 ecrire_plugin_actifs(array($dossier), false, 'enleve');
1195 sql_updateq('spip_paquets', array('actif' => 'non', 'installe' => 'non'), 'id_paquet=' . sql_quote($info['i']));
1196
1197 return true;
1198 } else {
1199 // echec
1200 $this->log("Échec de la désinstallation de " . $i['src_archive']);
1201 }
1202 }
1203 $this->actualiser_plugin_interessants();
1204
1205 return false;
1206 }
1207
1208
1209 /**
1210 * Effacer les fichiers d'un paquet
1211 *
1212 * @param array $info
1213 * Description courte du paquet
1214 * @return bool
1215 * false si erreur, true sinon
1216 */
1217 public function do_kill($info) {
1218 // on reverifie que c'est bien un plugin auto !
1219 // il faudrait aussi faire tres attention sur un site mutualise
1220 // cette option est encore plus delicate que les autres...
1221 $i = sql_fetsel('*', 'spip_paquets', 'id_paquet=' . sql_quote($info['i']));
1222
1223 if (in_array($i['constante'], array('_DIR_PLUGINS', '_DIR_PLUGINS_SUPPL'))
1224 and substr($i['src_archive'], 0, 5) == 'auto/'
1225 ) {
1226
1227 $dir = constant($i['constante']) . $i['src_archive'];
1228 if (supprimer_repertoire($dir)) {
1229 $id_plugin = sql_getfetsel('id_plugin', 'spip_paquets', 'id_paquet=' . sql_quote($info['i']));
1230
1231 // on supprime le paquet
1232 sql_delete('spip_paquets', 'id_paquet=' . sql_quote($info['i']));
1233
1234 // ainsi que le plugin s'il n'est plus utilise
1235 $utilise = sql_allfetsel(
1236 'pl.id_plugin',
1237 array('spip_paquets AS pa', 'spip_plugins AS pl'),
1238 array('pa.id_plugin = pl.id_plugin', 'pa.id_plugin=' . sql_quote($id_plugin)));
1239 if (!$utilise) {
1240 sql_delete('spip_plugins', 'id_plugin=' . sql_quote($id_plugin));
1241 } else {
1242 // on met a jour d'eventuels obsoletes qui ne le sont plus maintenant
1243 // ie si on supprime une version superieure à une autre qui existe en local...
1244 include_spip('inc/svp_depoter_local');
1245 svp_corriger_obsolete_paquets(array($id_plugin));
1246 }
1247
1248 // on tente un nettoyage jusqu'a la racine de auto/
1249 // si la suppression concerne une profondeur d'au moins 2
1250 // et que les repertoires sont vides
1251 $chemins = explode('/', $i['src_archive']); // auto / prefixe / version
1252 // le premier c'est auto
1253 array_shift($chemins);
1254 // le dernier est deja fait...
1255 array_pop($chemins);
1256 // entre les deux...
1257 while (count($chemins)) {
1258 $vide = true;
1259 $dir = constant($i['constante']) . 'auto/' . implode('/', $chemins);
1260 $fichiers = scandir($dir);
1261 if ($fichiers) {
1262 foreach ($fichiers as $f) {
1263 if ($f[0] != '.') {
1264 $vide = false;
1265 break;
1266 }
1267 }
1268 }
1269 // on tente de supprimer si c'est effectivement vide.
1270 if ($vide and !supprimer_repertoire($dir)) {
1271 break;
1272 }
1273 array_pop($chemins);
1274 }
1275
1276 return true;
1277 }
1278 }
1279
1280 return false;
1281 }
1282
1283
1284 /**
1285 * Installer une librairie
1286 *
1287 * @param array $info
1288 * Description courte du paquet (une librairie ici)
1289 * @return bool
1290 * false si erreur, true sinon
1291 */
1292 public function do_getlib($info) {
1293 if (!defined('_DIR_LIB') or !_DIR_LIB) {
1294 $this->err(_T('svp:erreur_dir_dib_indefini'));
1295 $this->log("/!\ Pas de _DIR_LIB defini !");
1296
1297 return false;
1298 }
1299 if (!is_writable(_DIR_LIB)) {
1300 $this->err(_T('svp:erreur_dir_dib_ecriture', array('dir' => _DIR_LIB)));
1301 $this->log("/!\ Ne peut pas écrire dans _DIR_LIB !");
1302
1303 return false;
1304 }
1305 if (!autoriser('plugins_ajouter')) {
1306 $this->err(_T('svp:erreur_auth_plugins_ajouter_lib'));
1307 $this->log("/!\ Pas autorisé à ajouter des libs !");
1308
1309 return false;
1310 }
1311
1312 $this->log("Recuperer la librairie : " . $info['n']);
1313
1314 // on recupere la mise a jour...
1315 include_spip('action/teleporter');
1316 $teleporter_composant = charger_fonction('teleporter_composant', 'action');
1317 $ok = $teleporter_composant('http', $info['v'], _DIR_LIB . $info['n']);
1318 if ($ok === true) {
1319 return true;
1320 }
1321
1322 $this->err($ok);
1323 $this->log("Téléporteur en erreur : " . $ok);
1324
1325 return false;
1326 }
1327
1328
1329 /**
1330 * Télécharger un paquet
1331 *
1332 * @param array $info
1333 * Description courte du paquet
1334 * @return bool
1335 * false si erreur, true sinon
1336 */
1337 public function do_get($info) {
1338 if (!$this->tester_repertoire_plugins_auto()) {
1339 return false;
1340 }
1341
1342 $i = sql_fetsel('*', 'spip_paquets', 'id_paquet=' . sql_quote($info['i']));
1343
1344 if ($dirs = $this->get_paquet_id($info['i'])) {
1345 $this->ajouter_plugin_interessants_meta($dirs['dossier']);
1346
1347 return true;
1348 }
1349
1350 return false;
1351 }
1352
1353
1354 /**
1355 * Lancer l'installation d'un paquet
1356 *
1357 * @param array $info
1358 * Description courte du paquet
1359 * @return bool
1360 * false si erreur, true sinon
1361 */
1362 public function do_install($info) {
1363 return $this->installer_plugin($info);
1364 }
1365
1366
1367 /**
1368 * Activer un plugin
1369 *
1370 * @param string $dossier
1371 * Chemin du répertoire du plugin
1372 * @param array $i
1373 * Description en BDD du paquet - row SQL (tableau clé => valeur)
1374 * @param string $constante
1375 * Constante indiquant le chemin de base du plugin (_DIR_PLUGINS, _DIR_PLUGINS_SUPPL, _DIR_PLUGINS_DIST)
1376 * @return void
1377 **/
1378 public function activer_plugin_dossier($dossier, $i, $constante = '_DIR_PLUGINS') {
1379 include_spip('inc/plugin');
1380 $this->log("Demande d'activation de : " . $dossier);
1381
1382 //il faut absolument que tous les fichiers de cache
1383 // soient inclus avant modification, sinon un appel ulterieur risquerait
1384 // de charger des fichiers deja charges par un autre !
1385 // C'est surtout le ficher de fonction le probleme (options et pipelines
1386 // sont normalement deja charges).
1387 if (@is_readable(_CACHE_PLUGINS_OPT)) {
1388 include_once(_CACHE_PLUGINS_OPT);
1389 }
1390 if (@is_readable(_CACHE_PLUGINS_FCT)) {
1391 include_once(_CACHE_PLUGINS_FCT);
1392 }
1393 if (@is_readable(_CACHE_PIPELINES)) {
1394 include_once(_CACHE_PIPELINES);
1395 }
1396
1397 include_spip('inc/plugin');
1398 ecrire_plugin_actifs(array($dossier), false, 'ajoute');
1399 $installe = $i['version_base'] ? 'oui' : 'non';
1400 if ($installe == 'oui') {
1401 if (!$i['constante']) {
1402 $i['constante'] = '_DIR_PLUGINS';
1403 }
1404 // installer le plugin au prochain tour
1405 $new_action = array_merge($this->work, array(
1406 'todo' => 'install',
1407 'dossier' => rtrim($dossier, '/'),
1408 'constante' => $i['constante'],
1409 'v' => $i['version'], // pas forcement la meme version qu'avant lors d'une mise a jour.
1410 ));
1411 array_unshift($this->end, $new_action);
1412 $this->log("Demande d'installation de $dossier");
1413 #$this->installer_plugin($dossier);
1414 }
1415
1416 $this->ajouter_plugin_interessants_meta($dossier);
1417 $this->actualiser_plugin_interessants();
1418 }
1419
1420
1421 /**
1422 * Actualiser les plugins intéressants
1423 *
1424 * Décrémente chaque score de plugin présent dans la méta
1425 * 'plugins_interessants' et signifiant que ces plugins
1426 * ont été utilisés récemment.
1427 *
1428 * Les plugins atteignant un score de zéro sont évacués ce la liste.
1429 */
1430 public function actualiser_plugin_interessants() {
1431 // Chaque fois que l'on valide des plugins,
1432 // on memorise la liste de ces plugins comme etant "interessants",
1433 // avec un score initial, qui sera decremente a chaque tour :
1434 // ainsi un plugin active pourra reter visible a l'ecran,
1435 // jusqu'a ce qu'il tombe dans l'oubli.
1436 $plugins_interessants = @unserialize($GLOBALS['meta']['plugins_interessants']);
1437 if (!is_array($plugins_interessants)) {
1438 $plugins_interessants = array();
1439 }
1440
1441 $dossiers = array();
1442 $dossiers_old = array();
1443 foreach ($plugins_interessants as $p => $score) {
1444 if (--$score > 0) {
1445 $plugins_interessants[$p] = $score;
1446 $dossiers[$p . '/'] = true;
1447 } else {
1448 unset($plugins_interessants[$p]);
1449 $dossiers_old[$p . '/'] = true;
1450 }
1451 }
1452
1453 // enlever les anciens
1454 if ($dossiers_old) {
1455 // ATTENTION, il faudra prendre en compte les _DIR_xx
1456 sql_updateq('spip_paquets', array('recent' => 0), sql_in('src_archive', array_keys($dossiers_old)));
1457 }
1458
1459 $plugs = sql_allfetsel('src_archive', 'spip_paquets', 'actif=' . sql_quote('oui'));
1460 $plugs = array_map('array_shift', $plugs);
1461 foreach ($plugs as $dossier) {
1462 $dossiers[$dossier] = true;
1463 $plugins_interessants[rtrim($dossier, '/')] = 30; // score initial
1464 }
1465
1466 $plugs = sql_updateq('spip_paquets', array('recent' => 1), sql_in('src_archive', array_keys($dossiers)));
1467 ecrire_meta('plugins_interessants', serialize($plugins_interessants));
1468 }
1469
1470
1471 /**
1472 * Ajoute un plugin dans les plugins intéressants
1473 *
1474 * Initialise à 30 le score du plugin indiqué par le chemin transmis,
1475 * dans la liste des plugins intéressants.
1476 *
1477 * @param string $dir
1478 * Chemin du répertoire du plugin
1479 */
1480 public function ajouter_plugin_interessants_meta($dir) {
1481 $plugins_interessants = @unserialize($GLOBALS['meta']['plugins_interessants']);
1482 if (!is_array($plugins_interessants)) {
1483 $plugins_interessants = array();
1484 }
1485 $plugins_interessants[$dir] = 30;
1486 ecrire_meta('plugins_interessants', serialize($plugins_interessants));
1487 }
1488
1489 /**
1490 * Lancer l'installation d'un plugin
1491 *
1492 * @param array $info
1493 * Description courte du paquet
1494 * @return bool
1495 * false si erreur, true sinon
1496 */
1497 public function installer_plugin($info) {
1498 // il faut info['dossier'] et info['constante'] pour installer
1499 if ($plug = $info['dossier']) {
1500 $installer_plugins = charger_fonction('installer', 'plugins');
1501 $infos = $installer_plugins($plug, 'install', $info['constante']);
1502 if ($infos) {
1503 // en absence d'erreur, on met a jour la liste des plugins installes...
1504 if (!is_array($infos) or $infos['install_test'][0]) {
1505 $meta_plug_installes = @unserialize($GLOBALS['meta']['plugin_installes']);
1506 if (!$meta_plug_installes) {
1507 $meta_plug_installes = array();
1508 }
1509 $meta_plug_installes[] = $plug;
1510 ecrire_meta('plugin_installes', serialize($meta_plug_installes), 'non');
1511 }
1512
1513 if (!is_array($infos)) {
1514 // l'installation avait deja ete faite un autre jour
1515 return true;
1516 } else {
1517 // l'installation est neuve
1518 list($ok, $trace) = $infos['install_test'];
1519 if ($ok) {
1520 return true;
1521 }
1522 // l'installation est en erreur
1523 $this->err(_T('svp:message_action_finale_install_fail',
1524 array('plugin' => $info['n'], 'version' => denormaliser_version($info['v']))) . "<br />" . $trace);
1525 }
1526 }
1527 }
1528
1529 return false;
1530 }
1531
1532
1533 /**
1534 * Télécharge un paquet
1535 *
1536 * Supprime les fichiers obsolètes (si présents)
1537 *
1538 * @param int|array $id_or_row
1539 * Identifiant du paquet ou description ligne SQL du paquet
1540 * @param string $dest_ancien
1541 * Chemin vers l'ancien répertoire (pour les mises à jour par VCS)
1542 * @return bool|array
1543 * False si erreur.
1544 * Tableau de 2 index sinon :
1545 * - dir : Chemin du paquet téléchargé depuis la racine
1546 * - dossier : Chemin du paquet téléchargé, depuis _DIR_PLUGINS
1547 */
1548 public function get_paquet_id($id_or_row, $dest_ancien = "") {
1549 // on peut passer direct le row sql...
1550 if (!is_array($id_or_row)) {
1551 $i = sql_fetsel('*', 'spip_paquets', 'id_paquet=' . sql_quote($id_or_row));
1552 } else {
1553 $i = $id_or_row;
1554 }
1555 unset($id_or_row);
1556
1557 if ($i['nom_archive'] and $i['id_depot']) {
1558 $this->log("Recuperer l'archive : " . $i['nom_archive']);
1559 // on récupère les informations intéressantes du dépot :
1560 // - url des archives
1561 // - éventuellement : type de serveur (svn, git) et url de la racine serveur (svn://..../)
1562 $adresses = sql_fetsel(array('url_archives', 'type', 'url_serveur'), 'spip_depots',
1563 'id_depot=' . sql_quote($i['id_depot']));
1564 if ($adresses and $adresse = $adresses['url_archives']) {
1565
1566 // destination : auto/prefixe/version (sinon auto/nom_archive/version)
1567 $prefixe = sql_getfetsel('pl.prefixe',
1568 array('spip_paquets AS pa', 'spip_plugins AS pl'),
1569 array('pa.id_plugin = pl.id_plugin', 'pa.id_paquet=' . sql_quote($i['id_paquet'])));
1570
1571 // prefixe
1572 $base = ($prefixe ? strtolower($prefixe) : substr($i['nom_archive'], 0, -4)); // enlever .zip ...
1573
1574 // prefixe/version
1575 $dest_future = $base . '/v' . denormaliser_version($i['version']);
1576
1577 // Nettoyer les vieux formats dans auto/
1578 if ($this->tester_repertoire_destination_ancien_format(_DIR_PLUGINS_AUTO . $base)) {
1579 supprimer_repertoire(_DIR_PLUGINS_AUTO . $base);
1580 }
1581
1582 // l'url est différente en fonction du téléporteur
1583 $teleporteur = $this->choisir_teleporteur($adresses['type']);
1584 if ($teleporteur == 'http') {
1585 $url = $adresse . '/' . $i['nom_archive'];
1586 $dest = $dest_future;
1587 } else {
1588 $url = $adresses['url_serveur'] . '/' . $i['src_archive'];
1589 $dest = $dest_ancien ? $dest_ancien : $dest_future;
1590 }
1591
1592 // on recupere la mise a jour...
1593 include_spip('action/teleporter');
1594 $teleporter_composant = charger_fonction('teleporter_composant', 'action');
1595 $ok = $teleporter_composant($teleporteur, $url, _DIR_PLUGINS_AUTO . $dest);
1596 if ($ok === true) {
1597 // pour une mise à jour via VCS, il faut rebasculer sur le nouveau nom de repertoire
1598 if ($dest != $dest_future) {
1599 rename(_DIR_PLUGINS_AUTO . $dest, _DIR_PLUGINS_AUTO . $dest_future);
1600 }
1601
1602 return array(
1603 'dir' => _DIR_PLUGINS_AUTO . $dest,
1604 'dossier' => 'auto/' . $dest, // c'est depuis _DIR_PLUGINS ... pas bien en dur...
1605 );
1606 }
1607 $this->err($ok);
1608 $this->log("Téléporteur en erreur : " . $ok);
1609 } else {
1610 $this->log("Aucune adresse pour le dépot " . $i['id_depot']);
1611 }
1612 }
1613
1614 return false;
1615 }
1616
1617
1618 /**
1619 * Teste que le répertoire plugins auto existe et
1620 * que l'on peut ecrire dedans !
1621 *
1622 * @return bool
1623 * True si on peut écrire dedans, false sinon
1624 **/
1625 public function tester_repertoire_plugins_auto() {
1626 include_spip('inc/plugin'); // pour _DIR_PLUGINS_AUTO
1627 if (!defined('_DIR_PLUGINS_AUTO') or !_DIR_PLUGINS_AUTO) {
1628 $this->err(_T('svp:erreur_dir_plugins_auto_indefini'));
1629 $this->log("/!\ Pas de _DIR_PLUGINS_AUTO defini !");
1630
1631 return false;
1632 }
1633 if (!is_writable(_DIR_PLUGINS_AUTO)) {
1634 $this->err(_T('svp:erreur_dir_plugins_auto_ecriture', array('dir' => _DIR_PLUGINS_AUTO)));
1635 $this->log("/!\ Ne peut pas écrire dans _DIR_PLUGINS_AUTO !");
1636
1637 return false;
1638 }
1639
1640 return true;
1641 }
1642
1643
1644 /**
1645 * Teste si un répertoire du plugin auto, contenant un plugin
1646 * est dans un ancien format auto/prefixe/ (qui doit alors être supprimé)
1647 * ou dans un format normal auto/prefixe/vx.y.z
1648 *
1649 * @example
1650 * $this->tester_repertoire_destination_ancien_format(_DIR_PLUGINS_AUTO . $base);
1651 *
1652 * @param string $dir_dans_auto
1653 * Chemin du répertoire à tester
1654 * @return bool
1655 * true si le répertoire est dans un ancien format
1656 */
1657 public function tester_repertoire_destination_ancien_format($dir_dans_auto) {
1658 // si on tombe sur un auto/X ayant des fichiers (et pas uniquement des dossiers)
1659 // ou un dossier qui ne commence pas par 'v'
1660 // c'est que auto/X n'était pas chargé avec SVP
1661 // ce qui peut arriver lorsqu'on migre de SPIP 2.1 à 3.0
1662 // dans ce cas, on supprime auto X pour mettre notre nouveau paquet.
1663 if (is_dir($dir_dans_auto)) {
1664 $base_files = scandir($dir_dans_auto);
1665 if (is_array($base_files)) {
1666 $base_files = array_diff($base_files, array('.', '..'));
1667 foreach ($base_files as $f) {
1668 if (($f[0] != '.' and $f[0] != 'v') // commence pas par v
1669 or ($f[0] != '.' and !is_dir($dir_dans_auto . '/' . $f))
1670 ) { // commence par v mais pas repertoire
1671 return true;
1672 }
1673 }
1674 }
1675 }
1676
1677 return false;
1678 }
1679
1680
1681 /**
1682 * Teste s'il est possible d'utiliser un téléporteur particulier,
1683 * sinon retourne le nom du téléporteur par défaut
1684 *
1685 * @param string $teleporteur Téléporteur VCS à tester
1686 * @param string $defaut Téléporteur par défaut
1687 *
1688 * @return string Nom du téléporteur à utiliser
1689 **/
1690 public function choisir_teleporteur($teleporteur, $defaut = 'http') {
1691 // Utiliser un teleporteur vcs si possible si demandé
1692 if (defined('SVP_PREFERER_TELECHARGEMENT_PAR_VCS') and SVP_PREFERER_TELECHARGEMENT_PAR_VCS) {
1693 if ($teleporteur) {
1694 include_spip('teleporter/' . $teleporteur);
1695 $tester_teleporteur = "teleporter_{$teleporteur}_tester";
1696 if (function_exists($tester_teleporteur)) {
1697 if ($tester_teleporteur()) {
1698 return $teleporteur;
1699 }
1700 }
1701 }
1702 }
1703
1704 return $defaut;
1705 }
1706
1707
1708 /**
1709 * Teste si le plugin SVP (celui-ci donc) a
1710 * été désinstallé / désactivé dans les actions réalisées
1711 *
1712 * @note
1713 * On ne peut tester sa désactivation que dans le hit où la désinstallation
1714 * est réalisée, puisque après, s'il a été désactivé, au prochain hit
1715 * on ne connaîtra plus ce fichier !
1716 *
1717 * @return bool
1718 * true si SVP a été désactivé, false sinon
1719 **/
1720 public function tester_si_svp_desactive() {
1721 foreach ($this->done as $d) {
1722 if ($d['p'] == 'SVP'
1723 and $d['done'] == true
1724 and in_array($d['todo'], array('off', 'stop'))
1725 ) {
1726 return true;
1727 }
1728 }
1729
1730 return false;
1731 }
1732
1733 }
1734
1735
1736 /**
1737 * Gère le traitement des actions des formulaires utilisant l'Actionneur
1738 *
1739 * @param array $actions
1740 * Liste des actions a faire (id_paquet => action)
1741 * @param array $retour
1742 * Tableau de retour du CVT dans la partie traiter
1743 * @param string $redirect
1744 * URL de retour
1745 * @return void
1746 **/
1747 function svp_actionner_traiter_actions_demandees($actions, &$retour, $redirect = null) {
1748 $actionneur = new Actionneur();
1749 $actionneur->ajouter_actions($actions);
1750 $actionneur->verrouiller();
1751 $actionneur->sauver_actions();
1752
1753 $redirect = $redirect ? $redirect : generer_url_ecrire('admin_plugin');
1754 $retour['redirect'] = generer_url_action('actionner', 'redirect=' . urlencode($redirect));
1755 set_request('_todo', '');
1756 $retour['message_ok'] = _T("svp:action_patienter");
1757 }