Merge pull request #3091 from aynsix/PHRAS-2507_Enhance_addplugin_downloadplugin

PHRAS-2507 #comment enhance addplugin downloadplugin
This commit is contained in:
Nicolas Maillat
2019-07-02 15:34:57 +02:00
committed by GitHub
6 changed files with 270 additions and 35 deletions

View File

@@ -24,6 +24,7 @@ use Alchemy\Phrasea\Command\Plugin\AddPlugin;
use Alchemy\Phrasea\Command\Plugin\RemovePlugin; use Alchemy\Phrasea\Command\Plugin\RemovePlugin;
use Alchemy\Phrasea\Command\Plugin\EnablePlugin; use Alchemy\Phrasea\Command\Plugin\EnablePlugin;
use Alchemy\Phrasea\Command\Plugin\DisablePlugin; use Alchemy\Phrasea\Command\Plugin\DisablePlugin;
use Alchemy\Phrasea\Command\Plugin\DownloadPlugin;
use Alchemy\Phrasea\CLI; use Alchemy\Phrasea\CLI;
use Alchemy\Phrasea\Command\Setup\CheckEnvironment; use Alchemy\Phrasea\Command\Setup\CheckEnvironment;
use Alchemy\Phrasea\Core\CLIProvider\DoctrineMigrationServiceProvider; use Alchemy\Phrasea\Core\CLIProvider\DoctrineMigrationServiceProvider;
@@ -50,7 +51,7 @@ $app = new CLI("
This program comes with ABSOLUTELY NO WARRANTY. This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it This is free software, and you are welcome to redistribute it
under certain conditions; type `about:license' for details.\n\n" under certain conditions; type `about:license' for details.\n\n"
. ' SETUP', $version->getName() . ' ' . $version->getNumber()); . ' SETUP', $version->getName() . ' ' . $version->getNumber());
$app->register(new DoctrineMigrationServiceProvider()); $app->register(new DoctrineMigrationServiceProvider());
@@ -70,6 +71,7 @@ if ($configurationTester->isInstalled()) {
} }
$app->command(new AddPlugin()); $app->command(new AddPlugin());
$app->command(new DownloadPlugin());
$app->command(new ListPlugin()); $app->command(new ListPlugin());
$app->command(new RemovePlugin()); $app->command(new RemovePlugin());
$app->command(new PluginsReset()); $app->command(new PluginsReset());

View File

