/**
 * Calcule une liste des paquets en fonctions de critères de recherche
 *
 * Cette liste :
 * - est sans doublons, ie on ne garde que la version la plus récente
 * - correspond aux critères
 * - est compatible avec la version SPIP installée sur le site
 * - ne liste pas ceux étant déjà installés (ces paquets peuvent toutefois être affichés)
 * - est triée par nom ou score
 *
 * @uses  liste_des_champs()
 * @uses  recherche_en_base()
 *
 * @param string $phrase
 *     Texte de la recherche
 * @param string $categorie
 *     Type de catégorie de plugin (auteur, date...)
 * @param string $etat
 *     État de plugin (stable, test...)
 * @param string|int $depot
 *     Identifiant de dépot
 * @param string $version_spip
 *     Version de SPIP dont le paquet doit être compatible
 * @param array $exclusions
 *     Liste d'identifiants de plugin à ne pas intégrer dans la liste
 * @param bool $afficher_exclusions
 *     Afficher aussi les paquets déjà installés (true)
 *     ou ceux qui ne le sont pas (false) ?
 * @param bool $doublon
 *     Afficher toutes les versions de paquet (true)
 *     ou seulement la plus récente (false) ?
 * @param string $tri
 *     Ordre du tri : nom | score
 *
 * @return array
 *     Tableau classé par pertinence de résultat
 *     - 'prefixe' => tableau de description du paquet (si pas de doublons demandé)
 *     - n => tableau de descriptions du paquet (si doublons autorisés)
 **/
