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