Skip to content

Commit 85f18f0

Browse files
feat(reduce-search-providers): reduce search providers
Signed-off-by: Misha M.-Kupriyanov <kupriyanov@strato.de>
1 parent b2dae69 commit 85f18f0

3 files changed

Lines changed: 225 additions & 0 deletions

File tree

lib/AppInfo/Application.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@
99

1010
namespace OCA\NcTheming\AppInfo;
1111

12+
use OC\AppFramework\Bootstrap\Coordinator;
1213
use OC\AppFramework\DependencyInjection\DIContainer;
14+
use OC\Search\SearchComposer;
1315
use OCA\NcTheming\Service\OverrideThemesService;
1416
use OCA\NcTheming\Themes\OverrideDefaultTheme;
17+
use OCA\NcTheming\Search\SearchComposerDecorator;
1518
use OCA\Theming\Service\ThemesService;
1619
use OCA\Theming\Themes\DarkHighContrastTheme;
1720
use OCA\Theming\Themes\DarkTheme;
@@ -25,12 +28,18 @@
2528
use OCP\AppFramework\Bootstrap\IRegistrationContext;
2629
use OCP\AppFramework\QueryException;
2730
use OCP\IConfig;
31+
use OCP\IURLGenerator;
2832
use OCP\IUserSession;
33+
use Psr\Container\ContainerInterface;
2934
use Psr\Log\LoggerInterface;
3035

3136
class Application extends App implements IBootstrap {
3237
public const APP_ID = 'nc_theming';
3338

39+
public const ALLOWED_SEARCH_PROVIDERS = [
40+
'files',
41+
];
42+
3443
/** @psalm-suppress PossiblyUnusedMethod */
3544
public function __construct() {
3645
parent::__construct(self::APP_ID);
@@ -74,8 +83,32 @@ public function register(IRegistrationContext $context): void {
7483
$c->get(DyslexiaFont::class)
7584
);
7685
});
86+
87+
// allow only desired search providers for full-text search
88+
$this->registerSearchComposerDecorator($context);
7789
}
7890