function svp_rechercher_plugins_spip($phrase, $categorie, $etat, $depot, $version_spip = '', $exclusions = array(), $afficher_exclusions = false, $doublon = false, $tri = 'nom')
{
    include_spip('inc/rechercher');
    $plugins = array();
    $scores = array();
    $ids_paquets = array();
    // On prepare l'utilisation de la recherche en base SPIP en la limitant aux tables spip_plugins
    // et spip_paquets  si elle n'est pas vide
    if ($phrase) {
        $liste = liste_des_champs();
        $tables = array('plugin' => $liste['plugin']);
        $options = array('jointures' => true, 'score' => true);
        // On cherche dans tous les enregistrements de ces tables des correspondances les plugins qui
        // correspondent a la phrase recherchee
        // -- On obtient une liste d'id de plugins et d'id de paquets
        $resultats = array('plugin' => array(), 'paquet' => array());
        $resultats = recherche_en_base($phrase, $tables, $options);
        // -- On prepare le tableau des scores avec les paquets trouves par la recherche
        if ($resultats) {
            // -- On convertit les id de plugins en id de paquets
            $ids = array();
            if (isset($resultats['plugin']) and $resultats['plugin']) {
                $ids_plugin = array_keys($resultats['plugin']);
                $where[] = sql_in('id_plugin', $ids_plugin);
                $ids = sql_allfetsel('id_paquet, id_plugin', 'spip_paquets', $where);
            }
            // -- On prepare les listes des id de paquet et des scores de ces memes paquets
            if (isset($resultats['paquet']) and $resultats['paquet']) {
                $ids_paquets = array_keys($resultats['paquet']);
                foreach ($resultats['paquet'] as $_id => $_score) {
                    $scores[$_id] = intval($resultats['paquet'][$_id]['score']);
                }
            }
            // -- On merge les deux tableaux de paquets sans doublon en mettant a jour un tableau des scores
            foreach ($ids as $_ids) {
                $id_paquet = intval($_ids['id_paquet']);
                $id_plugin = intval($_ids['id_plugin']);
                if (array_search($id_paquet, $ids_paquets) === false) {
                    $ids_paquets[] = $id_paquet;
                    $scores[$id_paquet] = intval($resultats['plugin'][$id_plugin]['score']);
                } else {
                    $scores[$id_paquet] = intval($resultats['paquet'][$id_paquet]['score']) + intval($resultats['plugin'][$id_plugin]['score']);
                }
            }
        }
    } else {
        if ($ids_paquets = sql_allfetsel('id_paquet', 'spip_paquets')) {
            $ids_paquets = array_map('reset', $ids_paquets);
            foreach ($ids_paquets as $_id) {
                $scores[$_id] = 0;
            }
        }
    }
    // Maintenant, on continue la recherche en appliquant, sur la liste des id de paquets,
    // les filtres complementaires : categorie, etat, exclusions et compatibilite spip
    // si on a bien trouve des resultats precedemment ou si aucune phrase n'a ete saisie
    // -- Preparation de la requete
    if ($ids_paquets) {
        $from = array('spip_plugins AS t1', 'spip_paquets AS t2', 'spip_depots AS t3');
        $select = array('t1.nom AS nom', 't1.slogan AS slogan', 't1.prefixe AS prefixe', 't1.id_plugin AS id_plugin', 't2.id_paquet AS id_paquet', 't2.description AS description', 't2.compatibilite_spip AS compatibilite_spip', 't2.lien_doc AS lien_doc', 't2.auteur AS auteur', 't2.licence AS licence', 't2.etat AS etat', 't2.logo AS logo', 't2.version AS version', 't2.nom_archive AS nom_archive', 't3.url_archives AS url_archives');
        $where = array('t1.id_plugin=t2.id_plugin', 't2.id_depot=t3.id_depot');
        if ($ids_paquets) {
            $where[] = sql_in('t2.id_paquet', $ids_paquets);
        }
        if ($categorie and $categorie != 'toute_categorie') {
            $where[] = 't1.categorie=' . sql_quote($categorie);
        }
        if ($etat and $etat != 'tout_etat') {
            $where[] = 't2.etat=' . sql_quote($etat);
        }
        if ($depot and $depot != 'tout_depot') {
            $where[] = 't2.id_depot=' . sql_quote($depot);
        }
        if ($exclusions and !$afficher_exclusions) {
            $where[] = sql_in('t2.id_plugin', $exclusions, 'NOT');
        }
        if ($resultats = sql_select($select, $from, $where)) {
            while ($paquets = sql_fetch($resultats)) {
                $prefixe = $paquets['prefixe'];
                $version = $paquets['version'];
                $nom = extraire_multi($paquets['nom']);
                $slogan = extraire_multi($paquets['slogan']);
                $description = extraire_multi($paquets['description']);
                if (svp_verifier_compatibilite_spip($paquets['compatibilite_spip'], $version_spip)) {
                    // Le paquet remplit tous les criteres, on peut le selectionner
                    // -- on utilise uniquement la langue du site
                    $paquets['nom'] = $nom;
                    $paquets['slogan'] = $slogan;
                    $paquets['description'] = $description;
                    // -- on ajoute le score si on a bien saisi une phrase
                    if ($phrase) {
                        $paquets['score'] = $scores[intval($paquets['id_paquet'])];
                    } else {
                        $paquets['score'] = 0;
                    }
                    // -- on construit l'url de l'archive
                    $paquets['url_archive'] = $paquets['url_archives'] . '/' . $paquets['nom_archive'];
                    // -- on gere les exclusions si elle doivent etre affichees
                    if ($afficher_exclusions and in_array($paquets['id_plugin'], $exclusions)) {
                        $paquets['installe'] = true;
                    } else {
                        $paquets['installe'] = false;
                    }
                    // -- On traite les doublons (meme plugin, versions differentes)
                    if ($doublon) {
                        $plugins[] = $paquets;
                    } else {
                        // ajout
                        // - si pas encore trouve
                        // - ou si sa version est inferieure (on garde que la derniere version)
                        if (!isset($plugins[$prefixe]) or !$plugins[$prefixe] or $plugins[$prefixe] and spip_version_compare($plugins[$prefixe]['version'], $version, '<')) {
                            $plugins[$prefixe] = $paquets;
                        }
                    }
                }
            }
        }
        // On trie le tableau par score décroissant ou nom croissant
        $fonction = 'svp_trier_par_' . $tri;
        if ($doublon) {
            usort($plugins, $fonction);
        } else {
            uasort($plugins, $fonction);
        }
    }
    return $plugins;
}
 /**
  * Pour une description de paquet donnée, vérifie sa validité.
  *
  * Teste la version de SPIP, les librairies nécessitées, ses dépendances
  * (et tente de les trouver et ajouter si elles ne sont pas là)
  *
  * Lorsqu'une dépendance est activée, on entre en récursion
  * dans cette fonction avec la description de la dépendance
  *
  * @param array $info
  *     Description du paquet
  * @param int $prof
  *     Profondeur de récursion
  * @return bool
  *     false si erreur (dépendance non résolue, incompatibilité...), true sinon
  **/
 public function verifier_dependances_plugin($info, $prof = 0)
 {
     $this->log("- [{$prof}] verifier dependances " . $info['p']);
     $id = $info['i'];
     $err = false;
     // variable receptionnant parfois des erreurs
     $cache = array();
     // cache des actions realisees dans ce tour
     // 1
     // tester la version de SPIP de notre paquet
     // si on ne valide pas, on retourne une erreur !
     // mais normalement, on ne devrait vraiment pas pouvoir tomber sur ce cas
     if (!svp_verifier_compatibilite_spip($info['compatibilite_spip'])) {
         $this->invalider($info);
         $this->erreur($id, _T('svp:message_incompatibilite_spip', array('plugin' => $info['n'])));
         return false;
     }
     // 2
     // ajouter les librairies necessaires a notre paquet
     if (is_array($info['dl']) and count($info['dl'])) {
         foreach ($info['dl'] as $l) {
             // $l = array('nom' => 'x', 'lien' => 'url')
             $lib = $l['nom'];
             $this->log("## Necessite la librairie : " . $lib);
             // on verifie sa presence OU le fait qu'on pourra la telecharger
             if ($lib and !$this->est_presente_lib($lib)) {
                 // peut on ecrire ?
                 if (!is_writable(_DIR_LIB)) {
                     $this->invalider($info);
                     $this->erreur($id, _T('svp:message_erreur_ecriture_lib', array('plugin' => $info['n'], 'lib_url' => $l['lien'], 'lib' => $lib)));
                     $err = true;
                 } else {
                     $this->change(array('i' => md5(serialize($l)), 'p' => $lib, 'n' => $lib, 'v' => $l['lien']), 'getlib');
                     $this->log("- La librairie {$lib} sera a télécharger");
                 }
             }
         }
         if ($err) {
             return false;
         }
     }
     // 3
     // Trouver les dependences aux necessites
     // et les activer au besoin
     if (is_array($info['dn']) and count($info['dn'])) {
         foreach ($info['dn'] as $n) {
             $p = $n['nom'];
             $v = $n['compatibilite'];
             if ($p == 'SPIP') {
                 // c'est pas la que ça se fait !
                 // ca ne devrait plus apparaitre comme dependence a un plugin.
             } elseif (array_key_exists($p, $this->procure) and plugin_version_compatible($v, $this->procure[$p], 'spip')) {
                 // rien a faire...
                 $this->log("-- est procure par le core ({$p})");
             } else {
                 $this->log("-- verifier : {$p}");
                 // nous sommes face a une dependance de plugin
                 // on regarde s'il est present et a la bonne version
                 // sinon on le cherche et on l'ajoute
                 if ($ninfo = $this->sera_actif($p) and !($err = $this->en_erreur($ninfo['i'])) and plugin_version_compatible($v, $ninfo['v'])) {
                     // il est deja actif ou a activer, et tout est ok
                     $this->log('-- dep OK pour ' . $info['p'] . ' : ' . $p);
                 } else {
                     // absent ou erreur ou pas compatible
                     $etat = $err ? 'erreur' : ($ninfo ? 'conflit' : 'absent');
                     // conflit signifie qu'il existe le prefixe actif, mais pas a la version demandee
                     $this->log("Dependance " . $p . " a resoudre ! ({$etat})");
                     switch ($etat) {
                         // commencons par le plus simple :
                         // en cas d'absence, on cherche ou est ce plugin !
                         case 'absent':
                             // on choisit par defaut le meilleur etat de plugin.
                             // de preference dans les plugins locaux, sinon en distant.
                             if (!$this->sera_off($p) and $new = $this->chercher_plugin_compatible($p, $v) and $this->verifier_dependances_plugin($new, ++$prof)) {
                                 // si le plugin existe localement et possede maj_version,
                                 // c'est que c'est peut etre une mise a jour + activation a faire
                                 // si le plugin
                                 // nouveau est local   => non
                                 // nouveau est distant => oui peut etre
                                 $cache[] = $new;
                                 $i = array();
                                 if (!$new['local']) {
                                     $i = $this->infos_courtes(array('pl.prefixe=' . sql_quote($new['p']), 'pa.maj_version=' . sql_quote($new['v'])), true);
                                 }
                                 if ($i and isset($i['p'][$new['p']]) and count($i['p'][$new['p']])) {
                                     // c'est une mise a jour
                                     $vieux = $i['p'][$new['p']][0];
                                     $this->change($vieux, 'upon');
                                     $this->log("-- update+active : {$p}");
                                 } else {
                                     // tout nouveau tout beau
                                     $this->change($new, $new['local'] ? 'on' : 'geton');
                                     if ($new['local']) {
                                         $this->log("-- nouveau present : {$p}");
                                     } else {
                                         $this->log("-- nouveau distant : {$p}");
                                     }
                                 }
                                 $this->add($new);
                             } else {
                                 $this->log("-- !erreur : {$p}");
                                 // on ne trouve pas la dependance !
                                 $this->invalider($info);
                                 $this->erreur($id, $v ? _T('svp:message_dependance_plugin_version', array('plugin' => $info['n'], 'dependance' => $p, 'version' => $v)) : _T('svp:message_dependance_plugin', array('plugin' => $info['n'], 'dependance' => $p)));
                             }
                             unset($new, $vieux);
                             break;
                         case 'erreur':
                             break;
                             // present, mais conflit de version
                             // de deux choses l'une :
                             // soit on trouve un paquet meilleur...
                             // soit pas :)
                         // present, mais conflit de version
                         // de deux choses l'une :
                         // soit on trouve un paquet meilleur...
                         // soit pas :)
                         case 'conflit':
                             $this->log("  conflit -> demande {$v}, present : " . $ninfo['v']);
                             if (!$this->sera_off($p) and $new = $this->chercher_plugin_compatible($p, $v) and $this->verifier_dependances_plugin($new, ++$prof)) {
                                 // on connait le nouveau...
                                 $cache[] = $new;
                                 $this->remove($ninfo);
                                 $this->add($new);
                                 $this->change($ninfo, 'up');
                                 $this->log("-- update : {$p}");
                             } else {
                                 $this->log("-- !erreur : {$p}");
                                 // on ne trouve pas la dependance !
                                 $this->invalider($info);
                                 $this->erreur($id, $v ? _T('svp:message_dependance_plugin_version', array('plugin' => $info['n'], 'dependance' => $p, 'version' => $v)) : _T('svp:message_dependance_plugin', array('plugin' => $info['n'], 'dependance' => $p)));
                             }
                             break;
                     }
                 }
             }
             if ($this->sera_invalide($info['p'])) {
                 break;
             }
         }
         unset($n, $v, $p, $ninfo, $present, $conflit, $erreur, $err);
         // si le plugin est devenu invalide...
         // on invalide toutes les actions qu'on vient de faire !
         if ($this->sera_invalide($info['p'])) {
             $this->log("> Purge du cache");
             foreach ($cache as $i) {
                 $this->invalider($i);
             }
             return false;
         }
     }
     return true;
 }