diff --git a/PHPCI/Controller/PluginController.php b/PHPCI/Controller/PluginController.php index 56d01f3e..b7e32ddb 100644 --- a/PHPCI/Controller/PluginController.php +++ b/PHPCI/Controller/PluginController.php @@ -11,6 +11,9 @@ namespace PHPCI\Controller; use b8; use PHPCI\Model\Build; +use PHPCI\Plugin\Util\ComposerPluginInformation; +use PHPCI\Plugin\Util\FilesPluginInformation; +use PHPCI\Plugin\Util\PluginInformationCollection; /** * Plugin Controller - Provides support for installing Composer packages. @@ -59,8 +62,18 @@ class PluginController extends \PHPCI\Controller $this->view->required = $this->required; $json = $this->getComposerJson(); - $this->view->installed = $json['require']; - $this->view->suggested = $json['suggest']; + $this->view->installedPackages = $json['require']; + $this->view->suggestedPackages = $json['suggest']; + + $pluginInfo = new PluginInformationCollection(); + $pluginInfo->add(FilesPluginInformation::newFromDir( + PHPCI_DIR . "PHPCI/Plugin/" + )); + $pluginInfo->add(ComposerPluginInformation::buildFromYaml( + PHPCI_DIR . "vendor/composer/installed.json" + )); + + $this->view->plugins = $pluginInfo->getInstalledPlugins(); return $this->view->render(); } diff --git a/PHPCI/Plugin/Util/ComposerPluginInformation.php b/PHPCI/Plugin/Util/ComposerPluginInformation.php new file mode 100644 index 00000000..0ce76e6f --- /dev/null +++ b/PHPCI/Plugin/Util/ComposerPluginInformation.php @@ -0,0 +1,147 @@ +composerPackages = $composerPackages; + } + + /** + * Returns an array of objects. Each one represents an available plugin + * and will have the following properties: + * name - The friendly name of the plugin (may be an empty string) + * class - The class of the plugin (will include namespace) + * @return \stdClass[] + */ + public function getInstalledPlugins() + { + $this->loadPluginInfo(); + return $this->pluginInfo; + } + + /** + * Returns an array of all the class names of plugins that have been + * loaded. + * + * @return string[] + */ + public function getPluginClasses() + { + return array_map( + function($plugin) { + return $plugin->class; + }, + $this->getInstalledPlugins() + ); + } + + protected function loadPluginInfo() + { + if ($this->pluginInfo !== null) { + return; + } + $this->pluginInfo = array(); + foreach($this->composerPackages as $package) { + $this->addPluginsFromPackage($package); + } + } + + /** + * @param \stdClass $package + */ + protected function addPluginsFromPackage($package) + { + if (isset($package->extra->phpci)) { + $phpciData = $package->extra->phpci; + + if (isset($phpciData->pluginNamespace)) { + $rootNamespace = $phpciData->pluginNamespace; + } + else { + $rootNamespace = ""; + } + + if (is_array($phpciData->suppliedPlugins)) { + $this->addPlugins( + $phpciData->suppliedPlugins, + $package->name, + $rootNamespace + ); + } + } + } + + /** + * @param \stdClass[] $plugins + * @param string $sourcePackageName + * @param string $rootNamespace + */ + protected function addPlugins( + array $plugins, + $sourcePackageName, + $rootNamespace = "") + { + foreach($plugins as $plugin) { + if (!isset($plugin->class)) { + continue; + } + $this->addPlugin($plugin, $sourcePackageName, $rootNamespace); + } + } + + /** + * @param \stdClass $plugin + * @param string $sourcePackageName + * @param string $rootNamespace + */ + protected function addPlugin( + $plugin, + $sourcePackageName, + $rootNamespace = "") + { + $newPlugin = clone $plugin; + + $newPlugin->class = $rootNamespace . $newPlugin->class; + + if (!isset($newPlugin->name)) { + $newPlugin->name = ""; + } + + $newPlugin->source = $sourcePackageName; + + $this->pluginInfo[] = $newPlugin; + } +} \ No newline at end of file diff --git a/PHPCI/Plugin/Util/FilesPluginInformation.php b/PHPCI/Plugin/Util/FilesPluginInformation.php new file mode 100644 index 00000000..3d8b6f47 --- /dev/null +++ b/PHPCI/Plugin/Util/FilesPluginInformation.php @@ -0,0 +1,105 @@ +files = $files; + } + + /** + * Returns an array of objects. Each one represents an available plugin + * and will have the following properties: + * name - The friendly name of the plugin (may be an empty string) + * class - The class of the plugin (will include namespace) + * @return \stdClass[] + */ + public function getInstalledPlugins() + { + if ($this->pluginInfo === null) { + $this->loadPluginInfo(); + } + return $this->pluginInfo; + } + + /** + * Returns an array of all the class names of plugins that have been + * loaded. + * + * @return string[] + */ + public function getPluginClasses() + { + return array_map( + function($plugin) { + return $plugin->class; + }, + $this->getInstalledPlugins() + ); + } + + protected function loadPluginInfo() + { + $this->pluginInfo = array(); + foreach($this->files as $fileInfo) { + if ($fileInfo instanceof \SplFileInfo) { + if ($fileInfo->isFile()) { + $this->addPluginFromFile($fileInfo); + } + } + } + } + + protected function addPluginFromFile(\SplFileInfo $fileInfo) + { + $newPlugin = new \stdClass(); + $newPlugin->class = $this->getFullClassFromFile($fileInfo); + $newPlugin->source = "core"; + $parts = explode('\\', $newPlugin->class); + $newPlugin->name = end($parts); + + $this->pluginInfo[] = $newPlugin; + } + + protected function getFullClassFromFile(\SplFileInfo $fileInfo) + { + //TODO: Something less horrible than a regular expression + // on the contents of a file + $contents = file_get_contents($fileInfo->getRealPath()); + + $matches = array(); + preg_match('#class +([A-Za-z]+) +implements#i', $contents, $matches); + $className = $matches[1]; + + $matches = array(); + preg_match('#namespace +([A-Za-z\\\\]+);#i', $contents, $matches); + $namespace = $matches[1]; + + return $namespace . '\\' . $className; + } + +} \ No newline at end of file diff --git a/PHPCI/Plugin/Util/InstalledPluginInformation.php b/PHPCI/Plugin/Util/InstalledPluginInformation.php new file mode 100644 index 00000000..f63c432a --- /dev/null +++ b/PHPCI/Plugin/Util/InstalledPluginInformation.php @@ -0,0 +1,23 @@ +pluginInformations[] = $information; + } + + /** + * Returns an array of objects. Each one represents an available plugin + * and will have the following properties: + * name - The friendly name of the plugin (may be an empty string) + * class - The class of the plugin (will include namespace) + * @return \stdClass[] + */ + public function getInstalledPlugins() + { + $arr = array(); + foreach($this->pluginInformations as $single) { + $arr = array_merge($arr, $single->getInstalledPlugins()); + } + return $arr; + } + + /** + * Returns an array of all the class names of plugins that have been + * loaded. + * + * @return string[] + */ + public function getPluginClasses() + { + $arr = array(); + foreach($this->pluginInformations as $single) { + $arr = array_merge($arr, $single->getPluginClasses()); + } + return $arr; + } + +} \ No newline at end of file diff --git a/PHPCI/View/Plugin/index.phtml b/PHPCI/View/Plugin/index.phtml index 134cf260..3cfac153 100644 --- a/PHPCI/View/Plugin/index.phtml +++ b/PHPCI/View/Plugin/index.phtml @@ -1,4 +1,4 @@ -

Plugins

+

Packages and Provided Plugins

PHPCI cannot automatically install/remove plugins for you, as either the shell_exec() function is disabled or PHPCI could not find Composer. PHPCI will update composer.json for you, but you will need to run Composer manually to make the changes.

@@ -21,7 +21,30 @@
-

Installed Plugins

+

Available Plugins

+ + + + + + + + + + + + + + + + + + +
NameClassProvided by Package
name; ?>class; ?>source; ?>
+
+ +
+

Installed Packages

@@ -32,7 +55,7 @@ - $version): ?> + $version): ?> @@ -48,7 +71,7 @@
-