@@ -120,7 +120,8 @@
"google/recaptcha": "^1.1", "google/recaptcha": "^1.1",
"facebook/graph-sdk": "^5.6", "facebook/graph-sdk": "^5.6",
"box/spout": "^2.7", "box/spout": "^2.7",
"paragonie/random-lib": "^2.0" "paragonie/random-lib": "^2.0",
"czproject/git-php": "^3.17"
}, },
"require-dev": { "require-dev": {
"mikey179/vfsstream": "~1.5", "mikey179/vfsstream": "~1.5",

48
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "a9c7fed873d5bfe962c81d543b33330f", "content-hash": "64830cb4d53b32b47e02d4a19df9cef2",
"packages": [ "packages": [
{ {
"name": "alchemy-fr/tcpdf-clone", "name": "alchemy-fr/tcpdf-clone",
@@ -1156,6 +1156,48 @@
], ],
"time": "2016-08-09T20:10:17+00:00" "time": "2016-08-09T20:10:17+00:00"
}, },
{
"name": "czproject/git-php",
"version": "v3.17.0",
"source": {
"type": "git",
"url": "https://github.com/czproject/git-php.git",
"reference": "a7b911b81a2fe626f748a4ac8955353c5777bc6c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/czproject/git-php/zipball/a7b911b81a2fe626f748a4ac8955353c5777bc6c",
"reference": "a7b911b81a2fe626f748a4ac8955353c5777bc6c",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"nette/tester": "^1.1"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Jan Pecha",
"email": "janpecha@email.cz"
}
],
"description": "Library for work with Git repository in PHP.",
"keywords": [
"git"
],
"time": "2019-02-09T13:11:36+00:00"
},
{ {
"name": "dailymotion/sdk", "name": "dailymotion/sdk",
"version": "1.6.5", "version": "1.6.5",
@@ -7747,12 +7789,12 @@
"version": "v1.6.4", "version": "v1.6.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/bovigo/vfsStream.git", "url": "https://github.com/mikey179/vfsStream.git",
"reference": "0247f57b2245e8ad2e689d7cee754b45fbabd592" "reference": "0247f57b2245e8ad2e689d7cee754b45fbabd592"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/bovigo/vfsStream/zipball/0247f57b2245e8ad2e689d7cee754b45fbabd592", "url": "https://api.github.com/repos/mikey179/vfsStream/zipball/0247f57b2245e8ad2e689d7cee754b45fbabd592",
"reference": "0247f57b2245e8ad2e689d7cee754b45fbabd592", "reference": "0247f57b2245e8ad2e689d7cee754b45fbabd592",
"shasum": "" "shasum": ""
}, },

View File

@@ -51,4 +51,41 @@ abstract class AbstractPluginCommand extends Command
$this->container['plugins.autoloader-generator']->write($manifests); $this->container['plugins.autoloader-generator']->write($manifests);
$output->writeln(" <comment>OK</comment>"); $output->writeln(" <comment>OK</comment>");
} }
protected function doInstallPlugin($source, InputInterface $input, OutputInterface $output)
{
$temporaryDir = $this->container['temporary-filesystem']->createTemporaryDirectory();
$output->write("Importing <info>$source</info>...");
$this->container['plugins.importer']->import($source, $temporaryDir);
$output->writeln(" <comment>OK</comment>");
$output->write("Validating plugin...");
$manifest = $this->container['plugins.plugins-validator']->validatePlugin($temporaryDir);
$output->writeln(" <comment>OK</comment> found <info>".$manifest->getName()."</info>");
$targetDir = $this->container['plugin.path'] . DIRECTORY_SEPARATOR . $manifest->getName();
$output->write("Setting up composer...");
$this->container['plugins.composer-installer']->install($temporaryDir);
$output->writeln(" <comment>OK</comment>");
$output->write("Installing plugin <info>".$manifest->getName()."</info>...");
$this->container['filesystem']->mirror($temporaryDir, $targetDir);
$output->writeln(" <comment>OK</comment>");
$output->write("Copying public files <info>".$manifest->getName()."</info>...");
$this->container['plugins.assets-manager']->update($manifest);
$output->writeln(" <comment>OK</comment>");
$output->write("Removing temporary directory...");
$this->container['filesystem']->remove($temporaryDir);
$output->writeln(" <comment>OK</comment>");
$output->write("Activating plugin...");
$this->container['conf']->set(['plugins', $manifest->getName(), 'enabled'], true);
$output->writeln(" <comment>OK</comment>");
$this->updateConfigFiles($input, $output);
}
} }

View File

@@ -14,6 +14,7 @@ namespace Alchemy\Phrasea\Command\Plugin;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\ArrayInput;
class AddPlugin extends AbstractPluginCommand class AddPlugin extends AbstractPluginCommand
{ {
@@ -29,41 +30,36 @@ class AddPlugin extends AbstractPluginCommand
protected function doExecutePluginAction(InputInterface $input, OutputInterface $output) protected function doExecutePluginAction(InputInterface $input, OutputInterface $output)
{ {
$source = $input->getArgument('source'); $source = $input->getArgument('source');
$shouldDownload = $this->shouldDownloadPlugin($source);
$temporaryDir = $this->container['temporary-filesystem']->createTemporaryDirectory(); if ($shouldDownload){
$command = $this->getApplication()->find('plugins:download');
$arguments = [
'command' => 'plugins:download',
'source' => $source,
'shouldInstallPlugin' => true
];
$output->write("Importing <info>$source</info>..."); $downloadInput = new ArrayInput($arguments);
$this->container['plugins.importer']->import($source, $temporaryDir); $command->run($downloadInput, $output);
$output->writeln(" <comment>OK</comment>");
$output->write("Validating plugin..."); } else {
$manifest = $this->container['plugins.plugins-validator']->validatePlugin($temporaryDir);
$output->writeln(" <comment>OK</comment> found <info>".$manifest->getName()."</info>");
$targetDir = $this->container['plugin.path'] . DIRECTORY_SEPARATOR . $manifest->getName(); $this->doInstallPlugin($source, $input, $output);
}
$output->write("Setting up composer...");
$this->container['plugins.composer-installer']->install($temporaryDir);
$output->writeln(" <comment>OK</comment>");
$output->write("Installing plugin <info>".$manifest->getName()."</info>...");
$this->container['filesystem']->mirror($temporaryDir, $targetDir);
$output->writeln(" <comment>OK</comment>");
$output->write("Copying public files <info>".$manifest->getName()."</info>...");
$this->container['plugins.assets-manager']->update($manifest);
$output->writeln(" <comment>OK</comment>");
$output->write("Removing temporary directory...");
$this->container['filesystem']->remove($temporaryDir);
$output->writeln(" <comment>OK</comment>");
$output->write("Activating plugin...");
$this->container['conf']->set(['plugins', $manifest->getName(), 'enabled'], true);
$output->writeln(" <comment>OK</comment>");
$this->updateConfigFiles($input, $output);
return 0; return 0;
} }
protected function shouldDownloadPlugin($source)
{
$allowedScheme = array('https','ssh');
$scheme = parse_url($source, PHP_URL_SCHEME);
if (in_array($scheme, $allowedScheme)){
return true;
} else{
return false;
}
}
} }

