mirror of https://github.com/nextcloud/server.git
Merge pull request #20916 from nextcloud/feature/unified-search-api
Add unified search APIpull/21563/head
commit
654cd18864
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @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 OCA\Comments\Search;
|
||||
|
||||
use OCP\Search\ASearchResultEntry;
|
||||
|
||||
class CommentsSearchResultEntry extends ASearchResultEntry {
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Joas Schilling <coding@schilljs.com>
|
||||
*
|
||||
* @author Joas Schilling <coding@schilljs.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 OCA\Comments\Search;
|
||||
|
||||
use OCP\Comments\IComment;
|
||||
use OCP\Files\Folder;
|
||||
use OCP\Files\Node;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\IUser;
|
||||
use OCP\Search\Provider;
|
||||
use function count;
|
||||
|
||||
class LegacyProvider extends Provider {
|
||||
|
||||
/**
|
||||
* Search for $query
|
||||
*
|
||||
* @param string $query
|
||||
* @return array An array of OCP\Search\Result's
|
||||
* @since 7.0.0
|
||||
*/
|
||||
public function search($query): array {
|
||||
$cm = \OC::$server->getCommentsManager();
|
||||
$us = \OC::$server->getUserSession();
|
||||
|
||||
$user = $us->getUser();
|
||||
if (!$user instanceof IUser) {
|
||||
return [];
|
||||
}
|
||||
$uf = \OC::$server->getUserFolder($user->getUID());
|
||||
|
||||
if ($uf === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
$numComments = 50;
|
||||
$offset = 0;
|
||||
|
||||
while (count($result) < $numComments) {
|
||||
/** @var IComment[] $comments */
|
||||
$comments = $cm->search($query, 'files', '', 'comment', $offset, $numComments);
|
||||
|
||||
foreach ($comments as $comment) {
|
||||
if ($comment->getActorType() !== 'users') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$displayName = $cm->resolveDisplayName('user', $comment->getActorId());
|
||||
|
||||
try {
|
||||
$file = $this->getFileForComment($uf, $comment);
|
||||
$result[] = new Result($query,
|
||||
$comment,
|
||||
$displayName,
|
||||
$file->getPath()
|
||||
);
|
||||
} catch (NotFoundException $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($comments) < $numComments) {
|
||||
// Didn't find more comments when we tried to get, so there are no more comments.
|
||||
return $result;
|
||||
}
|
||||
|
||||
$offset += $numComments;
|
||||
$numComments = 50 - count($result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Folder $userFolder
|
||||
* @param IComment $comment
|
||||
* @return Node
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
protected function getFileForComment(Folder $userFolder, IComment $comment): Node {
|
||||
$nodes = $userFolder->getById((int) $comment->getObjectId());
|
||||
if (empty($nodes)) {
|
||||
throw new NotFoundException('File not found');
|
||||
}
|
||||
|
||||
return array_shift($nodes);
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @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 OCA\Files\Search;
|
||||
|
||||
use OC\Search\Provider\File;
|
||||
use OC\Search\Result\File as FileResult;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\Search\IProvider;
|
||||
use OCP\Search\ISearchQuery;
|
||||
use OCP\Search\SearchResult;
|
||||
|
||||
class FilesSearchProvider implements IProvider {
|
||||
|
||||
/** @var File */
|
||||
private $fileSearch;
|
||||
|
||||
/** @var IL10N */
|
||||
private $l10n;
|
||||
|
||||
/** @var IURLGenerator */
|
||||
private $urlGenerator;
|
||||
|
||||
public function __construct(File $fileSearch,
|
||||
IL10N $l10n,
|
||||
IURLGenerator $urlGenerator) {
|
||||
$this->l10n = $l10n;
|
||||
$this->fileSearch = $fileSearch;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
return 'files';
|
||||
}
|
||||
|
||||
public function search(IUser $user, ISearchQuery $query): SearchResult {
|
||||
return SearchResult::complete(
|
||||
$this->l10n->t('Files'),
|
||||
array_map(function (FileResult $result) {
|
||||
return new FilesSearchResultEntry(
|
||||
$this->urlGenerator->linkToRoute('core.Preview.getPreviewByFileId', ['x' => 32, 'y' => 32, 'fileId' => $result->id]),
|
||||
$result->name,
|
||||
$result->path,
|
||||
$result->link
|
||||
);
|
||||
}, $this->fileSearch->search($query->getTerm()))
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @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 OCA\Files\Search;
|
||||
|
||||
use OCP\Search\ASearchResultEntry;
|
||||
|
||||
class FilesSearchResultEntry extends ASearchResultEntry {
|
||||
public function __construct(string $thumbnailUrl,
|
||||
string $filename,
|
||||
string $path,
|
||||
string $url) {
|
||||
parent::__construct($thumbnailUrl, $filename, $path, $url);
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @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\Core\Controller;
|
||||
|
||||
use OC\Search\SearchComposer;
|
||||
use OC\Search\SearchQuery;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Search\ISearchQuery;
|
||||
|
||||
class UnifiedSearchController extends Controller {
|
||||
|
||||
/** @var SearchComposer */
|
||||
private $composer;
|
||||
|
||||
/** @var IUserSession */
|
||||
private $userSession;
|
||||
|
||||
public function __construct(IRequest $request,
|
||||
IUserSession $userSession,
|
||||
SearchComposer $composer) {
|
||||
parent::__construct('core', $request);
|
||||
|
||||
$this->composer = $composer;
|
||||
$this->userSession = $userSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function getProviders(): JSONResponse {
|
||||
return new JSONResponse(
|
||||
$this->composer->getProviders()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*
|
||||
* @param string $providerId
|
||||
* @param string $term
|
||||
* @param int|null $sortOrder
|
||||
* @param int|null $limit
|
||||
* @param int|string|null $cursor
|
||||
*
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function search(string $providerId,
|
||||
string $term = '',
|
||||
?int $sortOrder = null,
|
||||
?int $limit = null,
|
||||
$cursor = null): JSONResponse {
|
||||
if (empty($term)) {
|
||||
return new JSONResponse(null, Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
return new JSONResponse(
|
||||
$this->composer->search(
|
||||
$this->userSession->getUser(),
|
||||
$providerId,
|
||||
new SearchQuery(
|
||||
$term,
|
||||
$sortOrder ?? ISearchQuery::SORT_DATE_DESC,
|
||||
$limit ?? SearchQuery::LIMIT_DEFAULT,
|
||||
$cursor
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @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 InvalidArgumentException;
|
||||
use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
||||
use OCP\AppFramework\QueryException;
|
||||
use OCP\ILogger;
|
||||
use OCP\IServerContainer;
|
||||
use OCP\IUser;
|
||||
use OCP\Search\IProvider;
|
||||
use OCP\Search\ISearchQuery;
|
||||
use OCP\Search\SearchResult;
|
||||
use function array_map;
|
||||
|
||||
/**
|
||||
* Queries individual \OCP\Search\IProvider implementations and composes a
|
||||
* unified search result for the user's search term
|
||||
*
|
||||
* The search process is generally split into two steps
|
||||
*
|
||||
* 1. Get a list of provider (`getProviders`)
|
||||
* 2. Get search results of each provider (`search`)
|
||||
*
|
||||
* The reasoning behind this is that the runtime complexity of a combined search
|
||||
* result would be O(n) and linearly grow with each provider added. This comes
|
||||
* from the nature of php where we can't concurrently fetch the search results.
|
||||
* So we offload the concurrency the client application (e.g. JavaScript in the
|
||||
* browser) and let it first get the list of providers to then fetch all results
|
||||
* concurrently. The client is free to decide whether all concurrent search
|
||||
* results are awaited or shown as they come in.
|
||||
*
|
||||
* @see IProvider::search() for the arguments of the individual search requests
|
||||
*/
|
||||
class SearchComposer {
|
||||
|
||||
/** @var string[] */
|
||||
private $lazyProviders = [];
|
||||
|
||||
/** @var IProvider[] */
|
||||
private $providers = [];
|
||||
|
||||
/** @var IServerContainer */
|
||||
private $container;
|
||||
|
||||
/** @var ILogger */
|
||||
private $logger;
|
||||
|
||||
public function __construct(IServerContainer $container,
|
||||
ILogger $logger) {
|
||||
$this->container = $container;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a search provider lazily
|
||||
*
|
||||
* Registers the fully-qualified class name of an implementation of an
|
||||
* IProvider. The service will only be queried on demand. Apps will register
|
||||
* the providers through the registration context object.
|
||||
*
|
||||
* @see IRegistrationContext::registerSearchProvider()
|
||||
*
|
||||
* @param string $class
|
||||
*/
|
||||
public function registerProvider(string $class): void {
|
||||
$this->lazyProviders[] = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all providers dynamically that were registered through `registerProvider`
|
||||
*
|
||||
* If a provider can't be loaded we log it but the operation continues nevertheless
|
||||
*/
|
||||
private function loadLazyProviders(): void {
|
||||
$classes = $this->lazyProviders;
|
||||
foreach ($classes as $class) {
|
||||
try {
|
||||
/** @var IProvider $provider */
|
||||
$provider = $this->container->query($class);
|
||||
$this->providers[$provider->getId()] = $provider;
|
||||
} catch (QueryException $e) {
|
||||
// Log an continue. We can be fault tolerant here.
|
||||
$this->logger->logException($e, [
|
||||
'message' => 'Could not load search provider dynamically: ' . $e->getMessage(),
|
||||
'level' => ILogger::ERROR,
|
||||
]);
|
||||
}
|
||||
}
|
||||
$this->lazyProviders = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all provider IDs for the consecutive calls to `search`
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getProviders(): array {
|
||||
$this->loadLazyProviders();
|
||||
|
||||
/**
|
||||
* Return an array with the IDs, but strip the associative keys
|
||||
*/
|
||||
return array_values(
|
||||
array_map(function (IProvider $provider) {
|
||||
return $provider->getId();
|
||||
}, $this->providers));
|
||||
}
|
||||
|
||||
/**
|
||||
* Query an individual search provider for results
|
||||
*
|
||||
* @param IUser $user
|
||||
* @param string $providerId one of the IDs received by `getProviders`
|
||||
* @param ISearchQuery $query
|
||||
*
|
||||
* @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();
|
||||
|
||||
$provider = $this->providers[$providerId] ?? null;
|
||||
if ($provider === null) {
|
||||
throw new InvalidArgumentException("Provider $providerId is unknown");
|
||||
}
|
||||
return $provider->search($user, $query);
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @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\ISearchQuery;
|
||||
|
||||
class SearchQuery implements ISearchQuery {
|
||||
public const LIMIT_DEFAULT = 20;
|
||||
|
||||
/** @var string */
|
||||
private $term;
|
||||
|
||||
/** @var int */
|
||||
private $sortOrder;
|
||||
|
||||
/** @var int */
|
||||
private $limit;
|
||||
|
||||
/** @var int|string|null */
|
||||
private $cursor;
|
||||
|
||||
/**
|
||||
* @param string $term
|
||||
* @param int $sortOrder
|
||||
* @param int $limit
|
||||
* @param int|string|null $cursor
|
||||
*/
|
||||
public function __construct(string $term,
|
||||
int $sortOrder = ISearchQuery::SORT_DATE_DESC,
|
||||
int $limit = self::LIMIT_DEFAULT,
|
||||
$cursor = null) {
|
||||
$this->term = $term;
|
||||
$this->sortOrder = $sortOrder;
|
||||
$this->limit = $limit;
|
||||
$this->cursor = $cursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getTerm(): string {
|
||||
return $this->term;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getSortOrder(): int {
|
||||
return $this->sortOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getLimit(): int {
|
||||
return $this->limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getCursor() {
|
||||
return $this->cursor;
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @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 JsonSerializable;
|
||||
|
||||
/**
|
||||
* Represents an entry in a list of results an app returns for a unified search
|
||||
* query.
|
||||
*
|
||||
* The app providing the results has to extend this class for customization. In
|
||||
* most cases apps do not have to add any additional code.
|
||||
*
|
||||
* @example ``class MailResultEntry extends ASearchResultEntry {}`
|
||||
*
|
||||
* This approach was chosen over a final class as it allows Nextcloud to later
|
||||
* add new optional properties of an entry without having to break the usage of
|
||||
* this class in apps.
|
||||
*
|
||||
* @since 20.0.0
|
||||
*/
|
||||
abstract class ASearchResultEntry implements JsonSerializable {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @since 20.0.0
|
||||
*/
|
||||
protected $thumbnailUrl;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @since 20.0.0
|
||||
*/
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @since 20.0.0
|
||||
*/
|
||||
protected $subline;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @since 20.0.0
|
||||
*/
|
||||
protected $resourceUrl;
|
||||
|
||||
/**
|
||||
* @param string $thumbnailUrl a relative or absolute URL to the thumbnail or icon of the entry
|
||||
* @param string $title a main title of the entry
|
||||
* @param string $subline the secondary line of the entry
|
||||
* @param string $resourceUrl the URL where the user can find the detail, like a deep link inside the app
|
||||
*
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public function __construct(string $thumbnailUrl,
|
||||
string $title,
|
||||
string $subline,
|
||||
string $resourceUrl) {
|
||||
$this->thumbnailUrl = $thumbnailUrl;
|
||||
$this->title = $title;
|
||||
$this->subline = $subline;
|
||||
$this->resourceUrl = $resourceUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
'thumbnailUrl' => $this->thumbnailUrl,
|
||||
'title' => $this->title,
|
||||
'subline' => $this->subline,
|
||||
'resourceUrl' => $this->resourceUrl,
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @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 OCP\IUser;
|
||||
|
||||
/**
|
||||
* Interface for 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 20.0.0
|
||||
*/
|
||||
interface IProvider {
|
||||
|
||||
/**
|
||||
* Get the unique ID of this search provider
|
||||
*
|
||||
* Ideally this should be the app name or an identifier identified with the
|
||||
* app name, especially if the app registers more than one provider.
|
||||
*
|
||||
* Example: 'mail', 'mail_recipients', 'files_sharing'
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public function getId(): string;
|
||||
|
||||
/**
|
||||
* Find matching search entries in an app
|
||||
*
|
||||
* Search results can either be a complete list of all the matches the app can
|
||||
* find, or ideally a paginated result set where more data can be fetched on
|
||||
* demand. To be able to tell where the next offset starts the search uses
|
||||
* "cursors" which are a property of the last result entry. E.g. search results
|
||||
* that show most recent entries first can look for entries older than the last
|
||||
* one of the first result set. This approach was chosen over a numeric limit/
|
||||
* offset approach as the offset moves as new data comes in. The cursor is
|
||||
* resistant to these changes and will still show results without overlaps or
|
||||
* gaps.
|
||||
*
|
||||
* See https://dev.to/jackmarchant/offset-and-cursor-pagination-explained-b89
|
||||
* for the concept of cursors.
|
||||
*
|
||||
* Implementations that return result pages have to adhere to the limit
|
||||
* property of a search query.
|
||||
*
|
||||
* @param IUser $user
|
||||
* @param ISearchQuery $query
|
||||
*
|
||||
* @return SearchResult
|
||||
*
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public function search(IUser $user, ISearchQuery $query): SearchResult;
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* The query objected passed into \OCP\Search\IProvider::search
|
||||
*
|
||||
* This mainly wraps the search term, but will ensure that Nextcloud can add new
|
||||
* optional properties to a search request without having break the interface of
|
||||
* \OCP\Search\IProvider::search.
|
||||
*
|
||||
* @see \OCP\Search\IProvider::search
|
||||
*
|
||||
* @since 20.0.0
|
||||
*/
|
||||
interface ISearchQuery {
|
||||
|
||||
/**
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public const SORT_DATE_DESC = 1;
|
||||
|
||||
/**
|
||||
* Get the user-entered search term to find matches for
|
||||
*
|
||||
* @return string the search term
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public function getTerm(): string;
|
||||
|
||||
/**
|
||||
* Get the sort order of results as defined as SORT_* constants on this interface
|
||||
*
|
||||
* @return int
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public function getSortOrder(): int;
|
||||
|
||||
/**
|
||||
* Get the number of items to return for a paginated result
|
||||
*
|
||||
* @return int
|
||||
* @see \OCP\Search\IProvider for details
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public function getLimit(): int;
|
||||
|
||||
/**
|
||||
* Get the app-specific cursor of the tail of the previous result entries
|
||||
*
|
||||
* @return int|string|null
|
||||
* @see \OCP\Search\IProvider for details
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public function getCursor();
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @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 JsonSerializable;
|
||||
|
||||
/**
|
||||
* @since 20.0.0
|
||||
*/
|
||||
final class SearchResult implements JsonSerializable {
|
||||
|
||||
/** @var string */
|
||||
private $name;
|
||||
|
||||
/** @var bool */
|
||||
private $isPaginated;
|
||||
|
||||
/** @var ASearchResultEntry[] */
|
||||
private $entries;
|
||||
|
||||
/** @var int|string|null */
|
||||
private $cursor;
|
||||
|
||||
/**
|
||||
* @param string $name the translated name of the result section or group, e.g. "Mail"
|
||||
* @param bool $isPaginated
|
||||
* @param ASearchResultEntry[] $entries
|
||||
* @param null $cursor
|
||||
*
|
||||
* @since 20.0.0
|
||||
*/
|
||||
private function __construct(string $name,
|
||||
bool $isPaginated,
|
||||
array $entries,
|
||||
$cursor = null) {
|
||||
$this->name = $name;
|
||||
$this->isPaginated = $isPaginated;
|
||||
$this->entries = $entries;
|
||||
$this->cursor = $cursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ASearchResultEntry[] $entries
|
||||
*
|
||||
* @return static
|
||||
*
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public static function complete(string $name, array $entries): self {
|
||||
return new self(
|
||||
$name,
|
||||
false,
|
||||
$entries
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ASearchResultEntry[] $entries
|
||||
* @param int|string $cursor
|
||||
*
|
||||
* @return static
|
||||
*
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public static function paginated(string $name,
|
||||
array $entries,
|
||||
$cursor): self {
|
||||
return new self(
|
||||
$name,
|
||||
true,
|
||||
$entries,
|
||||
$cursor
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
'name' => $this->name,
|
||||
'isPaginated' => $this->isPaginated,
|
||||
'entries' => $this->entries,
|
||||
'cursor' => $this->cursor,
|
||||
];
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue