Skip to content

Commit c04b88c

Browse files
committed
feat(unified-search): Use existing min search length config
This setting existed already for the legacy unified search. This commit expose that setting to the new front-end, and also ignore non valid requests in the backend. We also take the opportunity to register the config in the lexicon. Signed-off-by: Louis Chemineau <[email protected]>
1 parent e4e193a commit c04b88c

File tree

10 files changed

+91
-4
lines changed

10 files changed

+91
-4
lines changed

core/AppInfo/ConfigLexicon.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OC\Core\AppInfo;
10+
11+
use NCU\Config\Lexicon\ConfigLexiconEntry;
12+
use NCU\Config\Lexicon\ConfigLexiconStrictness;
13+
use NCU\Config\Lexicon\IConfigLexicon;
14+
use NCU\Config\ValueType;
15+
16+
/**
17+
* Config Lexicon for core.
18+
*
19+
* Please Add & Manage your Config Keys in that file and keep the Lexicon up to date!
20+
*/
21+
class ConfigLexicon implements IConfigLexicon {
22+
public const UNIFIED_SEARCH_MIN_SEARCH_LENGTH = 'unified_search_min_search_length';
23+
24+
public function getStrictness(): ConfigLexiconStrictness {
25+
return ConfigLexiconStrictness::IGNORE;
26+
}
27+
28+
public function getAppConfigs(): array {
29+
return [
30+
new ConfigLexiconEntry(self::UNIFIED_SEARCH_MIN_SEARCH_LENGTH, ValueType::INT, 1, 'Minimum search length to trigger the request', lazy: false),
31+
];
32+
}
33+
34+
public function getUserConfigs(): array {
35+
return [
36+
];
37+
}
38+
}

