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

Last change on this file was 117891, checked in by marcimat@…, 2 months ago

Ticket #4383 : Bien prendre en compte les nécessite / utilise des paquets.xml qui ont une balise <spip> intégrée.

  • Property svn:eol-style set to native
File size: 36.9 KB
Line 
1<?php
2
3/**
4 * Gestion du décideur : il calcule en fonction d'une demande d'action
5 * sur les plugins tous les enchainements que cela implique sur les
6 * dépendances.
7 *
8 * @plugin SVP pour SPIP
9 * @license GPL
10 * @package SPIP\SVP\Decideur
11 */
12
13if (!defined("_ECRIRE_INC_VERSION")) {
14        return;
15}
16
17include_spip('plugins/installer'); // pour spip_version_compare()
18include_spip('inc/svp_rechercher'); // svp_verifier_compatibilite_spip()
19# include_spip('inc/plugin'); // plugin_version_compatible() [inclu dans svp_rechercher]
20
21/**
22 * Le décideur calcule les actions qui doivent être faites en fonction
23 * de ce qui est demandé et des différentes dépendances des plugins.
24 *
25 * @package SPIP\SVP\Actionner
26 **/
27class Decideur {
28
29        /**
30         * Plugins actifs en cours avant toute modification
31         *
32         * @var array
33         *     Index 'i' : plugins triés par identifiant en base [i][32] = tableau de description
34         *     Index 'p' : plugins triés par prefixe de plugin [p][MOTS] = tableau de description
35         */
36        public $start = array(
37                'i' => array(),
38                'p' => array(),
39        );
40
41        /**
42         * Plugins actifs à la fin des modifications effectuées
43         *
44         * @var array
45         *     Index 'i' : plugins triés par identifiant en base [i][32] = tableau de description
46         *     Index 'p' : plugins triés par prefixe de plugin [p][MOTS] = tableau de description
47         */
48        public $end = array(
49                'i' => array(),
50                'p' => array(),
51        );
52
53        /**
54         * Plugins procure par SPIP
55         *
56         * @var array
57         *     Tableau ('PREFIXE' => numéro de version)
58         */
59        public $procure = array();
60
61        /**
62         * Toutes les actions à faire demandées
63         * (ce que l'on demande à l'origine)
64         *
65         * @var array
66         *     Tableau ('identifiant' => tableau de description)
67         */
68        public $ask = array();
69
70        /**
71         * Toutes les actions à faire demandées et consécutives aux dépendances
72         *
73         * @var array
74         *     Tableau ('identifiant' => tableau de description)
75         */
76        public $todo = array();
77
78        /**
79         * Toutes les actions à faire consécutives aux dépendances
80         *
81         * C'est à dire les actions à faire en plus de celles demandées.
82         *
83         * @var array
84         *     Tableau ('identifiant' => tableau de description)
85         */
86        public $changes = array();
87
88        /**
89         * Tous les plugins à arrêter (désactiver ou désinstaller)
90         *
91         * @var array
92         *     Tableau ('PREFIXE' => tableau de description)
93         */
94        public $off = array();
95
96        /**
97         * Tous les plugins invalidés (suite a des dependances introuvables, mauvaise version de SPIP...)
98         *
99         * @var array
100         *     Tableau ('PREFIXE' => tableau de description)
101         */
102        public $invalides = array();
103
104        /**
105         * Liste des erreurs
106         *
107         * @var array
108         *     Tableau ('identifiant' => liste des erreurs)
109         */
110        public $err = array();
111
112        /**
113         * État de santé (absence d'erreur)
114         *
115         * Le résultat true permettra d'effectuer toutes les actions.
116         * Passe à false dès qu'une erreur est présente !
117         *
118         * @var bool
119         */
120        public $ok = true;
121
122        /**
123         * Loguer les différents éléments
124         *
125         * Sa valeur sera initialisée par la configuration 'mode_log_verbeux' de SVP
126         *
127         * @var bool
128         */
129        public $log = false;
130
131        /**
132         * Générer une erreur si on demande une mise à jour d'un plugin
133         * alors qu'on ne la connait pas.
134         *
135         * @var bool
136         */
137        public $erreur_sur_maj_introuvable = true;
138
139        /**
140         * Constructeur
141         *
142         * Initialise la propriété $log en fonction de la configuration
143         */
144        public function __construct() {
145                include_spip('inc/config');
146                $this->log = (lire_config('svp/mode_log_verbeux') == 'oui');
147        }
148
149
150        /**
151         * Liste des plugins déjà actifs
152         *
153         * @var array
154         *     Index 'i' : plugins triés par identifiant en base [i][32] = tableau de description
155         *     Index 'p' : plugins triés par prefixe de plugin [p][MOTS] = tableau de description
156         */
157        public function liste_plugins_actifs() {
158                return $this->infos_courtes(array('pa.actif=' . sql_quote('oui'), 'pa.attente=' . sql_quote('non')));
159        }
160
161        /**
162         * Teste si un paquet (via son identifiant) est en attente
163         *
164         * Les plugins en attente ont un statut spécial : à la fois dans la
165         * liste des plugins actifs, mais désactivés. Un plugin passe 'en attente'
166         * lorsqu'il est actif mais perd accidentellement une dépendance,
167         * par exemple si une dépendance est supprimée par FTP.
168         * Dès que sa dépendance revient, le plugin se réactive.
169         *
170         * L'interface de gestion des plugins de SVP, elle, permet pour ces plugins
171         * de les désactiver ou réactiver (retéléchargeant alors la dépendance si possible).
172         *
173         * @param int $id
174         *     Identifiant du plugin
175         * @return bool
176         *     Le plugin est-il en attente ?
177         */
178        public function est_attente_id($id) {
179                static $attente = null;
180                if (is_null($attente)) {
181                        $attente = $this->infos_courtes('pa.attente=' . sql_quote('oui'));
182                }
183
184                return isset($attente['i'][$id]) ? $attente['i'][$id] : false;
185        }
186
187        /**
188         * Liste des plugins procurés par SPIP
189         *
190         * Calcule la liste des plugins que le core de SPIP déclare procurer.
191         *
192         * @return array
193         *     Tableau ('PREFIXE' => version)
194         */
195        public function liste_plugins_procure() {
196                $procure = array();
197                $get_infos = charger_fonction('get_infos', 'plugins');
198                $infos['_DIR_RESTREINT'][''] = $get_infos('./', false, _DIR_RESTREINT);
199
200                foreach ($infos['_DIR_RESTREINT']['']['procure'] as $_procure) {
201                        $prefixe = strtoupper($_procure['nom']);
202                        $procure[$prefixe] = $_procure['version'];
203                }
204
205                return $procure;
206        }
207
208        /**
209         * Écrit un log
210         *
211         * Écrit un log si la propriété $log l'autorise.
212         *
213         * @param mixed $quoi
214         *     La chose à logguer (souvent un texte)
215         **/
216        public function log($quoi) {
217                if ($this->log) {
218                        spip_log($quoi, 'decideur');
219                }
220        }
221
222        /**
223         * Retourne le tableau de description d'un paquet (via son identifiant)
224         *
225         * @note
226         *     Attention, retourne un tableau complexe.
227         *     La description sera dans : ['i'][$id]
228         * @param int $id
229         *     Identifiant du paquet
230         * @return array
231         *     Index 'i' : plugins triés par identifiant en base [i][32] = tableau de description
232         *     Index 'p' : plugins triés par prefixe de plugin [p][MOTS] = tableau de description
233         **/
234        public function infos_courtes_id($id) {
235                // on cache ceux la
236                static $plug = array();
237                if (!isset($plug[$id])) {
238                        $plug[$id] = $this->infos_courtes('pa.id_paquet=' . sql_quote($id));
239                }
240
241                return $plug[$id];
242        }
243
244        /**
245         * Récupérer les infos utiles des paquet
246         *
247         * Crée un tableau de description pour chaque paquet dans une
248         * écriture courte comme index ('i' pour identifiant) tel que :
249         * - i = identifiant
250         * - p = prefixe (en majuscule)
251         * - n = nom du plugin
252         * - v = version
253         * - e = etat
254         * - a = actif
255         * - du = dépendances utilise
256         * - dn = dépendances nécessite
257         * - dl = dépendances librairie
258         * - procure = prefixes procurés
259         * - maj = mise à jour
260         *
261         *
262         * On passe un where ($condition) et on crée deux tableaux, l'un des paquets
263         * triés par identifiant, l'autre par prefixe.
264         *
265         * @param array|string $condition
266         *     Condition where
267         * @param bool $multiple
268         *     Si multiple, le tableau par préfixe est un sous-tableau (il peut alors
269         *     y avoir plusieurs paquets pour un même prefixe, classés par états décroissants)
270         * @return array
271         *     Index 'i' : plugins triés par identifiant en base [i][32] = tableau de description
272         *     Index 'p' : plugins triés par prefixe de plugin [p][MOTS] = tableau de description
273         *                 ou, avec $multiple=true : [p][MOTS][] = tableau de description
274         */
275        public function infos_courtes($condition, $multiple = false) {
276                $plugs = array(
277                        'i' => array(),
278                        'p' => array()
279                );
280
281                $from = array('spip_paquets AS pa', 'spip_plugins AS pl');
282                $orderby = $multiple ? 'pa.etatnum DESC' : '';
283                $where = array('pa.id_plugin = pl.id_plugin');
284                if (is_array($condition)) {
285                        $where = array_merge($where, $condition);
286                } else {
287                        $where[] = $condition;
288                }
289
290                include_spip('inc/filtres'); // extraire_multi()
291                $res = sql_allfetsel(array(
292                        'pa.id_paquet AS i',
293                        'pl.nom AS n',
294                        'pl.prefixe AS p',
295                        'pa.version AS v',
296                        'pa.etatnum AS e',
297                        'pa.compatibilite_spip',
298                        'pa.dependances',
299                        'pa.procure',
300                        'pa.id_depot',
301                        'pa.maj_version AS maj',
302                        'pa.actif AS a'
303                ), $from, $where, '', $orderby);
304                foreach ($res as $r) {
305                        $r['p'] = strtoupper($r['p']); // on s'assure du prefixe en majuscule.
306
307                        // savoir si un paquet est en local ou non...
308                        $r['local'] = ($r['id_depot']) == 0 ? true : false;
309                        unset($r['id_depot']);
310
311                        $d = unserialize($r['dependances']);
312                        // voir pour enregistrer en bdd simplement 'n' et 'u' (pas la peine d'encombrer)...
313                        $deps = array('necessite' => array(array()), 'utilise' => array(array()), 'librairie' => array(array()));
314                        if (!$d) {
315                                $d = $deps;
316                        }
317
318                        unset($r['dependances']);
319                        if (!$r['procure'] or !$proc = unserialize($r['procure'])) {
320                                $proc = array();
321                        }
322                        $r['procure'] = $proc;
323
324                        /*
325                         * On extrait les multi sur le nom du plugin
326                         */
327                        $r['n'] = extraire_multi($r['n']);
328
329                        $plugs['i'][$r['i']] = $r;
330
331
332                        // pour chaque type de dependences... (necessite, utilise, librairie)
333                        // on cree un tableau unique [$dependence] = array()
334                        // au lieu de plusieurs tableaux par version de spip
335                        // en ne mettant dans 0 que ce qui concerne notre spip local
336                        foreach ($deps as $cle => $defaut) {
337                                if (!isset($d[$cle])) {
338                                        $d[$cle] = $defaut;
339                                }
340
341                                // gerer les dependences autres que dans 0 (communs ou local) !!!!
342                                // il peut exister des cles info[dn]["[version_spip_min;version_spip_max]"] de dependences
343                                if (!isset($d[$cle][0]) or count($d[$cle]) > 1) {
344                                        $dep = array();
345                                        $dep[0] = isset($d[$cle][0]) ? $d[$cle][0] : array();
346                                        unset($d[$cle][0]);
347                                        foreach ($d[$cle] as $version => $dependences) {
348                                                if (svp_verifier_compatibilite_spip($version)) {
349                                                        $dep[0] = array_merge($dep[0], $dependences);
350                                                }
351                                        }
352                                        $d[$cle] = $dep;
353                                }
354                        }
355                        // passer les prefixes en majuscule
356                        foreach ($d['necessite'][0] as $i => $n) {
357                                $d['necessite'][0][$i]['nom'] = strtoupper($n['nom']);
358                        }
359                        $plugs['i'][$r['i']]['dn'] = $d['necessite'][0];
360                        $plugs['i'][$r['i']]['du'] = $d['utilise'][0];
361                        $plugs['i'][$r['i']]['dl'] = $d['librairie'][0];
362
363
364                        if ($multiple) {
365                                $plugs['p'][$r['p']][] = &$plugs['i'][$r['i']]; // alias
366                        } else {
367                                $plugs['p'][$r['p']] = &$plugs['i'][$r['i']]; // alias
368                        }
369
370                }
371
372                return $plugs;
373        }
374
375
376        /**
377         * Ajoute une erreur sur un paquet
378         *
379         * Passe le flag OK à false : on ne pourra pas faire les actions demandées.
380         *
381         * @param int $id
382         *     Identifiant de paquet
383         * @param string $texte
384         *     Texte de l'erreur
385         */
386        public function erreur($id, $texte = '') {
387                $this->log("erreur: $id -> $texte");
388                if (!isset($this->err[$id]) or !is_array($this->err[$id])) {
389                        $this->err[$id] = array();
390                }
391                $this->err[$id][] = $texte;
392                $this->ok = false;
393        }
394
395        /**
396         * Teste si une erreur est présente sur un paquet (via son identifiant)
397         *
398         * @param int $id
399         *     Identifiant de paquet
400         * @return bool|array
401         *     false si pas d'erreur, tableau des erreurs sinon.
402         */
403        public function en_erreur($id) {
404                return isset($this->err[$id]) ? $this->err[$id] : false;
405        }
406
407
408        /**
409         * Vérifie qu'un plugin plus récent existe pour un préfixe et une version donnée
410         *
411         * @param string $prefixe
412         *     Préfixe du plugin
413         * @param string $version
414         *     Compatibilité à comparer, exemple '[1.0;]'
415         * @return bool|array
416         *     false si pas de plugin plus récent trouvé
417         *     tableau de description du paquet le plus récent sinon
418         */
419        public function chercher_plugin_recent($prefixe, $version) {
420                $news = $this->infos_courtes(array(
421                        'pl.prefixe=' . sql_quote($prefixe),
422                        'pa.obsolete=' . sql_quote('non'),
423                        'pa.id_depot > ' . sql_quote(0)
424                ), true);
425                $res = false;
426                if ($news and count($news['p'][$prefixe]) > 0) {
427                        foreach ($news['p'][$prefixe] as $new) {
428                                if (spip_version_compare($new['v'], $version, '>')) {
429                                        if (!$res or version_compare($new['v'], $res['v'], '>')) {
430                                                $res = $new;
431                                        }
432                                }
433                        }
434                }
435
436                return $res;
437        }
438
439        /**
440         * Vérifie qu'un plugin existe pour un préfixe et une version donnée
441         *
442         * @param string $prefixe
443         *     Préfixe du plugin
444         * @param string $version
445         *     Compatibilité à comparer, exemple '[1.0;]'
446         * @return bool|array
447         *     false si pas de plugin plus récent trouvé
448         *     tableau de description du paquet le plus récent sinon
449         */
450        public function chercher_plugin_compatible($prefixe, $version) {
451                $plugin = array();
452
453                $v = '000.000.000';
454                // on choisit en priorite dans les paquets locaux !
455                $locaux = $this->infos_courtes(array(
456                        'pl.prefixe=' . sql_quote($prefixe),
457                        'pa.obsolete=' . sql_quote('non'),
458                        'pa.id_depot=' . sql_quote(0)
459                ), true);
460                if ($locaux and isset($locaux['p'][$prefixe]) and count($locaux['p'][$prefixe]) > 0) {
461                        foreach ($locaux['p'][$prefixe] as $new) {
462                                if (plugin_version_compatible($version, $new['v'])
463                                        and svp_verifier_compatibilite_spip($new['compatibilite_spip'])
464                                        and ($new['v'] > $v)
465                                ) {
466                                        $plugin = $new;
467                                        $v = $new['v'];
468                                }
469                        }
470                }
471
472                // qu'on ait trouve ou non, on verifie si un plugin local ne procure pas le prefixe
473                // dans une version plus recente
474                $locaux_procure = $this->infos_courtes(array(
475                        'pa.procure LIKE ' . sql_quote('%' . $prefixe . '%'),
476                        'pa.obsolete=' . sql_quote('non'),
477                        'pa.id_depot=' . sql_quote(0)
478                ), true);
479                foreach ($locaux_procure['i'] as $new) {
480                        if (isset($new['procure'][$prefixe])
481                                and plugin_version_compatible($version, $new['procure'][$prefixe])
482                                and svp_verifier_compatibilite_spip($new['compatibilite_spip'])
483                                and spip_version_compare($new['procure'][$prefixe], $v, ">")
484                        ) {
485                                $plugin = $new;
486                                $v = $new['v'];
487                        }
488                }
489
490                // sinon dans les paquets distants (mais on ne sait pas encore y trouver les procure)
491                if (!$plugin) {
492                        $distants = $this->infos_courtes(array(
493                                'pl.prefixe=' . sql_quote($prefixe),
494                                'pa.obsolete=' . sql_quote('non'),
495                                'pa.id_depot>' . sql_quote(0)
496                        ), true);
497                        if ($distants and isset($distants['p'][$prefixe]) and count($distants['p'][$prefixe]) > 0) {
498                                foreach ($distants['p'][$prefixe] as $new) {
499                                        if (plugin_version_compatible($version, $new['v'])
500                                                and svp_verifier_compatibilite_spip($new['compatibilite_spip'])
501                                                and ($new['v'] > $v)
502                                        ) {
503                                                $plugin = $new;
504                                                $v = $new['v'];
505                                        }
506                                }
507                        }
508                }
509
510                return ($plugin ? $plugin : false);
511        }
512
513
514        /**
515         * Indique qu'un paquet passe à on (on l'active)
516         *
517         * @param array $info
518         *     Description du paquet
519         **/
520        public function add($info) {
521                $this->end['i'][$info['i']] = $info;
522                $this->end['p'][$info['p']] = &$this->end['i'][$info['i']];
523        }
524
525        /**
526         * Indique qu'un paquet passe à off (on le désactive ou désinstalle)
527         *
528         * @param array $info
529         *     Description du paquet
530         * @param bool $recur
531         *     Passer à off les plugins qui en dépendent, de façon récursive ?
532         **/
533        public function off($info, $recur = false) {
534                $this->log('- stopper ' . $info['p']);
535                $this->remove($info);
536                $this->off[$info['p']] = $info;
537
538                // si recursif, on stoppe aussi les plugins dependants
539                if ($recur) {
540                        $prefixes = array_merge(array($info['p']), array_keys($info['procure']));
541                        foreach ($this->end['i'] as $id => $plug) {
542                                if (is_array($plug['dn']) and $plug['dn']) {
543                                        foreach ($plug['dn'] as $n) {
544                                                if (in_array($n['nom'], $prefixes)) {
545                                                        $this->change($plug, 'off');
546                                                        $this->off($plug, true);
547                                                }
548                                        }
549                                }
550                        }
551                }
552        }
553
554        /**
555         * Teste qu'un paquet (via son préfixe) sera passé off (désactivé ou désinstallé)
556         *
557         * @param string $prefixe
558         *     Prefixe du paquet
559         * @return bool
560         *     Le paquet sera t'il off ?
561         **/
562        public function sera_off($prefixe) {
563                return isset($this->off[$prefixe]) ? $this->off[$prefixe] : false;
564        }
565
566        /**
567         * Teste qu'un paquet (via son identifiant) sera passé off (désactivé ou désinstallé)
568         *
569         * @param int $id
570         *     Identifiant du paquet
571         * @return bool
572         *     Le paquet sera t'il off ?
573         **/
574        public function sera_off_id($id) {
575                foreach ($this->off as $info) {
576                        if ($info['i'] == $id) {
577                                return $info;
578                        }
579                }
580
581                return false;
582        }
583
584        /**
585         * Teste qu'un paquet (via son préfixe) sera actif directement
586         * ou par l'intermediaire d'un procure
587         *
588         * @param string $prefixe
589         *     Préfixe du paquet
590         * @return bool
591         *     Le paquet sera t'il actif ?
592         **/
593        public function sera_actif($prefixe) {
594                if (isset($this->end['p'][$prefixe])) {
595                        return $this->end['p'][$prefixe];
596                }
597                // sinon regarder les procure
598                $v = "0.0.0";
599                $plugin = false;
600                foreach ($this->end['p'] as $endp => $end) {
601                        if (isset($end['procure'][$prefixe])
602                                and spip_version_compare($end['procure'][$prefixe], $v, ">")
603                        ) {
604                                $v = $end['procure'][$prefixe];
605                                $plugin = $this->end['p'][$endp];
606                        }
607                }
608
609                return $plugin;
610        }
611
612        /**
613         * Teste qu'un paquet (via son identifiant) sera actif
614         *
615         * @param int $id
616         *     Identifiant du paquet
617         * @return bool
618         *     Le paquet sera t'il actif ?
619         **/
620        public function sera_actif_id($id) {
621                return isset($this->end['i'][$id]) ? $this->end['i'][$id] : false;
622        }
623
624        /**
625         * Ajouter une action/paquet à la liste des demandées
626         *
627         * L'ajoute aussi à la liste de toutes les actions !
628         *
629         * @param array $info
630         *     Description du paquet concerné
631         * @param string $quoi
632         *     Type d'action (on, off, kill, upon...)
633         */
634        public function ask($info, $quoi) {
635                $this->ask[$info['i']] = $info;
636                $this->ask[$info['i']]['todo'] = $quoi;
637                $this->todo($info, $quoi);
638        }
639
640        /**
641         * Ajouter une action/paquet à la liste des changements en plus
642         * par rapport à la demande initiale
643         *
644         * L'ajoute aussi à la liste de toutes les actions !
645         *
646         * @param array $info
647         *     Description du paquet concerné
648         * @param string $quoi
649         *     Type d'action (on, off, kill, upon...)
650         */
651        public function change($info, $quoi) {
652                $this->changes[$info['i']] = $info;
653                $this->changes[$info['i']]['todo'] = $quoi;
654                $this->todo($info, $quoi);
655        }
656
657
658        /**
659         * Annule une action (automatique) qui finalement était réellement demandée.
660         *
661         * Par exemple, une mise à 'off' de paquet entraîne d'autres mises à
662         * 'off' des paquets qui en dépendent. Si une action sur un des paquets
663         * dépendants était aussi demandée, il faut annuler l'action automatique.
664         *
665         * @param array $info
666         *     Description du paquet concerné
667         */
668        public function annule_change($info) {
669                unset($this->changes[$info['i']]);
670        }
671
672        /**
673         * Ajouter une action/paquet à la liste de toutes les actions à faire
674         *
675         * @param array $info
676         *     Description du paquet concerné
677         * @param string $quoi
678         *     Type d'action (on, off, kill, upon...)
679         */
680        public function todo($info, $quoi) {
681                $this->todo[$info['i']] = $info;
682                $this->todo[$info['i']]['todo'] = $quoi;
683        }
684
685        /**
686         * Retire un paquet de la liste des paquets à activer
687         *
688         * @param array $info
689         *     Description du paquet concerné
690         */
691        public function remove($info) {
692                // aucazou ce ne soit pas les memes ids entre la demande et la bdd,
693                // on efface aussi avec l'id donne par le prefixe.
694                // Lorsqu'on desactive un plugin en "attente", il n'est pas actif !
695                // on teste tout de meme donc qu'il est la ce prefixe !
696                $i = false;
697                if (isset($this->end['p'][$info['p']])) {
698                        $i = $this->end['p'][$info['p']];
699                }
700                // on enleve les cles par id indique et par prefixe
701                unset($this->end['i'][$info['i']], $this->end['p'][$info['p']]);
702                // ainsi que l'id aucazou du prefixe
703                if ($i) {
704                        unset($this->end['i'][$i['i']]);
705                }
706
707        }
708
709
710        /**
711         * Invalide un plugin (il est introuvable, ne correspond pas à notre version de SPIP...)
712         *
713         * @param array $info
714         *     Description du paquet concerné
715         */
716        public function invalider($info) {
717                $this->log("-> invalider $info[p]");
718                $this->remove($info); // suffisant ?
719                $this->invalides[$info['p']] = $info;
720                $this->annule_change($info);
721                unset($this->todo[$info['i']]);
722        }
723
724        /**
725         * Teste qu'un paquet (via son préfixe) est déclaré invalide
726         *
727         * @param string $p
728         *     Prefixe du paquet
729         * @return bool
730         *     Le paquet est t'il invalide ?
731         **/
732        public function sera_invalide($p) {
733                return isset($this->invalides[$p]) ? $this->invalides[$p] : false;
734        }
735
736        /**
737         * Teste qu'une librairie (via son nom) est déjà présente
738         *
739         * @param string $lib
740         *     Nom de la librairie
741         * @return bool
742         *     La librairie est-elle présente ?
743         **/
744        public function est_presente_lib($lib) {
745                static $libs = false;
746                if ($libs === false) {
747                        include_spip('inc/svp_outiller');
748                        $libs = svp_lister_librairies();
749                }
750
751                return isset($libs[$lib]) ? $libs[$lib] : false;
752        }
753
754
755        /**
756         * Ajoute les actions demandées au décideur
757         *
758         * Chaque action est analysée et elles sont redispatchées dans différents
759         * tableaux via les méthodes :
760         * - ask  : ce qui est demandé (ils y vont tous)
761         * - todo : ce qui est à faire (ils y vont tous aussi)
762         * - add  : les plugins activés,
763         * - off  : les plugins désactivés
764         *
765         * La fonction peut lever des erreurs sur les actions tel que :
766         * - Paquet demandé inconnu
767         * - Mise à jour introuvable
768         * - Paquet à désactiver mais qui n'est pas actif
769         *
770         * @param array $todo
771         *     Ce qui est demandé de faire
772         *     Tableau identifiant du paquet => type d'action (on, off, up...)
773         * @return bool
774         *     False en cas d'erreur, true sinon
775         */
776        public function actionner($todo = null) {
777                if (is_array($todo)) {
778                        foreach ($todo as $id => $t) {
779                                // plusieurs choses nous interessent... Sauf... le simple telechargement
780                                // et la suppression des fichiers (qui ne peuvent etre fait
781                                // que si le plugin n'est pas actif)
782                                $this->log("-- todo: $id/$t");
783
784                                switch ($t) {
785                                        case 'getlib':
786                                                break;
787                                        case 'on':
788                                        case 'geton':
789                                                // ajouter ce plugin dans la liste
790                                                if (!$this->sera_actif_id($id)) {
791                                                        $i = $this->infos_courtes_id($id);
792                                                        if ($i = $i['i'][$id]) {
793                                                                $this->log("--> $t : " . $i['p'] . ' en version : ' . $i['v']);
794
795                                                                // se mefier : on peut tenter d'activer
796                                                                // un plugin de meme prefixe qu'un autre deja actif
797                                                                // mais qui n'est pas de meme version ou de meme etat
798                                                                // par exemple un plugin obsolete ou un plugin au contraire plus a jour.
799                                                                // dans ce cas, on desactive l'ancien (sans desactiver les dependences)
800                                                                // et on active le nouveau.
801                                                                // Si une dependance ne suit pas, une erreur se produira du coup.
802                                                                if (isset($this->end['p'][$i['p']])) {
803                                                                        $old = $this->end['p'][$i['p']];
804                                                                        $this->log("-->> off : " . $old['p'] . ' en version : ' . $old['v']);
805                                                                        $this->ask($old, 'off');
806                                                                        $this->todo($old, 'off');
807                                                                        // désactive l'ancien plugin, mais pas les dépendances qui en dépendent
808                                                                        // car normalement, ça devrait suivre...
809                                                                        $this->off($old, false);
810
811                                                                }
812
813                                                                // pas de prefixe equivalent actif...
814                                                                $this->add($i);
815                                                                $this->ask($i, $i['local'] ? 'on' : 'geton');
816
817                                                        } else {
818                                                                // la c'est vraiment pas normal... Erreur plugin inexistant...
819                                                                // concurrence entre administrateurs ?
820                                                                $this->erreur($id, _T('svp:message_nok_plugin_inexistant', array('plugin' => $id)));
821                                                        }
822                                                }
823                                                break;
824                                        case 'up':
825                                        case 'upon':
826                                                // le plugin peut etre actif !
827                                                // ajouter ce plugin dans la liste et retirer l'ancien
828                                                $i = $this->infos_courtes_id($id);
829                                                if ($i = $i['i'][$id]) {
830                                                        $this->log("--> $t : " . $i['p'] . ' en version : ' . $i['v']);
831
832                                                        // new : plugin a installer
833                                                        if ($new = $this->chercher_plugin_recent($i['p'], $i['v'])) {
834                                                                $this->log("--> maj : " . $new['p'] . ' en version : ' . $new['v']);
835                                                                // ajouter seulement si on l'active !
836                                                                // ou si le plugin est actuellement actif
837                                                                if ($t == 'upon' or $this->sera_actif_id($id)) {
838                                                                        $this->remove($i);
839                                                                        $this->add($new);
840                                                                }
841                                                                $this->ask($i, $t);
842                                                        } else {
843                                                                if ($this->erreur_sur_maj_introuvable) {
844                                                                        // on n'a pas trouve la nouveaute !!!
845                                                                        $this->erreur($id, _T('svp:message_nok_maj_introuvable', array('plugin' => $i['n'], 'id' => $id)));
846                                                                }
847                                                        }
848                                                } else {
849                                                        // mauvais identifiant ?
850                                                        // on n'a pas trouve le plugin !!!
851                                                        $this->erreur($id, _T('svp:message_erreur_maj_inconnu', array('id' => $id)));
852                                                }
853                                                break;
854                                        case 'off':
855                                        case 'stop':
856                                                // retirer ce plugin
857                                                // (il l'est peut etre deja)
858                                                if ($info = $this->sera_actif_id($id)
859                                                        or $info_off = $this->sera_off_id($id)
860                                                        // un plugin en attente (desactive parce que sa dependance a disparu certainement par ftp)
861                                                        // peut etre desactive
862                                                        or $info = $this->est_attente_id($id)
863                                                ) {
864                                                        // annuler le signalement en "proposition" (due a une mise a 'off' recursive)
865                                                        // de cet arret de plugin, vu qu'on le demande reellement
866                                                        if (!$info) {
867                                                                $info = $info_off;
868                                                                $this->annule_change($info);
869                                                        }
870                                                        $this->log("--> $t : " . $info['p'] . ' en version : ' . denormaliser_version($info['v']));
871                                                        $this->ask($info, $t);
872                                                        $this->todo($info, $t);
873                                                        // désactive tous les plugins qui en dépendent aussi.
874                                                        $this->off($info, true);
875
876                                                } else {
877                                                        // pas normal... plugin deja inactif...
878                                                        // concurrence entre administrateurs ?
879                                                        $this->erreur($id, _T('svp:message_erreur_plugin_non_actif'));
880                                                }
881                                                break;
882                                        case 'null':
883                                        case 'get':
884                                        case 'kill':
885                                                if ($info = $this->infos_courtes_id($id)) {
886                                                        $this->log("--> $t : " . $info['i'][$id]['p'] . ' en version : ' . $info['i'][$id]['v']);
887                                                        $this->ask($info['i'][$id], $t);
888                                                } else {
889                                                        // pas normal... plugin inconnu... concurrence entre administrateurs ?
890                                                        $this->erreur($id, _T('svp:message_erreur_plugin_introuvable', array('plugin' => $id, 'action' => $t)));
891                                                }
892                                                break;
893                                }
894                        }
895                }
896
897                return $this->ok;
898        }
899
900
901        /**
902         * Initialise les listes de plugins pour le calcul de dépendances
903         *
904         * Les propriété $start et $end reçoivent la liste des plugins actifs
905         * $procure celle des plugins procurés par le Core
906         */
907        public function start() {
908                $this->start = $this->end = $this->liste_plugins_actifs();
909                $this->procure = $this->liste_plugins_procure();
910        }
911
912        /**
913         * Vérifier (et activer) les dépendances
914         *
915         * Pour chaque plugin qui sera actif, vérifie qu'il respecte
916         * ses dépendances.
917         *
918         * Si ce n'est pas le cas, le plugin n'est pas activé et le calcul
919         * de dépendances se refait sans lui. À un moment on a normalement
920         * rapidement une liste de plugins cohérents (au pire on ne boucle
921         * que 100 fois maximum - ce qui ne devrait jamais se produire).
922         *
923         * Des erreurs sont levées lorsqu'un plugin ne peut honorer son activation
924         * à cause d'un problème de dépendance. On peut les récupérer dans la
925         * propriété $err.
926         *
927         * @api
928         * @param array $todo
929         *     Ce qui est demandé de faire
930         *     Tableau identifiant du paquet => type d'action (on, off, up...)
931         * @return bool
932         *     False en cas d'erreur, true sinon
933         */
934        public function verifier_dependances($todo = null) {
935
936                $this->start();
937
938                // ajouter les actions
939                if (!$this->actionner($todo)) {
940                        $this->log("! Todo en echec !");
941                        $this->log($this->err);
942
943                        return false;
944                }
945
946                // doit on reverifier les dependances ?
947                // oui des qu'on modifie quelque chose...
948                // attention a ne pas boucler infiniment !
949
950                $supersticieux = 0;
951                do {
952                        $try_again = 0;
953                        $supersticieux++;
954
955                        // verifier chaque dependance de chaque plugin a activer
956                        foreach ($this->end['i'] as $info) {
957                                if (!$this->verifier_dependances_plugin($info)) {
958                                        $try_again = true;
959                                }
960                        }
961                        unset($id, $info);
962                        $this->log("--------> try_again: $try_again, supersticieux: $supersticieux");
963                } while ($try_again > 0 and $supersticieux < 100); # and !count($this->err)
964
965                $this->log("Fin !");
966                $this->log("Ok: " . $this->ok);
967
968                # $this->log($this->todo);
969
970                return $this->ok;
971        }
972
973
974        /**
975         * Pour une description de paquet donnée, vérifie sa validité.
976         *
977         * Teste la version de SPIP, les librairies nécessitées, ses dépendances
978         * (et tente de les trouver et ajouter si elles ne sont pas là)
979         *
980         * Lorsqu'une dépendance est activée, on entre en récursion
981         * dans cette fonction avec la description de la dépendance
982         *
983         * @param array $info
984         *     Description du paquet
985         * @param int $prof
986         *     Profondeur de récursion
987         * @return bool
988         *     false si erreur (dépendance non résolue, incompatibilité...), true sinon
989         **/
990        public function verifier_dependances_plugin($info, $prof = 0) {
991                $this->log("- [$prof] verifier dependances " . $info['p']);
992                $id = $info['i'];
993                $err = false; // variable receptionnant parfois des erreurs
994                $cache = array(); // cache des actions realisees dans ce tour
995
996                // 1
997                // tester la version de SPIP de notre paquet
998                // si on ne valide pas, on retourne une erreur !
999                // mais normalement, on ne devrait vraiment pas pouvoir tomber sur ce cas
1000                if (!svp_verifier_compatibilite_spip($info['compatibilite_spip'])) {
1001                        $this->invalider($info);
1002                        $this->erreur($id, _T('svp:message_incompatibilite_spip', array('plugin' => $info['n'])));
1003
1004                        return false;
1005                }
1006
1007
1008                // 2
1009                // ajouter les librairies necessaires a notre paquet
1010                if (is_array($info['dl']) and count($info['dl'])) {
1011                        foreach ($info['dl'] as $l) {
1012                                // $l = array('nom' => 'x', 'lien' => 'url')
1013                                $lib = $l['nom'];
1014                                $this->log("## Necessite la librairie : " . $lib);
1015
1016                                // on verifie sa presence OU le fait qu'on pourra la telecharger
1017                                if ($lib and !$this->est_presente_lib($lib)) {
1018                                        // peut on ecrire ?
1019                                        if (!is_writable(_DIR_LIB)) {
1020                                                $this->invalider($info);
1021                                                $this->erreur($id, _T('svp:message_erreur_ecriture_lib',
1022                                                        array('plugin' => $info['n'], 'lib_url' => $l['lien'], 'lib' => $lib)));
1023                                                $err = true;
1024                                        }
1025                                        // ajout, pour info
1026                                        // de la librairie dans la todo list
1027                                        else {
1028                                                $this->change(array(
1029                                                        'i' => md5(serialize($l)),
1030                                                        'p' => $lib,
1031                                                        'n' => $lib,
1032                                                        'v' => $l['lien'],
1033                                                ), 'getlib');
1034                                                $this->log("- La librairie $lib sera a télécharger");
1035                                        }
1036                                }
1037                        }
1038                        if ($err) {
1039                                return false;
1040                        }
1041                }
1042
1043                // 3
1044                // Trouver les dependences aux necessites
1045                // et les activer au besoin
1046                if (is_array($info['dn']) and count($info['dn'])) {
1047                        foreach ($info['dn'] as $n) {
1048
1049                                $p = $n['nom'];
1050                                $v = isset($n['compatibilite']) ? $n['compatibilite'] : '';
1051
1052                                if ($p == 'SPIP') {
1053                                        // c'est pas la que ça se fait !
1054                                        // ca ne devrait plus apparaitre comme dependence a un plugin.
1055                                } 
1056                                // le core procure le paquet que l'on demande !
1057                                elseif (
1058                                        array_key_exists($p, $this->procure)
1059                                        and plugin_version_compatible($v, $this->procure[$p])
1060                                ) {
1061                                        // rien a faire...
1062                                        $this->log("-- est procure par le core ($p)");
1063
1064                                } // pas d'autre alternative qu'un vrai paquet a activer
1065                                else {
1066                                        $this->log("-- verifier : $p");
1067                                        // nous sommes face a une dependance de plugin
1068                                        // on regarde s'il est present et a la bonne version
1069                                        // sinon on le cherche et on l'ajoute
1070                                        if ($ninfo = $this->sera_actif($p)
1071                                                and !$err = $this->en_erreur($ninfo['i'])
1072                                                and plugin_version_compatible($v, $ninfo['v'])
1073                                        ) {
1074                                                // il est deja actif ou a activer, et tout est ok
1075                                                $this->log('-- dep OK pour ' . $info['p'] . ' : ' . $p);
1076                                        } // il faut le trouver et demander a l'activer
1077                                        else {
1078
1079                                                // absent ou erreur ou pas compatible
1080                                                $etat = $err ? 'erreur' : ($ninfo ? 'conflit' : 'absent');
1081                                                // conflit signifie qu'il existe le prefixe actif, mais pas a la version demandee
1082                                                $this->log("Dependance " . $p . " a resoudre ! ($etat)");
1083
1084                                                switch ($etat) {
1085                                                        // commencons par le plus simple :
1086                                                        // en cas d'absence, on cherche ou est ce plugin !
1087                                                        case 'absent':
1088                                                                // on choisit par defaut le meilleur etat de plugin.
1089                                                                // de preference dans les plugins locaux, sinon en distant.
1090                                                                if (!$this->sera_off($p)
1091                                                                        and $new = $this->chercher_plugin_compatible($p, $v)
1092                                                                        and $this->verifier_dependances_plugin($new, ++$prof)
1093                                                                ) {
1094                                                                        // si le plugin existe localement et possede maj_version,
1095                                                                        // c'est que c'est peut etre une mise a jour + activation a faire
1096                                                                        // si le plugin
1097                                                                        // nouveau est local   => non
1098                                                                        // nouveau est distant => oui peut etre
1099                                                                        $cache[] = $new;
1100                                                                        $i = array();
1101                                                                        if (!$new['local']) {
1102                                                                                $i = $this->infos_courtes(array(
1103                                                                                        'pl.prefixe=' . sql_quote($new['p']),
1104                                                                                        'pa.maj_version=' . sql_quote($new['v'])
1105                                                                                ), true);
1106                                                                        }
1107                                                                        if ($i and isset($i['p'][$new['p']]) and count($i['p'][$new['p']])) {
1108                                                                                // c'est une mise a jour
1109                                                                                $vieux = $i['p'][$new['p']][0];
1110                                                                                $this->change($vieux, 'upon');
1111                                                                                $this->log("-- update+active : $p");
1112                                                                        } else {
1113                                                                                // tout nouveau tout beau
1114                                                                                $this->change($new, $new['local'] ? 'on' : 'geton');
1115                                                                                if ($new['local']) {
1116                                                                                        $this->log("-- nouveau present : $p");
1117                                                                                } else {
1118                                                                                        $this->log("-- nouveau distant : $p");
1119                                                                                }
1120                                                                        }
1121                                                                        $this->add($new);
1122                                                                } else {
1123                                                                        $this->log("-- !erreur : $p");
1124                                                                        // on ne trouve pas la dependance !
1125                                                                        $this->invalider($info);
1126                                                                        $this->erreur($id, $this->presenter_erreur_dependance($info, $p, $v));
1127                                                                }
1128                                                                unset($new, $vieux);
1129                                                                break;
1130
1131                                                        case 'erreur':
1132                                                                break;
1133
1134                                                        // present, mais conflit de version
1135                                                        // de deux choses l'une :
1136                                                        // soit on trouve un paquet meilleur...
1137                                                        // soit pas :)
1138                                                        case 'conflit':
1139                                                                $this->log("  conflit -> demande $v, present : " . $ninfo['v']);
1140                                                                if (!$this->sera_off($p)
1141                                                                        and $new = $this->chercher_plugin_compatible($p, $v)
1142                                                                        and $this->verifier_dependances_plugin($new, ++$prof)
1143                                                                ) {
1144                                                                        // on connait le nouveau...
1145                                                                        $cache[] = $new;
1146                                                                        $this->remove($ninfo);
1147                                                                        $this->add($new);
1148                                                                        $this->change($ninfo, 'up');
1149                                                                        $this->log("-- update : $p");
1150                                                                } else {
1151                                                                        $this->log("-- !erreur : $p");
1152                                                                        // on ne trouve pas la dependance !
1153                                                                        $this->invalider($info);
1154                                                                        $this->erreur($id, $this->presenter_erreur_dependance($info, $p, $v));
1155                                                                }
1156                                                                break;
1157                                                }
1158
1159                                        }
1160                                }
1161
1162                                if ($this->sera_invalide($info['p'])) {
1163                                        break;
1164                                }
1165                        }
1166                        unset($n, $v, $p, $ninfo, $present, $conflit, $erreur, $err);
1167
1168                        // si le plugin est devenu invalide...
1169                        // on invalide toutes les actions qu'on vient de faire !
1170                        if ($this->sera_invalide($info['p'])) {
1171                                $this->log("> Purge du cache");
1172                                foreach ($cache as $i) {
1173                                        $this->invalider($i);
1174                                }
1175
1176                                return false;
1177                        }
1178                }
1179
1180                return true;
1181        }
1182
1183        /**
1184         * Retourne le texte d'erreur adapté à une dépendance donnée
1185         */
1186        public function presenter_erreur_dependance($info, $dependance, $intervalle) {
1187                // prendre en compte les erreurs de dépendances à PHP
1188                // ou à une extension PHP avec des messages d'erreurs dédiés.
1189                $type = 'plugin';
1190                if ($dependance === 'PHP') {
1191                        $type = 'php';
1192                } elseif (strncmp($dependance, 'PHP:', 4) === 0) {
1193                        $type = 'extension_php';
1194                        list(,$dependance) = explode(':', $dependance, 2);
1195                }
1196
1197                if ($intervalle) {
1198                        $info_dependance = svp_afficher_intervalle($intervalle, $dependance);
1199                } else {
1200                        $info_dependance = $dependance;
1201                }
1202
1203                $err = _T('svp:message_dependance_' . $type, array(
1204                        'plugin' => $info['n'],
1205                        'dependance' => $info_dependance,
1206                ));
1207
1208                return $err;
1209        }
1210
1211        /**
1212         * Retourne un tableau des différentes actions qui seront faites
1213         *
1214         * @param string $quoi
1215         *     Type de demande
1216         *     - ask : les actions demandées
1217         *     - changes : les actions en plus par rapport à ce qui était demandé
1218         *     - todo : toutes les actions
1219         * @return array
1220         *     Liste des actions (joliement traduites et expliquées)
1221         **/
1222        public function presenter_actions($quoi) {
1223                $res = array();
1224                foreach ($this->$quoi as $id => $info) {
1225                        $trads = array(
1226                                'plugin' => $info['n'],
1227                                'version' => denormaliser_version($info['v']),
1228                        );
1229                        if (isset($info['maj'])) {
1230                                $trads['version_maj'] = denormaliser_version($info['maj']);
1231                        }
1232                        $res[] = _T('svp:message_action_' . $info['todo'], $trads);
1233                }
1234
1235                return $res;
1236        }
1237}
1238
1239
1240/**
1241 * Gère la partie vérifier des formulaires utilisant le Décideur
1242 *
1243 * @param array $a_actionner
1244 *     Tableau des actions par paquet (id_paquet => action)
1245 * @param array $erreurs
1246 *     Tableau d'erreurs de verifier (CVT)
1247 * @return bool
1248 *     true si tout va bien, false sinon (erreur pour trouver les dépendances, ...)
1249 **/
1250function svp_decider_verifier_actions_demandees($a_actionner, &$erreurs) {
1251        $decideur = new Decideur;
1252        $decideur->erreur_sur_maj_introuvable = false;
1253        $decideur->verifier_dependances($a_actionner);
1254
1255        if (!$decideur->ok) {
1256                $erreurs['decideur_erreurs'] = array();
1257                foreach ($decideur->err as $id => $errs) {
1258                        foreach ($errs as $err) {
1259                                $erreurs['decideur_erreurs'][] = $err;
1260                        }
1261                }
1262
1263                return false;
1264        }
1265
1266        // On construit la liste des libellés d'actions
1267        $actions = array();
1268        $actions['decideur_propositions'] = $decideur->presenter_actions('changes');
1269        $actions['decideur_demandes'] = $decideur->presenter_actions('ask');
1270        $actions['decideur_actions'] = $decideur->presenter_actions('todo');
1271        set_request('_libelles_actions', $actions);
1272
1273        // On construit la liste des actions pour la passer au formulaire en hidden
1274        $todo = array();
1275        foreach ($decideur->todo as $_todo) {
1276                $todo[$_todo['i']] = $_todo['todo'];
1277        }
1278        set_request('_todo', serialize($todo));
1279
1280        return true;
1281}
Note: See TracBrowser for help on using the repository browser.