Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions API.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ public function getAllowedPlugins(): array
return $this->getPluginListProvider()->getAllowedPlugins();
}

/**
* Returns metadata for the plugins used by OpenApiDocs spec generation.
*
* @return array<string, array{description: string}>
*/
public function getAllowedPluginMetadata(): array
{
Piwik::checkUserHasSomeViewAccess();

return $this->getPluginListProvider()->getAllowedPluginMetadata();
}

/**
* Returns a previously generated OpenAPI specification for a plugin.
*
Expand Down
33 changes: 33 additions & 0 deletions Generation/PluginListProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace Piwik\Plugins\OpenApiDocs\Generation;

use Piwik\API\Proxy;
use Piwik\API\Request;
use Piwik\Piwik;
use Piwik\Plugin\Manager;

Expand Down Expand Up @@ -42,6 +44,28 @@ public function getAllowedPlugins(): array
}));
}

/**
* @return array<string, array{description: string}>
*/
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)) {
Expand All @@ -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
*/
Expand Down
21 changes: 21 additions & 0 deletions tests/Unit/APITest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
61 changes: 61 additions & 0 deletions tests/Unit/Generation/PluginListProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, bool> $inFilesystemByPlugin
Expand Down
Loading