Suggested Plugins

+

Suggested Packages

@@ -59,8 +82,8 @@ - $version): ?> - + $version): ?> + @@ -76,7 +99,7 @@
-

Search Packagist for More Plugins

+

Search Packagist for More Packages

diff --git a/Tests/PHPCI/Plugin/Util/ComposerPluginInformationTest.php b/Tests/PHPCI/Plugin/Util/ComposerPluginInformationTest.php new file mode 100644 index 00000000..113c1087 --- /dev/null +++ b/Tests/PHPCI/Plugin/Util/ComposerPluginInformationTest.php @@ -0,0 +1,51 @@ +testedInformation = ComposerPluginInformation::buildFromYaml($file); + } + + protected function phpciSetup() + { + $this->setUpFromFile( + __DIR__ . "/../../../../vendor/composer/installed.json" + ); + } + + public function testBuildFromYaml_ReturnsInstance() + { + $this->phpciSetup(); + $this->assertInstanceOf( + '\PHPCI\Plugin\Util\ComposerPluginInformation', + $this->testedInformation + ); + } + + public function testGetInstalledPlugins_ReturnsStdClassArray() + { + $this->phpciSetup(); + $plugins = $this->testedInformation->getInstalledPlugins(); + $this->assertInternalType("array", $plugins); + $this->assertContainsOnly("stdClass", $plugins); + } + + public function testGetPluginClasses_ReturnsStringArray() + { + $this->phpciSetup(); + $classes = $this->testedInformation->getPluginClasses(); + $this->assertInternalType("array", $classes); + $this->assertContainsOnly("string", $classes); + } +} + \ No newline at end of file diff --git a/Tests/PHPCI/Plugin/Util/FilesPluginInformationTest.php b/Tests/PHPCI/Plugin/Util/FilesPluginInformationTest.php new file mode 100644 index 00000000..6e1605ab --- /dev/null +++ b/Tests/PHPCI/Plugin/Util/FilesPluginInformationTest.php @@ -0,0 +1,26 @@ +getInstalledPlugins(); + $this->assertContainsOnlyInstancesOf('stdClass', $pluginInfos); + } + + public function testGetPluginClasses_returnsStrings() + { + $pluginDirPath = realpath(__DIR__ . "/../../../../PHPCI/Plugin/"); + $test = FilesPluginInformation::newFromDir($pluginDirPath); + $pluginInfos = $test->getPluginClasses(); + $this->assertContainsOnly('string', $pluginInfos); + } +} + \ No newline at end of file