diff --git a/API.php b/API.php index 13e6eb1..c3dfcde 100644 --- a/API.php +++ b/API.php @@ -37,6 +37,18 @@ public function getAllowedPlugins(): array return $this->getPluginListProvider()->getAllowedPlugins(); } + /** + * Returns metadata for the plugins used by OpenApiDocs spec generation. + * + * @return array + */ + public function getAllowedPluginMetadata(): array + { + Piwik::checkUserHasSomeViewAccess(); + + return $this->getPluginListProvider()->getAllowedPluginMetadata(); + } + /** * Returns a previously generated OpenAPI specification for a plugin. * diff --git a/Generation/PluginListProvider.php b/Generation/PluginListProvider.php index 19d024c..14d3fce 100644 --- a/Generation/PluginListProvider.php +++ b/Generation/PluginListProvider.php @@ -11,6 +11,8 @@ namespace Piwik\Plugins\OpenApiDocs\Generation; +use Piwik\API\Proxy; +use Piwik\API\Request; use Piwik\Piwik; use Piwik\Plugin\Manager; @@ -42,6 +44,28 @@ public function getAllowedPlugins(): array })); } + /** + * @return array + */ + public function getAllowedPluginMetadata(): array + { + $metadata = []; + + foreach ($this->getAllowedPlugins() as $pluginName) { + try { + $description = $this->getPluginDescription($pluginName); + } catch (\Throwable $e) { + $description = ''; + } + + $metadata[$pluginName] = [ + 'description' => $description, + ]; + } + + return $metadata; + } + private function shouldIncludeEventProvidedPlugin(string $pluginName): bool { if (!$this->pluginManager->isPluginInFilesystem($pluginName)) { @@ -55,6 +79,15 @@ protected function pluginHasApiFile(string $pluginName): bool return is_file(Manager::getPluginDirectory($pluginName) . '/API.php'); } + protected function getPluginDescription(string $pluginName): string + { + $apiClassName = Request::getClassNameAPI($pluginName); + Proxy::getInstance()->registerClass($apiClassName); + $documentation = Proxy::getInstance()->getMetadata()[$apiClassName]['__documentation'] ?? ''; + + return is_string($documentation) ? trim(strip_tags($documentation)) : ''; + } + /** * @param string[] $pluginNames */ diff --git a/tests/Unit/APITest.php b/tests/Unit/APITest.php index 5feff55..cb20b02 100644 --- a/tests/Unit/APITest.php +++ b/tests/Unit/APITest.php @@ -77,6 +77,27 @@ public function testGetAllowedPluginsReturnsProviderValues(): void $this->assertSame(['Login', 'ActivityLog'], $api->getAllowedPlugins()); } + public function testGetAllowedPluginMetadataReturnsProviderValues(): void + { + $provider = $this->createMock(PluginListProvider::class); + $provider->expects($this->once()) + ->method('getAllowedPluginMetadata') + ->willReturn([ + 'Login' => ['description' => 'Login API description'], + 'ActivityLog' => ['description' => ''], + ]); + + $api = $this->getMockBuilder(API::class) + ->onlyMethods(['getPluginListProvider']) + ->getMock(); + $api->method('getPluginListProvider')->willReturn($provider); + + $this->assertSame([ + 'Login' => ['description' => 'Login API description'], + 'ActivityLog' => ['description' => ''], + ], $api->getAllowedPluginMetadata()); + } + public function testGetOpenApiSpecThrowsExceptionWhenFileMissing() { $api = $this->buildApiMock('/tmp/CustomAlerts_openapi_spec_v1.0.0.json', false); diff --git a/tests/Unit/Generation/PluginListProviderTest.php b/tests/Unit/Generation/PluginListProviderTest.php index 3c0e1a2..a18c9a3 100644 --- a/tests/Unit/Generation/PluginListProviderTest.php +++ b/tests/Unit/Generation/PluginListProviderTest.php @@ -107,6 +107,67 @@ static function (string $eventName, array $params): void { $this->assertSame(['HasApi', 'Login', 'InactivePlugin'], $provider->getAllowedPlugins()); } + public function testGetAllowedPluginMetadataReturnsDescriptionPerAllowedPlugin(): void + { + $provider = $this->getMockBuilder(PluginListProvider::class) + ->disableOriginalConstructor() + ->onlyMethods(['getAllowedPlugins', 'getPluginDescription']) + ->getMock(); + + $provider->method('getAllowedPlugins') + ->willReturn(['Login', 'ActivityLog']); + $provider->method('getPluginDescription') + ->willReturnMap([ + ['Login', 'Login API description'], + ['ActivityLog', 'Activity log API description'], + ]); + + $this->assertSame([ + 'Login' => ['description' => 'Login API description'], + 'ActivityLog' => ['description' => 'Activity log API description'], + ], $provider->getAllowedPluginMetadata()); + } + + public function testGetAllowedPluginMetadataReturnsEmptyStringWhenDescriptionMissing(): void + { + $provider = $this->getMockBuilder(PluginListProvider::class) + ->disableOriginalConstructor() + ->onlyMethods(['getAllowedPlugins', 'getPluginDescription']) + ->getMock(); + + $provider->method('getAllowedPlugins') + ->willReturn(['Login']); + $provider->method('getPluginDescription') + ->willReturn(''); + + $this->assertSame(['Login' => ['description' => '']], $provider->getAllowedPluginMetadata()); + } + + public function testGetAllowedPluginMetadataContinuesWhenOnePluginDescriptionFails(): void + { + $provider = $this->getMockBuilder(PluginListProvider::class) + ->disableOriginalConstructor() + ->onlyMethods(['getAllowedPlugins', 'getPluginDescription']) + ->getMock(); + + $provider->method('getAllowedPlugins') + ->willReturn(['Login', 'BrokenPlugin', 'ActivityLog']); + $provider->method('getPluginDescription') + ->willReturnCallback(static function (string $pluginName): string { + if ($pluginName === 'BrokenPlugin') { + throw new \RuntimeException('Could not register API class'); + } + + return $pluginName === 'Login' ? 'Login API description' : 'Activity log API description'; + }); + + $this->assertSame([ + 'Login' => ['description' => 'Login API description'], + 'BrokenPlugin' => ['description' => ''], + 'ActivityLog' => ['description' => 'Activity log API description'], + ], $provider->getAllowedPluginMetadata()); + } + /** * @param string[] $activatedPlugins * @param array $inFilesystemByPlugin