core/Application.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
* @package OC\Core
3838
*/
3939
class Application extends App {
40+
41+
public const APP_ID = 'core';
42+
4043
public function __construct() {
4144
parent::__construct('core');
4245

core/Controller/UnifiedSearchController.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
2020
use OCP\AppFramework\Http\DataResponse;
2121
use OCP\AppFramework\OCSController;
22+
use OCP\IL10N;
2223
use OCP\IRequest;
2324
use OCP\IURLGenerator;
2425
use OCP\IUserSession;
@@ -37,6 +38,7 @@ public function __construct(
3738
private SearchComposer $composer,
3839
private IRouter $router,
3940
private IURLGenerator $urlGenerator,
41+
private IL10N $l10n,
4042
) {
4143
parent::__construct('core', $request);
4244
}
@@ -101,6 +103,11 @@ public function search(
101103
} catch (UnsupportedFilter|InvalidArgumentException $e) {
102104
return new DataResponse($e->getMessage(), Http::STATUS_BAD_REQUEST);
103105
}
106+
107+
if ($filters->count() === 0) {
108+
return new DataResponse($this->l10n->t('No valid filters provided'), Http::STATUS_BAD_REQUEST);
109+
}
110+
104111
return new DataResponse(
105112
$this->composer->search(
106113
$this->userSession->getUser(),

core/src/components/UnifiedSearch/UnifiedSearchModal.vue

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
173173
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
174174
import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js'
175175
import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js'
176+
import { loadState } from '@nextcloud/initial-state'
176177
177178
import CustomDateRangeModal from './CustomDateRangeModal.vue'
178179
import FilterChip from './SearchFilterChip.vue'
@@ -271,6 +272,7 @@ export default defineComponent({
271272
showDateRangeModal: false,
272273
internalIsVisible: this.open,
273274
initialized: false,
275+
minSearchLength: loadState('unified-search', 'min-search-length', 1),
274276
}
275277
},
276278
@@ -283,6 +285,10 @@ export default defineComponent({
283285
return !this.isEmptySearch && this.results.length === 0
284286
},
285287
288+
isSearchQueryTooShort() {
289+
return this.searchQuery.length < this.minSearchLength
290+
},
291+
286292
showEmptyContentInfo() {
287293
return this.isEmptySearch || this.hasNoResults
288294
},
@@ -291,9 +297,16 @@ export default defineComponent({
291297
if (this.searching && this.hasNoResults) {
292298
return t('core', 'Searching …')
293299
}
294-
if (this.isEmptySearch) {
295-
return t('core', 'Start typing to search')
300+
301+
if (this.isSearchQueryTooShort) {
302+
switch (this.minSearchLength) {
303+
case 1:
304+
return t('core', 'Start typing to search')
305+
default:
306+
return t('core', 'Minimum search length is {minSearchLength} characters', { minSearchLength: this.minSearchLength })
307+
}
296308
}
309+
297310
return t('core', 'No matching results')
298311
},
299312
@@ -375,7 +388,7 @@ export default defineComponent({
375388
})
376389
},
377390
find(query: string, providersToSearchOverride = null) {
378-
if (query.length === 0) {
391+
if (this.isSearchQueryTooShort) {
379392
this.results = []
380393
this.searching = false
381394
return

lib/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,6 +1196,7 @@
11961196
'OC\\Contacts\\ContactsMenu\\Providers\\EMailProvider' => $baseDir . '/lib/private/Contacts/ContactsMenu/Providers/EMailProvider.php',
11971197
'OC\\Contacts\\ContactsMenu\\Providers\\LocalTimeProvider' => $baseDir . '/lib/private/Contacts/ContactsMenu/Providers/LocalTimeProvider.php',
11981198
'OC\\Contacts\\ContactsMenu\\Providers\\ProfileProvider' => $baseDir . '/lib/private/Contacts/ContactsMenu/Providers/ProfileProvider.php',
1199+
'OC\\Core\\AppInfo\\ConfigLexicon' => $baseDir . '/core/AppInfo/ConfigLexicon.php',
11991200
'OC\\Core\\Application' => $baseDir . '/core/Application.php',
12001201
'OC\\Core\\BackgroundJobs\\BackgroundCleanupUpdaterBackupsJob' => $baseDir . '/core/BackgroundJobs/BackgroundCleanupUpdaterBackupsJob.php',
12011202
'OC\\Core\\BackgroundJobs\\CheckForUserCertificates' => $baseDir . '/core/BackgroundJobs/CheckForUserCertificates.php',

lib/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1245,6 +1245,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
12451245
'OC\\Contacts\\ContactsMenu\\Providers\\EMailProvider' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/Providers/EMailProvider.php',
12461246
'OC\\Contacts\\ContactsMenu\\Providers\\LocalTimeProvider' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/Providers/LocalTimeProvider.php',
12471247
'OC\\Contacts\\ContactsMenu\\Providers\\ProfileProvider' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/Providers/ProfileProvider.php',
1248+
'OC\\Core\\AppInfo\\ConfigLexicon' => __DIR__ . '/../../..' . '/core/AppInfo/ConfigLexicon.php',
12481249
'OC\\Core\\Application' => __DIR__ . '/../../..' . '/core/Application.php',
12491250
'OC\\Core\\BackgroundJobs\\BackgroundCleanupUpdaterBackupsJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/BackgroundCleanupUpdaterBackupsJob.php',
12501251
'OC\\Core\\BackgroundJobs\\CheckForUserCertificates' => __DIR__ . '/../../..' . '/core/BackgroundJobs/CheckForUserCertificates.php',

lib/private/Search/FilterCollection.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,8 @@ public function getIterator(): Generator {
4040
yield $k => $v;
4141
}
4242
}
43+
44+
public function count(): int {
45+
return count($this->filters);
46+
}
4347
}

lib/private/Search/SearchComposer.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
use InvalidArgumentException;
1212
use OC\AppFramework\Bootstrap\Coordinator;
13+
use OC\Core\AppInfo\ConfigLexicon;
14+
use OC\Core\Application;
1315
use OC\Core\ResponseDefinitions;
1416
use OCP\IAppConfig;
1517
use OCP\IURLGenerator;
@@ -312,6 +314,12 @@ private function buildFilter(string $name, string $value, string $providerId): ?
312314
throw new UnsupportedFilter($name, $providerId);
313315
}
314316

317+
$minSearchLength = $this->appConfig->getValueInt(Application::APP_ID, ConfigLexicon::UNIFIED_SEARCH_MIN_SEARCH_LENGTH);
318+
if ($filterDefinition->name() === 'term' && mb_strlen(trim($value)) < $minSearchLength) {
319+
// Ignore term values that are not long enough
320+
return null;
321+
}
322+
315323
return FilterFactory::get($filterDefinition->type(), $value);
316324
}
317325

lib/private/TemplateLayout.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
use bantu\IniGetWrapper\IniGetWrapper;
1111
use OC\AppFramework\Http\Request;
1212
use OC\Authentication\Token\IProvider;
13+
use OC\Core\AppInfo\ConfigLexicon;
14+
use OC\Core\Application;
1315
use OC\Files\FilenameValidator;
1416
use OC\Search\SearchQuery;
1517
use OC\Template\CSSResourceLocator;
@@ -18,6 +20,7 @@
1820
use OCP\App\IAppManager;
1921
use OCP\AppFramework\Http\TemplateResponse;
2022
use OCP\Defaults;
23+
use OCP\IAppConfig;
2124
use OCP\IConfig;
2225
use OCP\IInitialStateService;
2326
use OCP\INavigationManager;
@@ -41,6 +44,7 @@ class TemplateLayout extends \OC_Template {
4144
public static $jsLocator = null;
4245

4346
private IConfig $config;
47+
private IAppConfig $appConfig;
4448
private IAppManager $appManager;
4549
private InitialStateService $initialState;
4650
private INavigationManager $navigationManager;
@@ -51,6 +55,7 @@ class TemplateLayout extends \OC_Template {
5155
*/
5256
public function __construct($renderAs, $appId = '') {
5357
$this->config = \OCP\Server::get(IConfig::class);
58+
$this->appConfig = \OCP\Server::get(IAppConfig::class);
5459
$this->appManager = \OCP\Server::get(IAppManager::class);
5560
$this->initialState = \OCP\Server::get(InitialStateService::class);
5661
$this->navigationManager = \OCP\Server::get(INavigationManager::class);
@@ -73,9 +78,9 @@ public function __construct($renderAs, $appId = '') {
7378
$this->initialState->provideInitialState('core', 'active-app', $this->navigationManager->getActiveEntry());
7479
$this->initialState->provideInitialState('core', 'apps', array_values($this->navigationManager->getAll()));
7580

81+
$this->initialState->provideInitialState('unified-search', 'min-search-length', $this->appConfig->getValueInt(Application::APP_ID, ConfigLexicon::UNIFIED_SEARCH_MIN_SEARCH_LENGTH));
7682
if ($this->config->getSystemValueBool('unified_search.enabled', false) || !$this->config->getSystemValueBool('enable_non-accessible_features', true)) {
7783
$this->initialState->provideInitialState('unified-search', 'limit-default', (int)$this->config->getAppValue('core', 'unified-search.limit-default', (string)SearchQuery::LIMIT_DEFAULT));
78-
$this->initialState->provideInitialState('unified-search', 'min-search-length', (int)$this->config->getAppValue('core', 'unified-search.min-search-length', (string)1));
7984
$this->initialState->provideInitialState('unified-search', 'live-search', $this->config->getAppValue('core', 'unified-search.live-search', 'yes') === 'yes');
8085
Util::addScript('core', 'legacy-unified-search', 'core');
8186
} else {

lib/public/Search/IFilterCollection.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,11 @@ public function get(string $name): ?IFilter;
3636
* @since 28.0.0
3737
*/
3838
public function getIterator(): \Traversable;
39+
40+
/**
41+
* Return the number of filters
42+
*
43+
* @since 31.0.10
44+
*/
45+
public function count(): int;
3946
}

0 commit comments

Comments
 (0)