View File

@@ -0,0 +1,157 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2016 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Command\Plugin;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\ArrayInput;
use Cz\Git\GitRepository as GitRepository;
class DownloadPlugin extends AbstractPluginCommand
{
public function __construct()
{
parent::__construct('plugins:download');
$this
->setDescription('Downloads a plugin to Phraseanet')
->addArgument('source', InputArgument::REQUIRED, 'The source is a remote url (.zip or .git)')
->addArgument('destination', InputArgument::OPTIONAL, 'Download destination')
->addArgument('shouldInstallPlugin', InputArgument::OPTIONAL, 'True or false, determines if plugin should be installed after download');
}
protected function doExecutePluginAction(InputInterface $input, OutputInterface $output)
{
$source = $input->getArgument('source');
$destination = $input->getArgument('destination');
$shouldInstallPlugin = false;
$shouldInstallPlugin = $input->getArgument('shouldInstallPlugin');
$destinationSubdir = '/plugin-'.md5($source);
if ($destination){
$destination = trim($destination);
$destination = rtrim($destination, '/');
$localDownloadPath = $destination;
} else {
$localDownloadPath = '/tmp/plugin-download' . $destinationSubdir;
}
if (!is_dir($localDownloadPath)) {
mkdir($localDownloadPath, 0755, true);
}
$extension = $this->getURIExtension($source);
if ($extension){
switch ($extension){
case 'zip':
$localUnpackPath = '/tmp/plugin-zip'. $destinationSubdir;
if (!is_dir($localUnpackPath)) {
mkdir($localUnpackPath, 0755, true);
}
$localArchiveFile = $localUnpackPath . '/plugin-downloaded.zip';
// download
$output->writeln("Downloading <info>$source</info>...");
set_time_limit(0);
$fp = fopen ($localArchiveFile, 'w+');
$ch = curl_init($source);;
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_exec($ch);
curl_close($ch);
fclose($fp);
// unpack
$output->writeln("Unpacking <info>$source</info>...");
$zip = new \ZipArchive();
$errorUnpack = false;
if ($zip->open($localArchiveFile)) {
for ($i = 0; $i < $zip->numFiles; $i++) {
if (!($zip->extractTo($localDownloadPath, array($zip->getNameIndex($i))))) {
$errorUnpack = true;
}
}
$zip->close();
}
if ($errorUnpack){
$output->writeln("Failed unzipping <info>$source</info>");
} else {
$output->writeln("Plugin downloaded to <info>$localDownloadPath</info>");
if ($shouldInstallPlugin) $this->doInstallPlugin($localDownloadPath, $input, $output);
}
// remove zip archive
$this->delDirTree($localUnpackPath);
break;
case 'git':
$output->writeln("Downloading <info>$source</info>...");
$repo = GitRepository::cloneRepository($source, $localDownloadPath);
$output->writeln("Plugin downloaded to <info>$localDownloadPath</info>");
if ($shouldInstallPlugin) $this->doInstallPlugin($localDownloadPath, $input, $output);
break;
}
} else {
$output->writeln("The source <info>$source</info> is not supported. Only .zip and .git are supported.");
}
return 0;
}
protected function getURIExtension($source)
{
$validExtension = false;
$allowedExtension = array('zip','git');
$path = parse_url($source, PHP_URL_PATH);
if (strpos($path, '.') !== false) {
$pathParts = explode('.', $path);
$extension = $pathParts[1];
if (in_array($extension, $allowedExtension)){
$validExtension = true;
}
}
if ($validExtension){
return $extension;
} else {
return false;
}
}
protected static function delDirTree($dir) {
$files = array_diff(scandir($dir), array('.','..'));
foreach ($files as $file) {
(is_dir("$dir/$file")) ? self::delDirTree("$dir/$file") : unlink("$dir/$file");
}
return rmdir($dir);
}
}