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(); + } }