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