use OC\Search\SearchComposer;
use OC\Search\SearchQuery;
use OCA\Core\ResponseDefinitions;
-use OCP\AppFramework\OCSController;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCSController;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\Route\IRouter;
use OCP\Search\ISearchQuery;
+use OC\Search\UnsupportedFilter;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
/**
* @NoAdminRequired
* @NoCSRFRequired
*
- * Search
+ * Launch a search for a specific search provider.
+ *
+ * Additional filters are available for each provider.
+ * Send a request to /providers endpoint to list providers with their available filters.
*
* @param string $providerId ID of the provider
* @param string $term Term to search
* @param int|string|null $cursor Offset for searching
* @param string $from The current user URL
*
- * @return DataResponse<Http::STATUS_OK, CoreUnifiedSearchResult, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, null, array{}>
+ * @return DataResponse<Http::STATUS_OK, CoreUnifiedSearchResult, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, string, array{}>
*
* 200: Search entries returned
* 400: Searching is not possible
*/
- public function search(string $providerId,
- string $term = '',
- ?int $sortOrder = null,
- ?int $limit = null,
- $cursor = null,
- string $from = ''): DataResponse {
- if (trim($term) === "") {
- return new DataResponse(null, Http::STATUS_BAD_REQUEST);
- }
+ public function search(
+ string $providerId,
+ // Unused parameter for OpenAPI spec generator
+ string $term = '',
+ ?int $sortOrder = null,
+ ?int $limit = null,
+ $cursor = null,
+ string $from = '',
+ ): DataResponse {
[$route, $routeParameters] = $this->getRouteInformation($from);
+ try {
+ $filters = $this->composer->buildFilterList($providerId, $this->request->getParams());
+ } catch (UnsupportedFilter $e) {
+ return new DataResponse($e->getMessage(), Http::STATUS_BAD_REQUEST);
+ }
return new DataResponse(
$this->composer->search(
$this->userSession->getUser(),
$providerId,
new SearchQuery(
- $term,
+ $filters,
$sortOrder ?? ISearchQuery::SORT_DATE_DESC,
$limit ?? SearchQuery::LIMIT_DEFAULT,
$cursor,
"description": "Controller about the endpoint /ocm-provider/"
}
]
-}
\ No newline at end of file
+}
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
- return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
+ return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
}
}
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
- $constraint = $parser->parseConstraints($constraint);
+ $constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
- $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
+ $required = require $vendorDir.'/composer/installed.php';
+ $installed[] = self::$installedByVendor[$vendorDir] = $required;
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
- self::$installed = require __DIR__ . '/installed.php';
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
+ $required = require __DIR__ . '/installed.php';
+ self::$installed = $required;
} else {
self::$installed = array();
}
}
- $installed[] = self::$installed;
+
+ if (self::$installed !== array()) {
+ $installed[] = self::$installed;
+ }
return $installed;
}
$baseDir = dirname(dirname($vendorDir));
return array(
+ 'Bamarni\\Composer\\Bin\\ApplicationFactory\\FreshInstanceApplicationFactory' => $vendorDir . '/bamarni/composer-bin-plugin/src/ApplicationFactory/FreshInstanceApplicationFactory.php',
+ 'Bamarni\\Composer\\Bin\\ApplicationFactory\\NamespaceApplicationFactory' => $vendorDir . '/bamarni/composer-bin-plugin/src/ApplicationFactory/NamespaceApplicationFactory.php',
+ 'Bamarni\\Composer\\Bin\\BamarniBinPlugin' => $vendorDir . '/bamarni/composer-bin-plugin/src/BamarniBinPlugin.php',
+ 'Bamarni\\Composer\\Bin\\CommandProvider' => $vendorDir . '/bamarni/composer-bin-plugin/src/CommandProvider.php',
+ 'Bamarni\\Composer\\Bin\\Command\\BinCommand' => $vendorDir . '/bamarni/composer-bin-plugin/src/Command/BinCommand.php',
+ 'Bamarni\\Composer\\Bin\\Command\\CouldNotCreateNamespaceDir' => $vendorDir . '/bamarni/composer-bin-plugin/src/Command/CouldNotCreateNamespaceDir.php',
+ 'Bamarni\\Composer\\Bin\\Config\\Config' => $vendorDir . '/bamarni/composer-bin-plugin/src/Config/Config.php',
+ 'Bamarni\\Composer\\Bin\\Config\\ConfigFactory' => $vendorDir . '/bamarni/composer-bin-plugin/src/Config/ConfigFactory.php',
+ 'Bamarni\\Composer\\Bin\\Config\\InvalidBamarniComposerExtraConfig' => $vendorDir . '/bamarni/composer-bin-plugin/src/Config/InvalidBamarniComposerExtraConfig.php',
+ 'Bamarni\\Composer\\Bin\\Input\\BinInputFactory' => $vendorDir . '/bamarni/composer-bin-plugin/src/Input/BinInputFactory.php',
+ 'Bamarni\\Composer\\Bin\\Input\\InvalidBinInput' => $vendorDir . '/bamarni/composer-bin-plugin/src/Input/InvalidBinInput.php',
+ 'Bamarni\\Composer\\Bin\\Logger' => $vendorDir . '/bamarni/composer-bin-plugin/src/Logger.php',
+ 'Bamarni\\Composer\\Bin\\PublicIO' => $vendorDir . '/bamarni/composer-bin-plugin/src/PublicIO.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'OCP\\Accounts\\IAccount' => $baseDir . '/lib/public/Accounts/IAccount.php',
'OCP\\Accounts\\IAccountManager' => $baseDir . '/lib/public/Accounts/IAccountManager.php',
'OCP\\Route\\IRouter' => $baseDir . '/lib/public/Route/IRouter.php',
'OCP\\SabrePluginEvent' => $baseDir . '/lib/public/SabrePluginEvent.php',
'OCP\\SabrePluginException' => $baseDir . '/lib/public/SabrePluginException.php',
+ 'OCP\\Search\\FilterDefinition' => $baseDir . '/lib/public/Search/FilterDefinition.php',
+ 'OCP\\Search\\IFilter' => $baseDir . '/lib/public/Search/IFilter.php',
+ 'OCP\\Search\\IFilterCollection' => $baseDir . '/lib/public/Search/IFilterCollection.php',
+ 'OCP\\Search\\IFilteringProvider' => $baseDir . '/lib/public/Search/IFilteringProvider.php',
'OCP\\Search\\IProvider' => $baseDir . '/lib/public/Search/IProvider.php',
'OCP\\Search\\ISearchQuery' => $baseDir . '/lib/public/Search/ISearchQuery.php',
'OCP\\Search\\PagedProvider' => $baseDir . '/lib/public/Search/PagedProvider.php',
'OC\\Route\\Route' => $baseDir . '/lib/private/Route/Route.php',
'OC\\Route\\Router' => $baseDir . '/lib/private/Route/Router.php',
'OC\\Search' => $baseDir . '/lib/private/Search.php',
+ 'OC\\Search\\FilterCollection' => $baseDir . '/lib/private/Search/FilterCollection.php',
+ 'OC\\Search\\FilterFactory' => $baseDir . '/lib/private/Search/FilterFactory.php',
+ 'OC\\Search\\Filter\\BooleanFilter' => $baseDir . '/lib/private/Search/Filter/BooleanFilter.php',
+ 'OC\\Search\\Filter\\DateTimeFilter' => $baseDir . '/lib/private/Search/Filter/DateTimeFilter.php',
+ 'OC\\Search\\Filter\\FloatFilter' => $baseDir . '/lib/private/Search/Filter/FloatFilter.php',
+ 'OC\\Search\\Filter\\GroupFilter' => $baseDir . '/lib/private/Search/Filter/GroupFilter.php',
+ 'OC\\Search\\Filter\\IntegerFilter' => $baseDir . '/lib/private/Search/Filter/IntegerFilter.php',
+ 'OC\\Search\\Filter\\StringFilter' => $baseDir . '/lib/private/Search/Filter/StringFilter.php',
+ 'OC\\Search\\Filter\\StringsFilter' => $baseDir . '/lib/private/Search/Filter/StringsFilter.php',
+ 'OC\\Search\\Filter\\UserFilter' => $baseDir . '/lib/private/Search/Filter/UserFilter.php',
'OC\\Search\\Provider\\File' => $baseDir . '/lib/private/Search/Provider/File.php',
'OC\\Search\\Result\\Audio' => $baseDir . '/lib/private/Search/Result/Audio.php',
'OC\\Search\\Result\\File' => $baseDir . '/lib/private/Search/Result/File.php',
'OC\\Search\\Result\\Image' => $baseDir . '/lib/private/Search/Result/Image.php',
'OC\\Search\\SearchComposer' => $baseDir . '/lib/private/Search/SearchComposer.php',
'OC\\Search\\SearchQuery' => $baseDir . '/lib/private/Search/SearchQuery.php',
+ 'OC\\Search\\UnsupportedFilter' => $baseDir . '/lib/private/Search/UnsupportedFilter.php',
'OC\\Security\\Bruteforce\\Backend\\DatabaseBackend' => $baseDir . '/lib/private/Security/Bruteforce/Backend/DatabaseBackend.php',
'OC\\Security\\Bruteforce\\Backend\\IBackend' => $baseDir . '/lib/private/Security/Bruteforce/Backend/IBackend.php',
'OC\\Security\\Bruteforce\\Backend\\MemoryCacheBackend' => $baseDir . '/lib/private/Security/Bruteforce/Backend/MemoryCacheBackend.php',
'OC\\Core\\' => array($baseDir . '/core'),
'OC\\' => array($baseDir . '/lib/private'),
'OCP\\' => array($baseDir . '/lib/public'),
+ 'Bamarni\\Composer\\Bin\\' => array($vendorDir . '/bamarni/composer-bin-plugin/src'),
'' => array($baseDir . '/lib/private/legacy'),
);
'OC\\' => 3,
'OCP\\' => 4,
),
+ 'B' =>
+ array (
+ 'Bamarni\\Composer\\Bin\\' => 21,
+ ),
);
public static $prefixDirsPsr4 = array (
array (
0 => __DIR__ . '/../../..' . '/lib/public',
),
+ 'Bamarni\\Composer\\Bin\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src',
+ ),
);
public static $fallbackDirsPsr4 = array (
);
public static $classMap = array (
+ 'Bamarni\\Composer\\Bin\\ApplicationFactory\\FreshInstanceApplicationFactory' => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src/ApplicationFactory/FreshInstanceApplicationFactory.php',
+ 'Bamarni\\Composer\\Bin\\ApplicationFactory\\NamespaceApplicationFactory' => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src/ApplicationFactory/NamespaceApplicationFactory.php',
+ 'Bamarni\\Composer\\Bin\\BamarniBinPlugin' => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src/BamarniBinPlugin.php',
+ 'Bamarni\\Composer\\Bin\\CommandProvider' => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src/CommandProvider.php',
+ 'Bamarni\\Composer\\Bin\\Command\\BinCommand' => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src/Command/BinCommand.php',
+ 'Bamarni\\Composer\\Bin\\Command\\CouldNotCreateNamespaceDir' => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src/Command/CouldNotCreateNamespaceDir.php',
+ 'Bamarni\\Composer\\Bin\\Config\\Config' => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src/Config/Config.php',
+ 'Bamarni\\Composer\\Bin\\Config\\ConfigFactory' => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src/Config/ConfigFactory.php',
+ 'Bamarni\\Composer\\Bin\\Config\\InvalidBamarniComposerExtraConfig' => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src/Config/InvalidBamarniComposerExtraConfig.php',
+ 'Bamarni\\Composer\\Bin\\Input\\BinInputFactory' => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src/Input/BinInputFactory.php',
+ 'Bamarni\\Composer\\Bin\\Input\\InvalidBinInput' => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src/Input/InvalidBinInput.php',
+ 'Bamarni\\Composer\\Bin\\Logger' => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src/Logger.php',
+ 'Bamarni\\Composer\\Bin\\PublicIO' => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src/PublicIO.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'OCP\\Accounts\\IAccount' => __DIR__ . '/../../..' . '/lib/public/Accounts/IAccount.php',
'OCP\\Accounts\\IAccountManager' => __DIR__ . '/../../..' . '/lib/public/Accounts/IAccountManager.php',
'OCP\\Route\\IRouter' => __DIR__ . '/../../..' . '/lib/public/Route/IRouter.php',
'OCP\\SabrePluginEvent' => __DIR__ . '/../../..' . '/lib/public/SabrePluginEvent.php',
'OCP\\SabrePluginException' => __DIR__ . '/../../..' . '/lib/public/SabrePluginException.php',
+ 'OCP\\Search\\FilterDefinition' => __DIR__ . '/../../..' . '/lib/public/Search/FilterDefinition.php',
+ 'OCP\\Search\\IFilter' => __DIR__ . '/../../..' . '/lib/public/Search/IFilter.php',
+ 'OCP\\Search\\IFilterCollection' => __DIR__ . '/../../..' . '/lib/public/Search/IFilterCollection.php',
+ 'OCP\\Search\\IFilteringProvider' => __DIR__ . '/../../..' . '/lib/public/Search/IFilteringProvider.php',
'OCP\\Search\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Search/IProvider.php',
'OCP\\Search\\ISearchQuery' => __DIR__ . '/../../..' . '/lib/public/Search/ISearchQuery.php',
'OCP\\Search\\PagedProvider' => __DIR__ . '/../../..' . '/lib/public/Search/PagedProvider.php',
'OC\\Route\\Route' => __DIR__ . '/../../..' . '/lib/private/Route/Route.php',
'OC\\Route\\Router' => __DIR__ . '/../../..' . '/lib/private/Route/Router.php',
'OC\\Search' => __DIR__ . '/../../..' . '/lib/private/Search.php',
+ 'OC\\Search\\FilterCollection' => __DIR__ . '/../../..' . '/lib/private/Search/FilterCollection.php',
+ 'OC\\Search\\FilterFactory' => __DIR__ . '/../../..' . '/lib/private/Search/FilterFactory.php',
+ 'OC\\Search\\Filter\\BooleanFilter' => __DIR__ . '/../../..' . '/lib/private/Search/Filter/BooleanFilter.php',
+ 'OC\\Search\\Filter\\DateTimeFilter' => __DIR__ . '/../../..' . '/lib/private/Search/Filter/DateTimeFilter.php',
+ 'OC\\Search\\Filter\\FloatFilter' => __DIR__ . '/../../..' . '/lib/private/Search/Filter/FloatFilter.php',
+ 'OC\\Search\\Filter\\GroupFilter' => __DIR__ . '/../../..' . '/lib/private/Search/Filter/GroupFilter.php',
+ 'OC\\Search\\Filter\\IntegerFilter' => __DIR__ . '/../../..' . '/lib/private/Search/Filter/IntegerFilter.php',
+ 'OC\\Search\\Filter\\StringFilter' => __DIR__ . '/../../..' . '/lib/private/Search/Filter/StringFilter.php',
+ 'OC\\Search\\Filter\\StringsFilter' => __DIR__ . '/../../..' . '/lib/private/Search/Filter/StringsFilter.php',
+ 'OC\\Search\\Filter\\UserFilter' => __DIR__ . '/../../..' . '/lib/private/Search/Filter/UserFilter.php',
'OC\\Search\\Provider\\File' => __DIR__ . '/../../..' . '/lib/private/Search/Provider/File.php',
'OC\\Search\\Result\\Audio' => __DIR__ . '/../../..' . '/lib/private/Search/Result/Audio.php',
'OC\\Search\\Result\\File' => __DIR__ . '/../../..' . '/lib/private/Search/Result/File.php',
'OC\\Search\\Result\\Image' => __DIR__ . '/../../..' . '/lib/private/Search/Result/Image.php',
'OC\\Search\\SearchComposer' => __DIR__ . '/../../..' . '/lib/private/Search/SearchComposer.php',
'OC\\Search\\SearchQuery' => __DIR__ . '/../../..' . '/lib/private/Search/SearchQuery.php',
+ 'OC\\Search\\UnsupportedFilter' => __DIR__ . '/../../..' . '/lib/private/Search/UnsupportedFilter.php',
'OC\\Security\\Bruteforce\\Backend\\DatabaseBackend' => __DIR__ . '/../../..' . '/lib/private/Security/Bruteforce/Backend/DatabaseBackend.php',
'OC\\Security\\Bruteforce\\Backend\\IBackend' => __DIR__ . '/../../..' . '/lib/private/Security/Bruteforce/Backend/IBackend.php',
'OC\\Security\\Bruteforce\\Backend\\MemoryCacheBackend' => __DIR__ . '/../../..' . '/lib/private/Security/Bruteforce/Backend/MemoryCacheBackend.php',
{
- "packages": [],
- "dev": false,
- "dev-package-names": []
+ "packages": [
+ {
+ "name": "bamarni/composer-bin-plugin",
+ "version": "1.8.2",
+ "version_normalized": "1.8.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/bamarni/composer-bin-plugin.git",
+ "reference": "92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/bamarni/composer-bin-plugin/zipball/92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880",
+ "reference": "92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^2.0",
+ "php": "^7.2.5 || ^8.0"
+ },
+ "require-dev": {
+ "composer/composer": "^2.0",
+ "ext-json": "*",
+ "phpstan/extension-installer": "^1.1",
+ "phpstan/phpstan": "^1.8",
+ "phpstan/phpstan-phpunit": "^1.1",
+ "phpunit/phpunit": "^8.5 || ^9.5",
+ "symfony/console": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0",
+ "symfony/finder": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0",
+ "symfony/process": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0"
+ },
+ "time": "2022-10-31T08:38:03+00:00",
+ "type": "composer-plugin",
+ "extra": {
+ "class": "Bamarni\\Composer\\Bin\\BamarniBinPlugin"
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Bamarni\\Composer\\Bin\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "No conflicts for your bin dependencies",
+ "keywords": [
+ "composer",
+ "conflict",
+ "dependency",
+ "executable",
+ "isolation",
+ "tool"
+ ],
+ "support": {
+ "issues": "https://github.com/bamarni/composer-bin-plugin/issues",
+ "source": "https://github.com/bamarni/composer-bin-plugin/tree/1.8.2"
+ },
+ "install-path": "../bamarni/composer-bin-plugin"
+ }
+ ],
+ "dev": true,
+ "dev-package-names": [
+ "bamarni/composer-bin-plugin"
+ ]
}
<?php return array(
'root' => array(
- 'pretty_version' => '1.0.0+no-version-set',
- 'version' => '1.0.0.0',
+ 'name' => '__root__',
+ 'pretty_version' => 'dev-master',
+ 'version' => 'dev-master',
+ 'reference' => '1ad489970cd05a57df6649d0e601bfa46eba8564',
'type' => 'library',
'install_path' => __DIR__ . '/../../../',
'aliases' => array(),
- 'reference' => NULL,
- 'name' => '__root__',
- 'dev' => false,
+ 'dev' => true,
),
'versions' => array(
'__root__' => array(
- 'pretty_version' => '1.0.0+no-version-set',
- 'version' => '1.0.0.0',
+ 'pretty_version' => 'dev-master',
+ 'version' => 'dev-master',
+ 'reference' => '1ad489970cd05a57df6649d0e601bfa46eba8564',
'type' => 'library',
'install_path' => __DIR__ . '/../../../',
'aliases' => array(),
- 'reference' => NULL,
'dev_requirement' => false,
),
+ 'bamarni/composer-bin-plugin' => array(
+ 'pretty_version' => '1.8.2',
+ 'version' => '1.8.2.0',
+ 'reference' => '92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880',
+ 'type' => 'composer-plugin',
+ 'install_path' => __DIR__ . '/../bamarni/composer-bin-plugin',
+ 'aliases' => array(),
+ 'dev_requirement' => true,
+ ),
),
);
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\Search\IFilter;
+
+class BooleanFilter implements IFilter {
+ private bool $value;
+
+ public function __construct(string $value) {
+ $this->value = match ($value) {
+ 'true', 'yes', 'y', '1' => true,
+ 'false', 'no', 'n', '0', '' => false,
+ default => throw new InvalidArgumentException('Invalid boolean value '. $value),
+ };
+ }
+
+ public function get(): bool {
+ return $this->value;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Search\Filter;
+
+use DateTimeImmutable;
+use OCP\Search\IFilter;
+
+class DateTimeFilter implements IFilter {
+ private DateTimeImmutable $value;
+
+ public function __construct(string $value) {
+ if (filter_var($value, FILTER_VALIDATE_INT)) {
+ $value = '@'.$value;
+ }
+
+ $this->value = new DateTimeImmutable($value);
+ }
+
+ public function get(): DateTimeImmutable {
+ return $this->value;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\Search\IFilter;
+
+class FloatFilter implements IFilter {
+ private float $value;
+
+ public function __construct(string $value) {
+ $this->value = filter_var($value, FILTER_VALIDATE_FLOAT);
+ if ($this->value === false) {
+ throw new InvalidArgumentException('Invalid float value '. $value);
+ }
+ }
+
+ public function get(): float {
+ return $this->value;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\IGroup;
+use OCP\IGroupManager;
+use OCP\Search\IFilter;
+
+class GroupFilter implements IFilter {
+ private IGroup $group;
+
+ public function __construct(
+ string $value,
+ IGroupManager $groupManager,
+ ) {
+ $this->group = $groupManager->get($value);
+ if ($this->group === null) {
+ throw new InvalidArgumentException('Group '.$value.' not found');
+ }
+ }
+
+ public function get(): IGroup {
+ return $this->group;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\Search\IFilter;
+
+class IntegerFilter implements IFilter {
+ private int $value;
+
+ public function __construct(string $value) {
+ $this->value = filter_var($value, FILTER_VALIDATE_INT);
+ if ($this->value === false) {
+ throw new InvalidArgumentException('Invalid integer value '. $value);
+ }
+ }
+
+ public function get(): int {
+ return $this->value;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\Search\IFilter;
+
+class StringFilter implements IFilter {
+ public function __construct(
+ private string $value,
+ ) {
+ if ($value === '') {
+ throw new InvalidArgumentException('String filter can’t be empty');
+ }
+ }
+
+ public function get(): string {
+ return $this->value;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\Search\IFilter;
+
+class StringsFilter implements IFilter {
+ /**
+ * @var string[]
+ */
+ private array $values;
+
+ public function __construct(string ...$values) {
+ $this->values = array_unique(array_filter($values));
+ if (empty($this->values)) {
+ throw new InvalidArgumentException('Strings filter can’t be empty');
+ }
+ }
+
+ /**
+ * @return string[]
+ */
+ public function get(): array {
+ return $this->values;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\Search\IFilter;
+
+class UserFilter implements IFilter {
+ private IUser $user;
+
+ public function __construct(
+ string $value,
+ IUserManager $userManager,
+ ) {
+ $this->user = $userManager->get($value);
+ if ($this->user === null) {
+ throw new InvalidArgumentException('User '.$value.' not found');
+ }
+ }
+
+ public function get(): IUser {
+ return $this->user;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OC\Search;
+
+use Generator;
+use OCP\Search\IFilterCollection;
+use OCP\Search\IFilter;
+
+/**
+ * Interface for search filters
+ *
+ * @since 28.0.0
+ */
+class FilterCollection implements IFilterCollection {
+ /**
+ * @var IFilter[]
+ */
+ private array $filters;
+
+ public function __construct(IFilter ...$filters) {
+ $this->filters = $filters;
+ }
+
+ public function has(string $name): bool {
+ return isset($this->filters[$name]);
+ }
+
+ public function get(string $name): ?IFilter {
+ return $this->filters[$name] ?? null;
+ }
+
+ public function getIterator(): Generator {
+ foreach ($this->filters as $k => $v) {
+ yield $k => $v;
+ }
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OC\Search;
+
+use OCP\Search\FilterDefinition;
+use OCP\Search\IFilter;
+use OCP\IGroupManager;
+use OCP\IUserManager;
+use RuntimeException;
+
+final class FilterFactory {
+ public static function get(string $type, string|array $filter): IFilter {
+ return match ($type) {
+ FilterDefinition::TYPE_BOOL => new Filter\BooleanFilter($filter),
+ FilterDefinition::TYPE_DATETIME => new Filter\DateTimeFilter($filter),
+ FilterDefinition::TYPE_FLOAT => new Filter\FloatFilter($filter),
+ FilterDefinition::TYPE_INT => new Filter\IntegerFilter($filter),
+ FilterDefinition::TYPE_NC_GROUP => new Filter\GroupFilter($filter, \OC::$server->get(IGroupManager::class)),
+ FilterDefinition::TYPE_NC_USER => new Filter\UserFilter($filter, \OC::$server->get(IUserManager::class)),
+ FilterDefinition::TYPE_PERSON => self::getPerson($filter),
+ FilterDefinition::TYPE_STRING => new Filter\StringFilter($filter),
+ FilterDefinition::TYPE_STRINGS => new Filter\StringsFilter(... (array) $filter),
+ default => throw new RuntimeException('Invalid filter type '. $type),
+ };
+ }
+
+ private static function getPerson(string $person): IFilter {
+ $parts = explode('_', $person, 2);
+
+ return match (count($parts)) {
+ 1 => self::get(FilterDefinition::TYPE_NC_USER, $person),
+ 2 => self::get(... $parts),
+ };
+ }
+}
namespace OC\Search;
use InvalidArgumentException;
-use OCP\AppFramework\QueryException;
-use OCP\IServerContainer;
+use OCP\IURLGenerator;
+use OCP\Search\FilterDefinition;
+use OCP\Search\IFilteringProvider;
+use OC\AppFramework\Bootstrap\Coordinator;
use OCP\IUser;
+use OCP\Search\IFilter;
use OCP\Search\IProvider;
use OCP\Search\ISearchQuery;
use OCP\Search\SearchResult;
-use OC\AppFramework\Bootstrap\Coordinator;
+use Psr\Container\ContainerExceptionInterface;
+use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
+use RuntimeException;
use function array_map;
/**
* @see IProvider::search() for the arguments of the individual search requests
*/
class SearchComposer {
- /** @var IProvider[] */
- private $providers = [];
-
- /** @var Coordinator */
- private $bootstrapCoordinator;
+ /**
+ * @var array<string, array{appId: string, provider: IProvider}>
+ */
+ private array $providers = [];
- /** @var IServerContainer */
- private $container;
+ private array $commonFilters;
+ private array $customFilters = [];
- private LoggerInterface $logger;
+ private array $handlers = [];
- public function __construct(Coordinator $bootstrapCoordinator,
- IServerContainer $container,
- LoggerInterface $logger) {
- $this->container = $container;
- $this->logger = $logger;
- $this->bootstrapCoordinator = $bootstrapCoordinator;
+ public function __construct(
+ private Coordinator $bootstrapCoordinator,
+ private ContainerInterface $container,
+ private IURLGenerator $urlGenerator,
+ private LoggerInterface $logger
+ ) {
+ $this->commonFilters = [
+ 'term' => new FilterDefinition('term', FilterDefinition::TYPE_STRING),
+ 'since' => new FilterDefinition('since', FilterDefinition::TYPE_DATETIME),
+ 'until' => new FilterDefinition('until', FilterDefinition::TYPE_DATETIME),
+ 'title-only' => new FilterDefinition('title-only', FilterDefinition::TYPE_BOOL, false),
+ 'person' => new FilterDefinition('person', FilterDefinition::TYPE_PERSON),
+ 'places' => new FilterDefinition('places', FilterDefinition::TYPE_STRINGS, false),
+ 'provider' => new FilterDefinition('provider', FilterDefinition::TYPE_STRING, false),
+ ];
}
/**
* Load all providers dynamically that were registered through `registerProvider`
*
+ * If $targetProviderId is provided, only this provider is loaded
* If a provider can't be loaded we log it but the operation continues nevertheless
*/
- private function loadLazyProviders(): void {
+ private function loadLazyProviders(?string $targetProviderId = null): void {
$context = $this->bootstrapCoordinator->getRegistrationContext();
if ($context === null) {
// Too early, nothing registered yet
foreach ($registrations as $registration) {
try {
/** @var IProvider $provider */
- $provider = $this->container->query($registration->getService());
- $this->providers[$provider->getId()] = $provider;
- } catch (QueryException $e) {
+ $provider = $this->container->get($registration->getService());
+ $providerId = $provider->getId();
+ if ($targetProviderId !== null && $targetProviderId !== $providerId) {
+ continue;
+ }
+ $this->providers[$providerId] = [
+ 'appId' => $registration->getAppId(),
+ 'provider' => $provider,
+ ];
+ $this->handlers[$providerId] = [$providerId];
+ if ($targetProviderId !== null) {
+ break;
+ }
+ } catch (ContainerExceptionInterface $e) {
// Log an continue. We can be fault tolerant here.
$this->logger->error('Could not load search provider dynamically: ' . $e->getMessage(), [
'exception' => $e,
]);
}
}
+
+ $this->loadFilters();
+ }
+
+ private function loadFilters(): void {
+ foreach ($this->providers as $providerId => $providerData) {
+ $appId = $providerData['appId'];
+ $provider = $providerData['provider'];
+ if (!$provider instanceof IFilteringProvider) {
+ continue;
+ }
+
+ foreach ($provider->getCustomFilters() as $filter) {
+ $this->registerCustomFilter($filter, $providerId);
+ }
+ foreach ($provider->getAlternateIds() as $alternateId) {
+ $this->handlers[$alternateId][] = $providerId;
+ }
+ foreach ($provider->getSupportedFilters() as $filterName) {
+ if ($this->getFilterDefinition($filterName, $providerId) === null) {
+ throw new InvalidArgumentException('Invalid filter '. $filterName);
+ }
+ }
+ }
+ }
+
+ private function registerCustomFilter(FilterDefinition $filter, string $providerId): void {
+ $name = $filter->name();
+ if (isset($this->commonFilters[$name])) {
+ throw new InvalidArgumentException('Filter name is already used');
+ }
+
+ if (isset($this->customFilters[$providerId])) {
+ $this->customFilters[$providerId][$name] = $filter;
+ } else {
+ $this->customFilters[$providerId] = [$name => $filter];
+ }
}
/**
public function getProviders(string $route, array $routeParameters): array {
$this->loadLazyProviders();
- $providers = array_values(
- array_map(function (IProvider $provider) use ($route, $routeParameters) {
+ $providers = array_map(
+ function (array $providerData) use ($route, $routeParameters) {
+ $appId = $providerData['appId'];
+ $provider = $providerData['provider'];
+ $triggers = [$provider->getId()];
+ if ($provider instanceof IFilteringProvider) {
+ $triggers += $provider->getAlternateIds();
+ $filters = $provider->getSupportedFilters();
+ } else {
+ $filters = ['term'];
+ }
+
return [
'id' => $provider->getId(),
+ 'appId' => $appId,
'name' => $provider->getName(),
+ 'icon' => $this->fetchIcon($appId, $provider->getId()),
'order' => $provider->getOrder($route, $routeParameters),
+ 'triggers' => $triggers,
+ 'filters' => $this->getFiltersType($filters, $provider->getId()),
];
- }, $this->providers)
+ },
+ $this->providers,
);
+ // Sort providers by order and strip associative keys
usort($providers, function ($provider1, $provider2) {
return $provider1['order'] <=> $provider2['order'];
});
- /**
- * Return an array with the IDs, but strip the associative keys
- */
return $providers;
}
+ private function fetchIcon(string $appId, string $providerId): string {
+ $icons = [
+ [$providerId, $providerId.'.svg'],
+ [$providerId, 'app.svg'],
+ [$appId, $providerId.'.svg'],
+ [$appId, $appId.'.svg'],
+ [$appId, 'app.svg'],
+ ['core', 'places/default-app-icon.svg'],
+ ];
+ foreach ($icons as $i => $icon) {
+ try {
+ return $this->urlGenerator->imagePath(... $icon);
+ } catch (RuntimeException $e) {
+ // Ignore error
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * @param $filters string[]
+ * @return array<string, string>
+ */
+ private function getFiltersType(array $filters, string $providerId): array {
+ $filterList = [];
+ foreach ($filters as $filter) {
+ $filterList[$filter] = $this->getFilterDefinition($filter, $providerId)->type();
+ }
+
+ return $filterList;
+ }
+
+ private function getFilterDefinition(string $name, string $providerId): ?FilterDefinition {
+ if (isset($this->commonFilters[$name])) {
+ return $this->commonFilters[$name];
+ }
+ if (isset($this->customFilters[$providerId][$name])) {
+ return $this->customFilters[$providerId][$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * @param array<string, string> $parameters
+ */
+ public function buildFilterList(string $providerId, array $parameters): FilterCollection {
+ $this->loadLazyProviders($providerId);
+
+ $list = [];
+ foreach ($parameters as $name => $value) {
+ $filter = $this->buildFilter($name, $value, $providerId);
+ if ($filter === null) {
+ continue;
+ }
+ $list[$name] = $filter;
+ }
+
+ return new FilterCollection(... $list);
+ }
+
+ private function buildFilter(string $name, string $value, string $providerId): ?IFilter {
+ $filterDefinition = $this->getFilterDefinition($name, $providerId);
+ if ($filterDefinition === null) {
+ $this->logger->debug('Unable to find {name} definition', [
+ 'name' => $name,
+ 'value' => $value,
+ ]);
+
+ return null;
+ }
+
+ if (!$this->filterSupportedByProvider($filterDefinition, $providerId)) {
+ // FIXME Use dedicated exception and handle it
+ throw new UnsupportedFilter($name, $providerId);
+ }
+
+ return FilterFactory::get($filterDefinition->type(), $value);
+ }
+
+ private function filterSupportedByProvider(FilterDefinition $filterDefinition, string $providerId): bool {
+ // Non exclusive filters can be ommited by apps
+ if (!$filterDefinition->exclusive()) {
+ return true;
+ }
+
+ $provider = $this->providers[$providerId]['provider'];
+ $supportedFilters = $provider instanceof IFilteringProvider
+ ? $provider->getSupportedFilters()
+ : ['term'];
+
+ return in_array($filterDefinition->name(), $supportedFilters, true);
+ }
+
/**
* Query an individual search provider for results
*
* @return SearchResult
* @throws InvalidArgumentException when the $providerId does not correspond to a registered provider
*/
- public function search(IUser $user,
- string $providerId,
- ISearchQuery $query): SearchResult {
- $this->loadLazyProviders();
+ public function search(
+ IUser $user,
+ string $providerId,
+ ISearchQuery $query,
+ ): SearchResult {
+ $this->loadLazyProviders($providerId);
- $provider = $this->providers[$providerId] ?? null;
+ $provider = $this->providers[$providerId]['provider'] ?? null;
if ($provider === null) {
throw new InvalidArgumentException("Provider $providerId is unknown");
}
+
return $provider->search($user, $query);
}
}
*/
namespace OC\Search;
+use OCP\Search\IFilterCollection;
+use OCP\Search\IFilter;
use OCP\Search\ISearchQuery;
class SearchQuery implements ISearchQuery {
public const LIMIT_DEFAULT = 5;
- /** @var string */
- private $term;
-
- /** @var int */
- private $sortOrder;
-
- /** @var int */
- private $limit;
-
- /** @var int|string|null */
- private $cursor;
-
- /** @var string */
- private $route;
-
- /** @var array */
- private $routeParameters;
-
/**
- * @param string $term
- * @param int $sortOrder
- * @param int $limit
- * @param int|string|null $cursor
- * @param string $route
- * @param array $routeParameters
+ * @param string[] $params Request query
+ * @param string[] $routeParameters
*/
- public function __construct(string $term,
- int $sortOrder = ISearchQuery::SORT_DATE_DESC,
- int $limit = self::LIMIT_DEFAULT,
- $cursor = null,
- string $route = '',
- array $routeParameters = []) {
- $this->term = $term;
- $this->sortOrder = $sortOrder;
- $this->limit = $limit;
- $this->cursor = $cursor;
- $this->route = $route;
- $this->routeParameters = $routeParameters;
+ public function __construct(
+ private IFilterCollection $filters,
+ private int $sortOrder = ISearchQuery::SORT_DATE_DESC,
+ private int $limit = self::LIMIT_DEFAULT,
+ private int|string|null $cursor = null,
+ private string $route = '',
+ private array $routeParameters = [],
+ ) {
}
- /**
- * @inheritDoc
- */
public function getTerm(): string {
- return $this->term;
+ return $this->getFilter('term')?->get() ?? '';
+ }
+
+ public function getFilter(string $name): ?IFilter {
+ return $this->filters->has($name)
+ ? $this->filters->get($name)
+ : null;
+ }
+
+ public function getFilters(): IFilterCollection {
+ return $this->filters;
}
- /**
- * @inheritDoc
- */
public function getSortOrder(): int {
return $this->sortOrder;
}
- /**
- * @inheritDoc
- */
public function getLimit(): int {
return $this->limit;
}
- /**
- * @inheritDoc
- */
- public function getCursor() {
+ public function getCursor(): int|string|null {
return $this->cursor;
}
- /**
- * @inheritDoc
- */
public function getRoute(): string {
return $this->route;
}
- /**
- * @inheritDoc
- */
public function getRouteParameters(): array {
return $this->routeParameters;
}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OC\Search;
+
+use Exception;
+
+final class UnsupportedFilter extends Exception {
+ public function __construct(string $filerName, $providerId) {
+ parent::__construct('Provider '.$providerId.' doesn’t support filter '.$filerName.'.');
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCP\Search;
+
+use InvalidArgumentException;
+
+/**
+ * Filter definition
+ *
+ * Describe filter attributes
+ *
+ * @since 28.0.0
+ */
+class FilterDefinition {
+ public const TYPE_BOOL = 'bool';
+ public const TYPE_INT = 'int';
+ public const TYPE_FLOAT = 'float';
+ public const TYPE_STRING = 'string';
+ public const TYPE_STRINGS = 'strings';
+ public const TYPE_DATETIME = 'datetime';
+ public const TYPE_PERSON = 'person';
+ public const TYPE_NC_USER = 'nc-user';
+ public const TYPE_NC_GROUP = 'nc-group';
+
+ /**
+ * Build filter definition
+ *
+ * @param self::TYPE_* $type
+ * @param bool $exclusive If true, all providers not supporting this filter will be ignored when this filter is provided
+ * @throw InvalidArgumentException in case of invalid name. Allowed characters are -, 0-9, a-z.
+ * @since 28.0.0
+ */
+ public function __construct(
+ private string $name,
+ private string $type = self::TYPE_STRING,
+ private bool $exclusive = true,
+ ) {
+ if (!preg_match('/[-0-9a-z]+/Au', $name)) {
+ throw new InvalidArgumentException('Invalid filter name. Allowed characters are [-0-9a-z]');
+ }
+ }
+
+ /**
+ * Filter name
+ *
+ * Name is used in query string and for advanced syntax `name: <value>`
+ *
+ * @since 28.0.0
+ */
+ public function name(): string {
+ return $this->name;
+ }
+
+ /**
+ * Filter type
+ *
+ * Expected type of value for the filter
+ *
+ * @return self::TYPE_*
+ * @since 28.0.0
+ */
+ public function type(): string {
+ return $this->type;
+ }
+
+ /**
+ * Is filter exclusive?
+ *
+ * If exclusive, only provider with support for this filter will receive the query.
+ * Example: if an exclusive filter `mimetype` is declared, a search with this term will not
+ * be send to providers like `settings` that doesn't support it.
+ *
+ * @since 28.0.0
+ */
+ public function exclusive(): bool {
+ return $this->exclusive;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCP\Search;
+
+/**
+ * Interface for search filters
+ *
+ * @since 28.0.0
+ */
+interface IFilter {
+ /**
+ * Get filter value
+ *
+ * @since 28.0.0
+ */
+ public function get(): mixed;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCP\Search;
+
+use IteratorAggregate;
+
+/**
+ * Interface for search filters
+ *
+ * @since 28.0.0
+ * @extends IteratorAggregate<string, \OCP\Search\IFilter>
+ */
+interface IFilterCollection extends IteratorAggregate {
+ /**
+ * Check if a filter exits
+ *
+ * @since 28.0.0
+ */
+ public function has(string $name): bool;
+
+ /**
+ * Get a filter by name
+ *
+ * @since 28.0.0
+ */
+ public function get(string $name): ?IFilter;
+
+ /**
+ * Return Iterator of filters
+ *
+ * @since 28.0.0
+ */
+ public function getIterator(): \Traversable;
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCP\Search;
+
+/**
+ * Interface for advanced search providers
+ *
+ * These providers will be implemented in apps, so they can participate in the
+ * global search results of Nextcloud. If an app provides more than one type of
+ * resource, e.g. contacts and address books in Nextcloud Contacts, it should
+ * register one provider per group.
+ *
+ * @since 28.0.0
+ */
+interface IFilteringProvider extends IProvider {
+ /**
+ * Return the names of filters supported by the application
+ *
+ * If a filter sent by client is not in this list,
+ * the current provider will be ignored.
+ * Example:
+ * array('term', 'since', 'custom-filter');
+ *
+ * @since 28.0.0
+ * @return string[] Name of supported filters (default or defined by application)
+ */
+ public function getSupportedFilters(): array;
+
+ /**
+ * Get alternate IDs handled by this provider
+ *
+ * A search provider can complete results from other search providers.
+ * For example, files and full-text-search can search in files.
+ * If you use `in:files` in a search, provider files will be invoked,
+ * with all other providers declaring `files` in this method
+ *
+ * @since 28.0.0
+ * @return string[] IDs
+ */
+ public function getAlternateIds(): array;
+
+ /**
+ * Allows application to declare custom filters
+ *
+ * @since 28.0.0
+ * @return list<FilterDefinition>
+ */
+ public function getCustomFilters(): array;
+}
*
* @return string the search term
* @since 20.0.0
+ * @deprecated 28.0.0
*/
public function getTerm(): string;
+ /**
+ * Get a single request filter
+ *
+ * @since 28.0.0
+ */
+ public function getFilter(string $name): ?IFilter;
+
+ /**
+ * Get request filters
+ *
+ * @since 28.0.0
+ */
+ public function getFilters(): IFilterCollection;
+
/**
* Get the sort order of results as defined as SORT_* constants on this interface
*