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

Last change on this file was 112276, checked in by marcimat@…, 13 months ago

Fix #4205 : À l’installation, il n’y a pas encore de fichier de sauvegarde des actions SVP. Du coup, il pouvait y avoir un petit bug en tentant de les lire.
(Francky).

File size: 50.0 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']);
1580                                if ($teleporteur == 'http') {
1581                                        $url = $adresse . '/' . $i['nom_archive'];
1582                                        $dest = $dest_future;
1583                                } else {
1584                                        $url = $adresses['url_serveur'] . '/' . $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 $defaut Téléporteur par défaut
1683         *
1684         * @return string Nom du téléporteur à utiliser
1685         **/
1686        public function choisir_teleporteur($teleporteur, $defaut = 'http') {
1687                // Utiliser un teleporteur vcs si possible si demandé
1688                if (defined('SVP_PREFERER_TELECHARGEMENT_PAR_VCS') and SVP_PREFERER_TELECHARGEMENT_PAR_VCS) {
1689                        if ($teleporteur) {
1690                                include_spip('teleporter/' . $teleporteur);
1691                                $tester_teleporteur = "teleporter_{$teleporteur}_tester";
1692                                if (function_exists($tester_teleporteur)) {
1693                                        if ($tester_teleporteur()) {
1694                                                return $teleporteur;
1695                                        }
1696                                }
1697                        }
1698                }
1699
1700                return $defaut;
1701        }
1702
1703
1704        /**
1705         * Teste si le plugin SVP (celui-ci donc) a
1706         * été désinstallé / désactivé dans les actions réalisées
1707         *
1708         * @note
1709         *     On ne peut tester sa désactivation que dans le hit où la désinstallation
1710         *     est réalisée, puisque après, s'il a été désactivé, au prochain hit
1711         *     on ne connaîtra plus ce fichier !
1712         *
1713         * @return bool
1714         *     true si SVP a été désactivé, false sinon
1715         **/
1716        public function tester_si_svp_desactive() {
1717                foreach ($this->done as $d) {
1718                        if ($d['p'] == 'SVP'
1719                                and $d['done'] == true
1720                                and in_array($d['todo'], array('off', 'stop'))
1721                        ) {
1722                                return true;
1723                        }
1724                }
1725
1726                return false;
1727        }
1728
1729}
1730
1731
1732/**
1733 * Gère le traitement des actions des formulaires utilisant l'Actionneur
1734 *
1735 * @param array $actions
1736 *     Liste des actions a faire (id_paquet => action)
1737 * @param array $retour
1738 *     Tableau de retour du CVT dans la partie traiter
1739 * @param string $redirect
1740 *     URL de retour
1741 * @return void
1742 **/
1743function svp_actionner_traiter_actions_demandees($actions, &$retour, $redirect = null) {
1744        $actionneur = new Actionneur();
1745        $actionneur->ajouter_actions($actions);
1746        $actionneur->verrouiller();
1747        $actionneur->sauver_actions();
1748
1749        $redirect = $redirect ? $redirect : generer_url_ecrire('admin_plugin');
1750        $retour['redirect'] = generer_url_action('actionner', 'redirect=' . urlencode($redirect));
1751        set_request('_todo', '');
1752        $retour['message_ok'] = _T("svp:action_patienter");
1753}
Note: See TracBrowser for help on using the repository browser.