You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

450 lines
15 KiB

<?php
namespace Spip\Cli\Command;
use ArrayObject;
use Spip\Cli\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Input\ArrayInput;
class PluginsGitTelecharger extends PluginsLister
{
public $depots_spip = [
'extensions', 'squelettes', 'themes',
];
// ici on met tous les plugins dérogatoires à la règle nom du repo Git = prefixe
// sous la forme: prefixe => nom_repo
public $remplacements_prefixes = [
'bootstrap' => 'bootstrap2',
'cextras' => 'champs_extras_core',
'ck' => 'couteau-kiss',
'cvtupload' => 'cvt-upload',
'iextras' => 'champs_extras_interface',
'inscriptionmotdepasse' => 'inscription_motdepasse',
'jquerymasonry' => 'jquery_masonry',
'lesscss' => 'less-css',
'notifavancees' => 'notifications_avancees',
'numero' => 'numerotation',
'spip_bonux' => 'spip-bonux',
'suivant_precedent' => 'criteres_suivant_precedent',
'zcore' => 'z-core',
'spipr_dist' => 'spipr-dist',
'auted' => 'autorisations_etendues',
'gisgeom' => 'gis_geometries',
'jqueryui' => 'jquery_ui',
'polyhier' => 'polyhierarchie',
];
public $chemin_cache_git = 'spip_depot_git';
public $chemin_conf_git = 'spip_depot_git.json';
protected function configure() {
$this
->setName('plugins:git:telecharger')
->setDescription('Télécharger (ou met à jour) un ou plusieurs plugins via git.')
->setHelp('<comment>Quelques exemples :</comment>
<info>spip plugins:git:telecharger facteur</info> : clone le plugin facteur si il n’est pas encore présent, le met à jour sinon.
<info>spip plugins:git:telecharger facteur,crayons,oembed</info> : clone ou met à jour les plugins facteur, crayons et oembed
<info>spip p:g:t https://mon-spip-modele.tld</info> : clone ou met à jour tous les plugins qui sont actifs sur le SPIP https://mon-spip-modele.tld
<info>spip p:g:t la_liste.txt</info> : clone ou met à jour tous les plugins listés dans le fichier la_liste.txt ou tmp/la_liste.txt
<info>spip p:g:t facteur,crayons la_liste.txt</info> : combinaison de sources pour la liste des plugins
<info>spip p:g:t facteur,crayons --activer</info> : clone les plugins facteur et crayons et les active (option --activer ou -a)
<info>spip p:g:t</info> : lancement sans liste de plugins pour mise à jour de tous les plugins actifs
<comment>Détails du fonctionnement:</comment>
<info>Fichiers txt pour fournir une liste de plugins</info>
Le fichier doit contenir une liste de préfixe de plugins séparés par une virgule. Par défaut il peut être placé à la racine du SPIP ou dans le répertoire tmp/
<info>Répertoire de cache</info>
Cette commande utilise un répertoire de cache sur le serveur pour stocker les plugins clonés afin d’éviter les téléchargements systématiques depuis le repo Git. Au démarrage les plugins demandés sont clonés dans le cache puis copiés dans le SPIP. Lors de l’appel d’un plugin existant dans le cache (par ex pour un autre SPIP sur le même serveur), le cache est mis à jour (git pull) puis copié dans le SPIP.
Par défaut le cache est stocké dans un répertoire <info>spip_depot_git</info> qui est recherché en remontant l’arborescence depuis le répertoire du SPIP où est lancé la commande.
La constante <info>_CHEMIN_CACHE_GIT</info> permet d’indiquer un répertoire différent (nom du dossier ou chemin absolu).
<info>Forges personnalisés</info>
Par défaut les plugins à cloner/mettre à jour sont recherchés dans les repos Git "officiels" de SPIP : spip-contrib-extensions, spip-contrib-squelettes et spip-contrib-themes.
Il est possible d’ajouter des repos en créant un fichier <info>spip_depot_git.json</info> placé dans l’arborescence "amont" du SPIP (en général à côté du dossier de cache). Dans ce fichier on indiquera les paramètres du repo sous forme d’un Json avec les infos suivantes (exemple pour un gitlab version 4 et le github de seenthis):
{
"forges": [
{
"forge": "gitlab",
"version": "v4",
"url": "https://git.ma-forge.tld/api/v4/projects?access_token=AB123cdef987",
"par_page": 100,
"remplacements_prefixes": {
"prefixe": "nom_repo",
"prefixe_2": "nom_repo_2"
}
},
{
"forge": "github",
"user": "seenthis",
"par_page": 100,
"remplacements_prefixes": {
"seenthissq": "seenthis_squelettes",
"seenthisoc": "seenthis_opencalais"
}
}
]
}
La constante <info>_CHEMIN_CONF_GIT</info> permet d’utiliser un fichier source différent (nom du fichier ou chemin absolu).
')
->addArgument('from', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Les plugins à cloner ou mettre à jour : liste et/ou fichier et/ou URL. Détermine automatiquement le depot.')
->addOption('activer', 'a', InputOption::VALUE_NONE, 'Activer les plugins téléchargés sans poser de question');
;
}
protected function execute(InputInterface $input, OutputInterface $output) {
$this->demarrerSpip();
$this->io->title("Téléchargement/Mise à jour des plugins");
// Récup de la conf avec éventuelle personnalisation des chemins
$this->chemin_cache_git = defined('_CHEMIN_CACHE_GIT') ? _CHEMIN_CACHE_GIT : $this->chemin_cache_git;
$this->chemin_conf_git = defined('_CHEMIN_CONF_GIT') ? _CHEMIN_CONF_GIT : $this->chemin_conf_git;
$this->chemin_dossier_cache_git = $this->getChemin($this->chemin_cache_git);
$this->conf = $this->getConfiguration($this->chemin_conf_git);
/*
* récupérer un array avec les préfixes des plugins-dist
*/
$dists = $this->getPluginsActifs(['php'=>false, 'dist'=>true]);
$this->plugins_dist = $this->arrayPlugins($dists, true);
/*
* Récuperation des préfixes passé dans l'argument from
* soit sous la forme de prefixes
* soit via une url d'un site SPIP
* soit sous forme d'une liste dans un fichier .txt
*
* Si from est vide c'est une mise à jour
* on récupère donc la liste des plugins actifs
*/
$this->plugins_a_telecharger = [];
$this->plugins_a_telecharger_via_url = [];
$from = $input->getArgument('from');
if (count($from)) {
foreach ($from as $quoi) {
if (preg_match(',^https?://,', $quoi)) {
$this->addPrefix($this->getPrefixesFromUrl($quoi));
} elseif (strrpos($quoi, '.txt') === (strlen($quoi) - 4)) {
$this->addPrefix($this->getPrefixesFromFile($quoi));
} else {
$this->addPrefix(explode(',',$quoi));
}
}
} else {
$this->addPrefix($this->arrayPlugins($this->getPluginsActifs(['php'=>false, 'dist'=>false, 'procure'=>false]), true));
}
/*
* On peut lancer le telechargement / copie des plugins
*/
if ($this->plugins_a_telecharger) {
$this->telecharger_git_plugins();
}
/**
* si option --activer enchainer avec l'activation des plugins téléchargés
**/
if ($input->getOption('activer')) {
$command = $this->getApplication()->find('plugins:activer');
$args = [
'from' => $this->plugins_a_telecharger,
'--yes' => true
];
$argsInput = new ArrayInput($args);
$command->run($argsInput, $output);
}
/**
* dans tous les cas on fait la maj BDD des plugins
**/
$command = $this->getApplication()->find('plugins:maj:bdd');
$args = [];
$argsInput = new ArrayInput($args);
$command->run($argsInput, $output);
}
protected function getChemin(String $search) :String {
// chemin absolu (nix/win)?
if (strpos($search, '/') === 0 or strpos($search, ':') === 1) {
return (file_exists($search) ? $search : false);
}
// sinon remonter l'arbo à partir du dossier en cours
$dir = getcwd();
$dir_ini = $dir;
while (!file_exists($search)) {
chdir('../');
if (getcwd() === $dir) {
$dir = '';
break;
}
$dir = getcwd();
}
chdir($dir_ini);
return $dir.'/'.$search;
}
public function telecharger_git_plugins(){
$chemin_dossier_cache_git = false;
if ($this->chemin_dossier_cache_git) {
$chemin_dossier_cache_git = $this->chemin_dossier_cache_git;
}
$plugins = $this->plugins_a_telecharger;
if ($plugins) {
foreach ($plugins as $prefix) {
// faire les remplacements des préfixes pour les plugins dérogatoires à la règle nom du repo Git = préfixe
if (array_key_exists($prefix, $this->remplacements_prefixes)) {
$prefix = $this->remplacements_prefixes[$prefix];
}
$this->io->section("Plugin : $prefix");
// on teste si le plugin est déja présent dans le dossier plugins/
if (file_exists(_DIR_PLUGINS . $prefix)) {
$this->io->text("Déjà présent -> On pull");
$r = getcwd();
chdir(_DIR_PLUGINS . $prefix);
passthru("git pull");
chdir($r);
}
// sinon, on regarde si le plugin est present dans le cache
// dans ce cas, on pull et on le copie
else if (
$chemin_dossier_cache_git and
file_exists($chemin_dossier_cache_git .'/'. $prefix)
) {
$this->io->text("Présent dans le cache -> On pull puis on copie");
$r = getcwd();
chdir($chemin_dossier_cache_git .'/'. $prefix);
passthru("git pull");
chdir($r);
$source = $chemin_dossier_cache_git .'/'. $prefix;
$target = _DIR_PLUGINS;
passthru("cp -r $source $target");
}
// on clone puis on copie
else {
$url = $this->chercher_url($prefix);
if ($url) {
// si on a un dossier de cache gogogo
// on le clone dans le cache puis le copie
if ( $chemin_dossier_cache_git) {
$this->io->text("Pas dans le cache -> On clone puis on copie");
$r = getcwd();
chdir($chemin_dossier_cache_git);
passthru("git clone $url $prefix");
chdir($r);
$source = $chemin_dossier_cache_git .'/'. $prefix;
$target = _DIR_PLUGINS;
passthru("cp -r $source $target");
}
// sinon, on clone simplement
else {
$this->io->text("Pas de dossier de cache -> On clone");
$r = getcwd();
chdir(_DIR_PLUGINS);
passthru("git clone $url");
chdir($r);
}
}else {
$this->io->caution("Le depot de : $prefix n'a pas été trouvé");
}
}
}
}
}
public function chercher_url_forge($forge, $prefix){
$url = '';
$fct = "chercher_url_".$forge->forge;
if (method_exists($this, $fct)){
$url = $this->$fct($prefix, $forge, NULL);
}
return $url;
}
public function chercher_url($prefix){
static $recup_json_spip;
$urlClone = '';
// 1. dans les depots SPIP
if ($recup_json_spip === null) {
$recup_json_spip = [];
foreach ($this->depots_spip as $depot) {
$url = "https://git.spip.net/api/v1/orgs/spip-contrib-$depot/repos?limit=50&page=";
$json = $this->recuperation_json_forge($url);
$recup_json_spip = array_merge($recup_json_spip, $json);
}
// scanner aussi dans les plugins-dist pour les plugins sortis de la dist en SPIP 4
foreach ($this->depots_spip as $depot) {
$url = "https://git.spip.net/api/v1/orgs/spip/repos?limit=50&page=";
$json = $this->recuperation_json_forge($url);
$recup_json_spip = array_merge($recup_json_spip, $json);
}
}
if ($recup_json_spip) {
foreach ($recup_json_spip as $plugin) {
if ($plugin->name === $prefix) {
$urlClone = $plugin->clone_url;
break;
}
}
}
if ($urlClone) {
return $urlClone;
}
// 2. Dans les autres forges
if ($this->conf and
isset($this->conf->forges)
) {
foreach ($this->conf->forges as $forge) {
// effectuer les remplacements de préfixes spécifiques de cette forge
if (isset($forge->remplacements_prefixes)) {
$T = (array) $forge->remplacements_prefixes;
if (array_key_exists($prefix, $T)) {
$prefix = strtolower($T[$prefix]);
}
}
$urlClone = $this->chercher_url_forge($forge, $prefix);
// s'arrêter dès qu'on a trouvé un repo OK pour ce préfixe
if ($urlClone != '') {
return $urlClone;
}
}
}
return $urlClone;
}
protected function getConfiguration($file) {
$url_file = $this->getChemin($file);
if (!file_exists($url_file)) {
return [];
}
$conf = file_get_contents($url_file);
$conf = json_decode($conf);
return $conf;
}
protected function addPrefix(array $prefixes) {
$prefixes = array_map('trim', $prefixes);
$prefixes = array_map('strtolower', $prefixes);
$plugins_a_telecharger = array_unique(array_merge($this->plugins_a_telecharger, $prefixes));
// virer les plugins-dist
$plugins_a_telecharger = array_diff($plugins_a_telecharger, $this->plugins_dist);
$this->plugins_a_telecharger = $plugins_a_telecharger;
}
public function recuperation_json_forge(String $url) :Array {
include_spip('inc/distant');
$json_ok = true;
$json_total = [];
$page = 1;
while ($json_ok){
$gitUrl = $url . $page;
$Tjson = recuperer_url($gitUrl);
$json = json_decode($Tjson['page']);
if ($json) {
$json_total = array_merge($json_total, $json);
$page++;
} else {
$json_ok = false;
break;
}
}
return $json_total;
}
/*
* recherche l'url d'un plugin à cloner pour une forge gitlab
*
* @parma $prefix : le prefix du plugin que l'on cherche
* @param $v : le numéro de version de l'api
* @param $json: le json à parser
*
* @return l'url à cloner
*/
public function chercher_url_gitlab($prefix, $forge, $json_total=NULL){
$urlClone = '';
static $json_total;
$par_page = isset($forge->par_page) ? $forge->par_page : 50;
$url = $forge->url . "&simple=true&per_page=$par_page";
$version = $forge->version;
if ($version === 'v4') {
// Recup du json
if ($json_total === null) {
$url = $url . "&page=";
$json_total = [];
$json_total = $this->recuperation_json_forge($url);
}
//print_r(count($json_total));
if ($json_total) {
foreach ($json_total as $plugin) {
if (strtolower($plugin->path) === $prefix) {
$urlClone = $plugin->http_url_to_repo;
break;
}
}
}
}
return $urlClone;
}
/*
* recherche l'url d'un plugin à cloner pour github
*
* @parma $prefix : le prefix du plugin que l'on cherche
* @param $user : le compte Github
* @param $json: le json à parser
*
* @return l'url à cloner
*/
public function chercher_url_github($prefix, $forge, $json_total=NULL){
$urlClone = '';
//static $json_total;
//$json_total = NULL;
$par_page = isset($forge->par_page) ? $forge->par_page : 50;
if (!isset($forge->user)) {
return $urlClone;
}
$user = $forge->user;
$url = "https://api.github.com/users/".$user."/repos?per_page=$par_page";
// Recup du json
if ($json_total === null) {
$url = $url . "&page=";
$json_total = [];
$json_total = $this->recuperation_json_forge($url);
}
//print_r(count($json_total));
if ($json_total) {
foreach ($json_total as $plugin) {
if (strtolower($plugin->name) === $prefix) {
$urlClone = $plugin->clone_url;
break;
}
}
}
return $urlClone;
}
}