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