7991
public function boot(IBootContext $context): void {
8092
}
93+
94+
/**
95+
* Decorate SearchComposer with allowed search providers -
96+
* to ensure being listed and used for searches
97+
*
98+
* For allow list, the ids of the Search\IProvider is used
99+
*/
100+
protected function registerSearchComposerDecorator(IRegistrationContext $context) {
101+
$this->getContainer()->getServer()->registerService(SearchComposer::class,
102+
function ($c) {
103+
return new SearchComposerDecorator(
104+
new SearchComposer(
105+
$c->get(Coordinator::class),
106+
$c->get(ContainerInterface::class),
107+
$c->get(IURLGenerator::class),
108+
$c->get(LoggerInterface::class)
109+
),
110+
self::ALLOWED_SEARCH_PROVIDERS
111+
);
112+
});
113+
}
81114
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileLicenseText: 2023 T-Systems International
6+
* SPDX-FileLicenseText: 2024 STRATO AG
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\NcTheming\Search;
11+
12+
use OC\Search\FilterCollection;
13+
use OC\Search\SearchComposer;
14+
use OCP\IUser;
15+
use OCP\Search\ISearchQuery;
16+
use OCP\Search\SearchResult;
17+
18+
/**
19+
* Allow search types like apps or settings for full-text search
20+
* so that only these types are delivered to client. Also when using
21+
* API directly.
22+
*/
23+
class SearchComposerDecorator extends SearchComposer {
24+
protected SearchComposer $decorated;
25+
protected array $allowedProviderIds = [];
26+
27+
public function __construct(
28+
SearchComposer $decorated,
29+
array $allowedProviderIds
30+
) {
31+
$this->decorated = $decorated;
32+
$this->allowedProviderIds = $allowedProviderIds;
33+
}
34+
35+
/**
36+
* Get providers from allowed provider list
37+
*/
38+
public function getProviders(string $route, array $routeParameters): array {
39+
$providers = $this->decorated->getProviders($route, $routeParameters);
40+
41+
if (empty($this->allowedProviderIds)) {
42+
return $providers;
43+
}
44+
45+
return array_values(array_filter($providers, function ($p) {
46+
return in_array($p['id'], $this->allowedProviderIds);
47+
}));
48+
}
49+
50+
/**
51+
* No decoration, only delegate.
52+
*/
53+
public function search(IUser $user, string $providerId, ISearchQuery $query): SearchResult {
54+
return $this->decorated->search($user, $providerId, $query);
55+
}
56+
57+
/**
58+
* No decoration, only delegate.
59+
*/
60+
public function buildFilterList(string $providerId, array $parameters): FilterCollection {
61+
return $this->decorated->buildFilterList($providerId, $parameters);
62+
}
63+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileLicenseText: 2023 T-Systems International
7+
* SPDX-FileLicenseText: 2024 STRATO AG
8+
* SPDX-License-Identifier: AGPL-3.0-or-later
9+
*/
10+
11+
namespace OCA\NcTheming\Tests\Search;
12+
13+
use OC\AppFramework\Bootstrap\Coordinator;
14+
use OC\Search\SearchComposer;
15+
use OCA\NcTheming\AppInfo\Application;
16+
use OCA\NcTheming\Search\SearchComposerDecorator;
17+
use OCP\Collaboration\Collaborators\ISearchResult;
18+
use OCP\IURLGenerator;
19+
use OCP\IUser;
20+
use OCP\Search\ISearchQuery;
21+
use OCP\Search\SearchResult;
22+
use PHPUnit\Framework\TestCase;
23+
use Psr\Container\ContainerInterface;
24+
use Psr\Log\LoggerInterface;
25+
26+
27+
class SearchComposerDecoratorTest extends TestCase {
28+
29+
protected \OCP\AppFramework\App $app;
30+
protected IUser|\PHPUnit\Framework\MockObject\MockObject $user;
31+
protected ISearchQuery|\PHPUnit\Framework\MockObject\MockObject $query;
32+
protected SearchComposer|\PHPUnit\Framework\MockObject\MockObject $decoratedSearchComposer;
33+
protected SearchComposerDecorator $composerService;
34+
35+
protected function setUp(): void {
36+
parent::setUp();
37+
38+
// test behavior in combination with the core apps
39+
$this->app = new \OCP\AppFramework\App(Application::APP_ID);
40+
}
41+
42+
public function testGetProvidersDefault() {
43+
$this->composerService =
44+
new SearchComposerDecorator(
45+
new SearchComposer(
46+
$this->app->getContainer()->get(Coordinator::class),
47+
$this->app->getContainer()->get(ContainerInterface::class),
48+
$this->app->getContainer()->get(IURLGenerator::class),
49+
$this->app->getContainer()->get(LoggerInterface::class)
50+
),
51+
[
52+
]
53+
);
54+
$providers = array_values($this->composerService->getProviders('/', []));
55+
$providerIds = array_map(function ($p) {
56+
return $p['id'];
57+
}, $providers);
58+
59+
$this->assertContains('files', $providerIds);
60+
$this->assertContains('systemtags', $providerIds);
61+
$this->assertContains('comments', $providerIds);
62+
$this->assertContains('settings_apps', $providerIds);
63+
$this->assertContains('settings', $providerIds);
64+
}
65+
66+
public function testGetProvidersAllowed() {
67+
$this->composerService =
68+
new SearchComposerDecorator(
69+
new SearchComposer(
70+
$this->app->getContainer()->get(Coordinator::class),
71+
$this->app->getContainer()->get(ContainerInterface::class),
72+
$this->app->getContainer()->get(IURLGenerator::class),
73+
$this->app->getContainer()->get(LoggerInterface::class)
74+
),
75+
[
76+
'files',
77+
'settings',
78+
]
79+
);
80+
$providers = array_values($this->composerService->getProviders('/', []));
81+
$providerIds = array_map(function ($p) {
82+
return $p['id'];
83+
}, $providers);
84+
$this->assertContains('settings', $providerIds);
85+
$this->assertContains('files', $providerIds);
86+
$this->assertNotContains('settings_apps', $providerIds);
87+
$this->assertNotContains('systemtags', $providerIds);
88+
}
89+
90+
protected function createSearchMock(): void {
91+
$this->decoratedSearchComposer = $this->createMock(SearchComposer::class);
92+
$this->composerService =
93+
new SearchComposerDecorator(
94+
$this->decoratedSearchComposer,
95+
[
96+
'files',
97+
'settings',
98+
]
99+
);
100+
101+
$this->user = $this->createMock(IUser::class);
102+
$this->query = $this->createMock(ISearchQuery::class);
103+
}
104+
105+
public function testSearchDisabledProvider() {
106+
$this->createSearchMock();
107+
/** @var ISearchResult $searchResult */
108+
$searchResult = SearchResult::complete('files', []);
109+
$this->decoratedSearchComposer->expects(self::once())
110+
->method('search')
111+
->with($this->user, 'files', $this->query)
112+
->willReturn($searchResult);
113+
114+
$result = $this->composerService->search($this->user, 'files', $this->query)->jsonSerialize();
115+
116+
$this->assertEquals('files', $result['name']);
117+
$this->assertFalse($result['isPaginated']);
118+
$this->assertEmpty($result['entries']);
119+
}
120+
121+
public function testSearch() {
122+
$this->createSearchMock();
123+
$this->decoratedSearchComposer->expects(self::once())
124+
->method("search")
125+
->with($this->equalTo($this->user), $this->equalTo('files'), $this->equalTo($this->query))
126+
->willReturn(SearchResult::complete('files', []));
127+
$this->composerService->search($this->user, 'files', $this->query)->jsonSerialize();
128+
}
129+
}

0 commit comments

Comments
 (0)