source: spip-zone/_core_/plugins/svp/inc/svp_actionner.php

Last change on this file was 125009, checked in by Matthieu Marcillaud, 2 months ago

Tickets #4473 et #4474 (Real3t) : réparer partièlement le téléchargement par vcs.
On fait fonctionner Git. Mais si SVN, on n’a plus l’URL actuellement (donc on ne le fait pas).
On utilise dans tous les cas la valeur de src_archive uniquement.

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