From 6b81b2b39edcd21a3d336c36bb243b1cdded7989 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Wed, 29 Jan 2014 18:17:34 +0100 Subject: [PATCH 1/4] Add plugin:list command --- bin/console | 2 + .../Phrasea/Command/Plugin/ListPlugin.php | 48 ++++++++++++++ .../CLIProvider/PluginServiceProvider.php | 5 ++ lib/Alchemy/Phrasea/Plugin/Plugin.php | 65 +++++++++++++++++++ lib/Alchemy/Phrasea/Plugin/PluginManager.php | 64 ++++++++++++++++++ .../Phrasea/Plugin/Schema/PluginValidator.php | 2 - .../Phrasea/Command/Plugin/ListPluginTest.php | 36 ++++++++++ .../CLIProvider/PluginServiceProviderTest.php | 5 ++ .../test-plugin/twig-views/template.html.twig | 0 .../Management/ComposerInstallerTest.php | 1 - .../Phrasea/Plugin/PluginManagerTest.php | 43 ++++++++++++ .../Tests/Phrasea/Plugin/PluginTest.php | 45 +++++++++++++ .../Tests/Phrasea/Plugin/PluginTestCase.php | 7 ++ 13 files changed, 320 insertions(+), 3 deletions(-) create mode 100644 lib/Alchemy/Phrasea/Command/Plugin/ListPlugin.php create mode 100644 lib/Alchemy/Phrasea/Plugin/Plugin.php create mode 100644 lib/Alchemy/Phrasea/Plugin/PluginManager.php create mode 100644 tests/Alchemy/Tests/Phrasea/Command/Plugin/ListPluginTest.php create mode 100644 tests/Alchemy/Tests/Phrasea/Plugin/Fixtures/PluginDirInstalled/test-plugin/twig-views/template.html.twig create mode 100644 tests/Alchemy/Tests/Phrasea/Plugin/PluginManagerTest.php create mode 100644 tests/Alchemy/Tests/Phrasea/Plugin/PluginTest.php diff --git a/bin/console b/bin/console index 5982037aa6..3c603f819c 100755 --- a/bin/console +++ b/bin/console @@ -16,6 +16,7 @@ namespace KonsoleKommander; * @license http://opensource.org/licenses/gpl-3.0 GPLv3 * @link www.phraseanet.com */ +use Alchemy\Phrasea\Command\Plugin\ListPlugin; use Alchemy\Phrasea\Core\Version; use Alchemy\Phrasea\Command\BuildMissingSubdefs; use Alchemy\Phrasea\Command\CreateCollection; @@ -94,6 +95,7 @@ $cli->command(new RescanTechnicalDatas('records:rescan-technical-datas')); $cli->command(new BuildMissingSubdefs('records:build-missing-subdefs')); $cli->command(new AddPlugin()); +$cli->command(new ListPlugin()); $cli->command(new RemovePlugin()); $cli->command(new Configuration()); $cli->command(new XSendFileConfigurationDumper()); diff --git a/lib/Alchemy/Phrasea/Command/Plugin/ListPlugin.php b/lib/Alchemy/Phrasea/Command/Plugin/ListPlugin.php new file mode 100644 index 0000000000..c863952aeb --- /dev/null +++ b/lib/Alchemy/Phrasea/Command/Plugin/ListPlugin.php @@ -0,0 +1,48 @@ +setDescription('Lists installed plugins'); + } + + protected function doExecute(InputInterface $input, OutputInterface $output) + { + $plugins = array_map(function (Plugin $plugin) { + if ($plugin->isErroneous()) { + return array(''.$plugin->getName().'', 'Error : '.$plugin->getError()->getMessage().'', ''); + } + + return array($plugin->getName(), $plugin->getManifest()->getVersion(), $plugin->getManifest()->getDescription()); + }, $this->container['plugins.manager']->listPlugins()); + + $table = $this->getHelperSet()->get('table'); + $table + ->setHeaders(array('Name', 'Version', 'Description')) + ->setRows($plugins) + ; + + $table->render($output); + + return 0; + } +} diff --git a/lib/Alchemy/Phrasea/Core/CLIProvider/PluginServiceProvider.php b/lib/Alchemy/Phrasea/Core/CLIProvider/PluginServiceProvider.php index b2aa22d13d..9368f091a0 100644 --- a/lib/Alchemy/Phrasea/Core/CLIProvider/PluginServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/CLIProvider/PluginServiceProvider.php @@ -11,6 +11,7 @@ namespace Alchemy\Phrasea\Core\CLIProvider; +use Alchemy\Phrasea\Plugin\PluginManager; use Alchemy\Phrasea\Plugin\Schema\ManifestValidator; use Alchemy\Phrasea\Plugin\Management\PluginsExplorer; use Alchemy\Phrasea\Plugin\Management\ComposerInstaller; @@ -31,6 +32,10 @@ class PluginServiceProvider implements ServiceProviderInterface { $app['plugins.schema'] = realpath(__DIR__ . '/../../../../conf.d/plugin-schema.json'); + $app['plugins.manager'] = $app->share(function (Application $app) { + return new PluginManager($app['plugins.directory'], $app['plugins.plugins-validator']); + }); + $app['plugins.json-validator'] = $app->share(function (Application $app) { return new JsonValidator(); }); diff --git a/lib/Alchemy/Phrasea/Plugin/Plugin.php b/lib/Alchemy/Phrasea/Plugin/Plugin.php new file mode 100644 index 0000000000..fceed2e45d --- /dev/null +++ b/lib/Alchemy/Phrasea/Plugin/Plugin.php @@ -0,0 +1,65 @@ +name = $name; + $this->manifest = $manifest; + $this->error = $error; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return Boolean + */ + public function isErroneous() + { + return null !== $this->error; + } + + /** + * @return Manifest + */ + public function getManifest() + { + return $this->manifest; + } + + /** + * @return PluginValidationException + */ + public function getError() + { + return $this->error; + } +} diff --git a/lib/Alchemy/Phrasea/Plugin/PluginManager.php b/lib/Alchemy/Phrasea/Plugin/PluginManager.php new file mode 100644 index 0000000000..491743bb64 --- /dev/null +++ b/lib/Alchemy/Phrasea/Plugin/PluginManager.php @@ -0,0 +1,64 @@ +pluginDir = $pluginDir; + $this->validator = $validator; + } + + /** + * @return Plugin[] An array containing plugins + */ + public function listPlugins() + { + $finder = new Finder(); + $finder + ->depth(0) + ->in($this->pluginDir) + ->directories(); + + $plugins = array(); + + foreach ($finder as $pluginDir) { + $manifest = $error = null; + $name = $pluginDir->getBasename(); + + try { + $manifest = $this->validator->validatePlugin((string) $pluginDir); + } catch (PluginValidationException $e) { + $error = $e; + } + + $plugins[$name] = new Plugin($name, $manifest, $error); + } + + return $plugins; + } + + public function hasPlugin($name) + { + $plugins = $this->listPlugins(); + + return isset($plugins[$name]); + } +} diff --git a/lib/Alchemy/Phrasea/Plugin/Schema/PluginValidator.php b/lib/Alchemy/Phrasea/Plugin/Schema/PluginValidator.php index 192e3534f4..7117bbce5d 100644 --- a/lib/Alchemy/Phrasea/Plugin/Schema/PluginValidator.php +++ b/lib/Alchemy/Phrasea/Plugin/Schema/PluginValidator.php @@ -11,8 +11,6 @@ namespace Alchemy\Phrasea\Plugin\Schema; -use Alchemy\Phrasea\Plugin\Schema\ManifestValidator; -use Alchemy\Phrasea\Plugin\Schema\Manifest; use Alchemy\Phrasea\Plugin\Exception\PluginValidationException; use Alchemy\Phrasea\Plugin\Exception\JsonValidationException; diff --git a/tests/Alchemy/Tests/Phrasea/Command/Plugin/ListPluginTest.php b/tests/Alchemy/Tests/Phrasea/Command/Plugin/ListPluginTest.php new file mode 100644 index 0000000000..eacdf8cdf9 --- /dev/null +++ b/tests/Alchemy/Tests/Phrasea/Command/Plugin/ListPluginTest.php @@ -0,0 +1,36 @@ +getMock('Symfony\Component\Console\Input\InputInterface'); + $output = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + + $table = $this->getMockBuilder('Symfony\Component\Console\Helper\TableHelper') + ->disableOriginalConstructor() + ->getMock(); + $table->expects($this->once()) + ->method('setHeaders') + ->will($this->returnSelf()); + + $helperSet = $this->getMockBuilder('Symfony\Component\Console\Helper\HelperSet') + ->disableOriginalConstructor() + ->getMock(); + $helperSet->expects($this->once()) + ->method('get') + ->will($this->returnValue($table)); + + $command = new ListPlugin(); + $command->setContainer(self::$DI['cli']); + $command->setHelperSet($helperSet); + + $result = $command->execute($input, $output); + + $this->assertSame(0, $result); + } +} diff --git a/tests/Alchemy/Tests/Phrasea/Core/CLIProvider/PluginServiceProviderTest.php b/tests/Alchemy/Tests/Phrasea/Core/CLIProvider/PluginServiceProviderTest.php index cac14ebb68..bcbedff10b 100644 --- a/tests/Alchemy/Tests/Phrasea/Core/CLIProvider/PluginServiceProviderTest.php +++ b/tests/Alchemy/Tests/Phrasea/Core/CLIProvider/PluginServiceProviderTest.php @@ -18,6 +18,11 @@ class PluginServiceProvidertest extends ServiceProviderTestCase 'plugins.json-validator', 'JsonSchema\Validator' ), + array( + 'Alchemy\Phrasea\Core\CLIProvider\PluginServiceProvider', + 'plugins.manager', + 'Alchemy\Phrasea\Plugin\PluginManager' + ), array( 'Alchemy\Phrasea\Core\CLIProvider\PluginServiceProvider', 'plugins.plugins-validator', diff --git a/tests/Alchemy/Tests/Phrasea/Plugin/Fixtures/PluginDirInstalled/test-plugin/twig-views/template.html.twig b/tests/Alchemy/Tests/Phrasea/Plugin/Fixtures/PluginDirInstalled/test-plugin/twig-views/template.html.twig new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/Alchemy/Tests/Phrasea/Plugin/Management/ComposerInstallerTest.php b/tests/Alchemy/Tests/Phrasea/Plugin/Management/ComposerInstallerTest.php index 07e954e7c8..2894407e24 100644 --- a/tests/Alchemy/Tests/Phrasea/Plugin/Management/ComposerInstallerTest.php +++ b/tests/Alchemy/Tests/Phrasea/Plugin/Management/ComposerInstallerTest.php @@ -2,7 +2,6 @@ namespace Alchemy\Phrasea\Plugin\Management; -use Alchemy\Phrasea\Plugin\Management\ComposerInstaller; use Alchemy\Phrasea\Utilities\ComposerSetup; use Guzzle\Http\Client as Guzzle; use Symfony\Component\Process\ExecutableFinder; diff --git a/tests/Alchemy/Tests/Phrasea/Plugin/PluginManagerTest.php b/tests/Alchemy/Tests/Phrasea/Plugin/PluginManagerTest.php new file mode 100644 index 0000000000..6bbe410a2e --- /dev/null +++ b/tests/Alchemy/Tests/Phrasea/Plugin/PluginManagerTest.php @@ -0,0 +1,43 @@ +listPlugins(); + $this->assertCount(1, $plugins); + $plugin = array_pop($plugins); + + $this->assertFalse($plugin->isErroneous()); + } + + public function testListWrongPlugins() + { + $manager = new PluginManager(__DIR__ . '/Fixtures/WrongPlugins', self::$DI['cli']['plugins.plugins-validator']); + $plugins = $manager->listPlugins(); + $this->assertCount(8, $plugins); + $plugin = array_pop($plugins); + + $this->assertTrue($plugin->isErroneous()); + } + + public function testHasPlugin() + { + $manager = new PluginManager(__DIR__ . '/Fixtures/PluginDirInstalled', self::$DI['cli']['plugins.plugins-validator']); + $this->assertTrue($manager->hasPlugin('test-plugin')); + $this->assertFalse($manager->hasPlugin('test-plugin2')); + } + + private function createValidatorMock() + { + return $this->getMockBuilder('Alchemy\Phrasea\Plugin\Schema\PluginValidator') + ->disableOriginalConstructor() + ->getMock(); + } +} diff --git a/tests/Alchemy/Tests/Phrasea/Plugin/PluginTest.php b/tests/Alchemy/Tests/Phrasea/Plugin/PluginTest.php new file mode 100644 index 0000000000..6bdc3e7638 --- /dev/null +++ b/tests/Alchemy/Tests/Phrasea/Plugin/PluginTest.php @@ -0,0 +1,45 @@ +createManifestMock(); + $error = $this->getMock('Alchemy\Phrasea\Plugin\Exception\PluginValidationException'); + + $plugin = new Plugin('toto', $manifest, null); + $this->assertSame('toto', $plugin->getName()); + $this->assertSame($manifest, $plugin->getManifest()); + $this->assertNull($plugin->getError()); + $this->assertFalse($plugin->isErroneous()); + + $plugin = new Plugin('toto', null, $error); + $this->assertSame('toto', $plugin->getName()); + $this->assertNull($plugin->getManifest()); + $this->assertSame($error, $plugin->getError()); + $this->assertTrue($plugin->isErroneous()); + } + + /** + * @expectedException \LogicException + */ + public function testBothNull() + { + new Plugin('toto', null, null); + } + + /** + * @expectedException \LogicException + */ + public function testBothNotNull() + { + $manifest = $this->createManifestMock(); + $error = $this->getMock('Alchemy\Phrasea\Plugin\Exception\PluginValidationException'); + + new Plugin('toto', $manifest, $error); + } +} diff --git a/tests/Alchemy/Tests/Phrasea/Plugin/PluginTestCase.php b/tests/Alchemy/Tests/Phrasea/Plugin/PluginTestCase.php index 448d6f29b0..c051c13785 100644 --- a/tests/Alchemy/Tests/Phrasea/Plugin/PluginTestCase.php +++ b/tests/Alchemy/Tests/Phrasea/Plugin/PluginTestCase.php @@ -30,4 +30,11 @@ class PluginTestCase extends \PhraseanetPHPUnitAbstract { return __DIR__ . '/../../../../../lib/conf.d/plugin-schema.json'; } + + protected function createManifestMock() + { + return $this->getMockBuilder('Alchemy\Phrasea\Plugin\Schema\Manifest') + ->disableOriginalConstructor() + ->getMock(); + } } From 690a4436d59121524478856ad07b1c10194a5910 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Fri, 31 Jan 2014 12:07:27 +0100 Subject: [PATCH 2/4] Fix #1677 : Avoid migration failure --- lib/Alchemy/Phrasea/Command/Upgrade/Step35.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/Alchemy/Phrasea/Command/Upgrade/Step35.php b/lib/Alchemy/Phrasea/Command/Upgrade/Step35.php index ea71a4b53c..14a49b1157 100644 --- a/lib/Alchemy/Phrasea/Command/Upgrade/Step35.php +++ b/lib/Alchemy/Phrasea/Command/Upgrade/Step35.php @@ -47,11 +47,6 @@ class Step35 implements DatasUpgraderInterface $this->ensureMigrateColumn($databox); - $sql = 'TRUNCATE metadatas'; - $stmt = $databox->get_connection()->prepare($sql); - $stmt->execute(); - $stmt->closeCursor(); - do { $rs = $this->getEmptyOriginalNameRecords($databox); From d53e05e3bcd1dca2c1116a7ec40b23b4a5f0e5bd Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Fri, 31 Jan 2014 12:44:42 +0100 Subject: [PATCH 3/4] Fix #1673 : Avoid interpolation for image and videos --- composer.json | 2 +- composer.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index fdc99ac306..cf3aea836f 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "jms/serializer" : "~0.10", "justinrainbow/json-schema" : "~1.3", "mediavorus/mediavorus" : "~0.4.0", - "media-alchemyst/media-alchemyst" : "~0.4, >=0.4.3", + "media-alchemyst/media-alchemyst" : "~0.4, >=0.4.4", "monolog/monolog" : "~1.3", "mrclay/minify" : "~2.1.6", "neutron/silex-imagine-provider" : "~0.1.0", diff --git a/composer.lock b/composer.lock index 103f21f5da..2bce0ef03e 100644 --- a/composer.lock +++ b/composer.lock @@ -3,7 +3,7 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "21af9b0c45201bacb0045dd4b8220358", + "hash": "6d21ea16cd4f85707ff5c89c7a5777ac", "packages": [ { "name": "alchemy-fr/tcpdf-clone", @@ -1434,16 +1434,16 @@ }, { "name": "media-alchemyst/media-alchemyst", - "version": "0.4.3", + "version": "0.4.4", "source": { "type": "git", "url": "https://github.com/alchemy-fr/Media-Alchemyst.git", - "reference": "319f55c2265a9056d68f1f0e6314dce977ef47ef" + "reference": "067599fd669b86ff3a1cfb344730014b4a339b7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/alchemy-fr/Media-Alchemyst/zipball/319f55c2265a9056d68f1f0e6314dce977ef47ef", - "reference": "319f55c2265a9056d68f1f0e6314dce977ef47ef", + "url": "https://api.github.com/repos/alchemy-fr/Media-Alchemyst/zipball/067599fd669b86ff3a1cfb344730014b4a339b7e", + "reference": "067599fd669b86ff3a1cfb344730014b4a339b7e", "shasum": "" }, "require": { @@ -1503,7 +1503,7 @@ "video", "video processing" ], - "time": "2013-12-03 17:07:51" + "time": "2014-01-31 11:32:50" }, { "name": "mediavorus/mediavorus", From aa70b21f86bdd200c78d47c83bdfeb491398113f Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Wed, 29 Jan 2014 18:26:33 +0100 Subject: [PATCH 4/4] Add json output to plugins:list command --- .../Phrasea/Command/Plugin/ListPlugin.php | 62 ++++++++++++++++--- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/lib/Alchemy/Phrasea/Command/Plugin/ListPlugin.php b/lib/Alchemy/Phrasea/Command/Plugin/ListPlugin.php index c863952aeb..db269b02ef 100644 --- a/lib/Alchemy/Phrasea/Command/Plugin/ListPlugin.php +++ b/lib/Alchemy/Phrasea/Command/Plugin/ListPlugin.php @@ -13,6 +13,7 @@ namespace Alchemy\Phrasea\Command\Plugin; use Alchemy\Phrasea\Plugin\Plugin; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class ListPlugin extends AbstractPluginCommand @@ -22,27 +23,68 @@ class ListPlugin extends AbstractPluginCommand parent::__construct('plugins:list'); $this - ->setDescription('Lists installed plugins'); + ->setDescription('Lists installed plugins') + ->addOption('json', 'j', InputOption::VALUE_NONE, 'Output result in JSON'); } protected function doExecute(InputInterface $input, OutputInterface $output) { - $plugins = array_map(function (Plugin $plugin) { + $plugins = array_map(function (Plugin $plugin) use ($input) { if ($plugin->isErroneous()) { - return array(''.$plugin->getName().'', 'Error : '.$plugin->getError()->getMessage().'', ''); + return $this->formatErroneousPlugin($input, $plugin); } - return array($plugin->getName(), $plugin->getManifest()->getVersion(), $plugin->getManifest()->getDescription()); + return $this->formatPlugin($input, $plugin); }, $this->container['plugins.manager']->listPlugins()); - $table = $this->getHelperSet()->get('table'); - $table - ->setHeaders(array('Name', 'Version', 'Description')) - ->setRows($plugins) - ; + if ($input->getOption('json')) { + $output->writeln(json_encode(array('plugins' => array_values($plugins)))); + } else { + $table = $this->getHelperSet()->get('table'); + $table + ->setHeaders(array('Name', 'Version', 'Description')) + ->setRows($plugins) + ; - $table->render($output); + $table->render($output); + } return 0; } + + private function formatPlugin(InputInterface $input, Plugin $plugin) + { + if ($input->getOption('json')) { + return array( + 'name' => $plugin->getName(), + 'version' => $plugin->getManifest()->getVersion(), + 'description' => $plugin->getManifest()->getDescription(), + 'error' => false, + ); + } + + return array( + $plugin->getName(), + $plugin->getManifest()->getVersion(), + $plugin->getManifest()->getDescription(), + ); + } + + private function formatErroneousPlugin(InputInterface $input, Plugin $plugin) + { + if ($input->getOption('json')) { + return array( + 'name' => $plugin->getName(), + 'error' => true, + 'description' => 'Error : '.$plugin->getError()->getMessage(), + 'version' => null, + ); + } + + return array( + '' . $plugin->getName() . '', + 'Error : ' . $plugin->getError()->getMessage() . '', + '', + ); + } }