Add unified search APItags/v20.0.0beta1
@@ -21,6 +21,8 @@ return array( | |||
'OCA\\Comments\\Listener\\LoadSidebarScripts' => $baseDir . '/../lib/Listener/LoadSidebarScripts.php', | |||
'OCA\\Comments\\Notification\\Listener' => $baseDir . '/../lib/Notification/Listener.php', | |||
'OCA\\Comments\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php', | |||
'OCA\\Comments\\Search\\CommentsSearchResultEntry' => $baseDir . '/../lib/Search/CommentsSearchResultEntry.php', | |||
'OCA\\Comments\\Search\\LegacyProvider' => $baseDir . '/../lib/Search/LegacyProvider.php', | |||
'OCA\\Comments\\Search\\Provider' => $baseDir . '/../lib/Search/Provider.php', | |||
'OCA\\Comments\\Search\\Result' => $baseDir . '/../lib/Search/Result.php', | |||
); |
@@ -36,6 +36,8 @@ class ComposerStaticInitComments | |||
'OCA\\Comments\\Listener\\LoadSidebarScripts' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarScripts.php', | |||
'OCA\\Comments\\Notification\\Listener' => __DIR__ . '/..' . '/../lib/Notification/Listener.php', | |||
'OCA\\Comments\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php', | |||
'OCA\\Comments\\Search\\CommentsSearchResultEntry' => __DIR__ . '/..' . '/../lib/Search/CommentsSearchResultEntry.php', | |||
'OCA\\Comments\\Search\\LegacyProvider' => __DIR__ . '/..' . '/../lib/Search/LegacyProvider.php', | |||
'OCA\\Comments\\Search\\Provider' => __DIR__ . '/..' . '/../lib/Search/Provider.php', | |||
'OCA\\Comments\\Search\\Result' => __DIR__ . '/..' . '/../lib/Search/Result.php', | |||
); |
@@ -35,6 +35,7 @@ use OCA\Comments\Listener\CommentsEntityEventListener; | |||
use OCA\Comments\Listener\LoadAdditionalScripts; | |||
use OCA\Comments\Listener\LoadSidebarScripts; | |||
use OCA\Comments\Notification\Notifier; | |||
use OCA\Comments\Search\LegacyProvider; | |||
use OCA\Comments\Search\Provider; | |||
use OCA\Files\Event\LoadAdditionalScriptsEvent; | |||
use OCA\Files\Event\LoadSidebar; | |||
@@ -70,6 +71,7 @@ class Application extends App implements IBootstrap { | |||
CommentsEntityEvent::EVENT_ENTITY, | |||
CommentsEntityEventListener::class | |||
); | |||
$context->registerSearchProvider(Provider::class); | |||
} | |||
public function boot(IBootContext $context): void { | |||
@@ -79,7 +81,7 @@ class Application extends App implements IBootstrap { | |||
$jsSettingsHelper = new JSSettingsHelper($context->getServerContainer()); | |||
Util::connectHook('\OCP\Config', 'js', $jsSettingsHelper, 'extend'); | |||
$context->getServerContainer()->getSearch()->registerProvider(Provider::class, ['apps' => ['files']]); | |||
$context->getServerContainer()->getSearch()->registerProvider(LegacyProvider::class, ['apps' => ['files']]); | |||
} | |||
protected function registerNotifier(IServerContainer $container) { |
@@ -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); | |||
} | |||
} |
@@ -1,8 +1,11 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2018 Joas Schilling <coding@schilljs.com> | |||
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @author Joas Schilling <coding@schilljs.com> | |||
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
@@ -17,92 +20,62 @@ | |||
* 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/>. | |||
* | |||
* 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\IL10N; | |||
use OCP\IURLGenerator; | |||
use OCP\IUser; | |||
use OCP\Search\IProvider; | |||
use OCP\Search\ISearchQuery; | |||
use OCP\Search\SearchResult; | |||
use function array_map; | |||
use function pathinfo; | |||
class Provider extends \OCP\Search\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 []; | |||
} | |||
class Provider implements IProvider { | |||
$result = []; | |||
$numComments = 50; | |||
$offset = 0; | |||
/** @var IL10N */ | |||
private $l10n; | |||
while (\count($result) < $numComments) { | |||
/** @var IComment[] $comments */ | |||
$comments = $cm->search($query, 'files', '', 'comment', $offset, $numComments); | |||
/** @var IURLGenerator */ | |||
private $urlGenerator; | |||
foreach ($comments as $comment) { | |||
if ($comment->getActorType() !== 'users') { | |||
continue; | |||
} | |||
/** @var LegacyProvider */ | |||
private $legacyProvider; | |||
$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; | |||
public function __construct(IL10N $l10n, | |||
IURLGenerator $urlGenerator, | |||
LegacyProvider $legacyProvider) { | |||
$this->l10n = $l10n; | |||
$this->urlGenerator = $urlGenerator; | |||
$this->legacyProvider = $legacyProvider; | |||
} | |||
/** | |||
* @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'); | |||
} | |||
public function getId(): string { | |||
return 'comments'; | |||
} | |||
return array_shift($nodes); | |||
public function search(IUser $user, ISearchQuery $query): SearchResult { | |||
return SearchResult::complete( | |||
$this->l10n->t('Comments'), | |||
array_map(function (Result $result) { | |||
$path = $result->path; | |||
$pathInfo = pathinfo($path); | |||
return new CommentsSearchResultEntry( | |||
$this->urlGenerator->linkToRoute('core.Preview.getPreviewByFileId', ['x' => 32, 'y' => 32, 'fileId' => $result->id]), | |||
$result->name, | |||
$path, | |||
$this->urlGenerator->linkToRoute( | |||
'files.view.index', | |||
[ | |||
'dir' => $pathInfo['dirname'], | |||
'scrollto' => $pathInfo['basename'], | |||
] | |||
) | |||
); | |||
}, $this->legacyProvider->search($query->getTerm())) | |||
); | |||
} | |||
} |
@@ -28,12 +28,33 @@ use OCP\Comments\IComment; | |||
use OCP\Files\NotFoundException; | |||
use OCP\Search\Result as BaseResult; | |||
/** | |||
* @deprecated 20.0.0 | |||
*/ | |||
class Result extends BaseResult { | |||
/** | |||
* @deprecated 20.0.0 | |||
*/ | |||
public $type = 'comment'; | |||
/** | |||
* @deprecated 20.0.0 | |||
*/ | |||
public $comment; | |||
/** | |||
* @deprecated 20.0.0 | |||
*/ | |||
public $authorId; | |||
/** | |||
* @deprecated 20.0.0 | |||
*/ | |||
public $authorName; | |||
/** | |||
* @deprecated 20.0.0 | |||
*/ | |||
public $path; | |||
/** | |||
* @deprecated 20.0.0 | |||
*/ | |||
public $fileName; | |||
/** | |||
@@ -42,6 +63,7 @@ class Result extends BaseResult { | |||
* @param string $authorName | |||
* @param string $path | |||
* @throws NotFoundException | |||
* @deprecated 20.0.0 | |||
*/ | |||
public function __construct(string $search, | |||
IComment $comment, |
@@ -47,6 +47,8 @@ return array( | |||
'OCA\\Files\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php', | |||
'OCA\\Files\\Migration\\Version11301Date20191205150729' => $baseDir . '/../lib/Migration/Version11301Date20191205150729.php', | |||
'OCA\\Files\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php', | |||
'OCA\\Files\\Search\\FilesSearchProvider' => $baseDir . '/../lib/Search/FilesSearchProvider.php', | |||
'OCA\\Files\\Search\\FilesSearchResultEntry' => $baseDir . '/../lib/Search/FilesSearchResultEntry.php', | |||
'OCA\\Files\\Service\\DirectEditingService' => $baseDir . '/../lib/Service/DirectEditingService.php', | |||
'OCA\\Files\\Service\\OwnershipTransferService' => $baseDir . '/../lib/Service/OwnershipTransferService.php', | |||
'OCA\\Files\\Service\\TagService' => $baseDir . '/../lib/Service/TagService.php', |
@@ -62,6 +62,8 @@ class ComposerStaticInitFiles | |||
'OCA\\Files\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php', | |||
'OCA\\Files\\Migration\\Version11301Date20191205150729' => __DIR__ . '/..' . '/../lib/Migration/Version11301Date20191205150729.php', | |||
'OCA\\Files\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php', | |||
'OCA\\Files\\Search\\FilesSearchProvider' => __DIR__ . '/..' . '/../lib/Search/FilesSearchProvider.php', | |||
'OCA\\Files\\Search\\FilesSearchResultEntry' => __DIR__ . '/..' . '/../lib/Search/FilesSearchResultEntry.php', | |||
'OCA\\Files\\Service\\DirectEditingService' => __DIR__ . '/..' . '/../lib/Service/DirectEditingService.php', | |||
'OCA\\Files\\Service\\OwnershipTransferService' => __DIR__ . '/..' . '/../lib/Service/OwnershipTransferService.php', | |||
'OCA\\Files\\Service\\TagService' => __DIR__ . '/..' . '/../lib/Service/TagService.php', |
@@ -44,6 +44,7 @@ use OCA\Files\Event\LoadSidebar; | |||
use OCA\Files\Listener\LegacyLoadAdditionalScriptsAdapter; | |||
use OCA\Files\Listener\LoadSidebarListener; | |||
use OCA\Files\Notification\Notifier; | |||
use OCA\Files\Search\FilesSearchProvider; | |||
use OCA\Files\Service\TagService; | |||
use OCP\AppFramework\App; | |||
use OCP\AppFramework\Bootstrap\IBootContext; | |||
@@ -106,6 +107,8 @@ class Application extends App implements IBootstrap { | |||
$context->registerEventListener(LoadAdditionalScriptsEvent::class, LegacyLoadAdditionalScriptsAdapter::class); | |||
$context->registerEventListener(LoadSidebar::class, LoadSidebarListener::class); | |||
$context->registerSearchProvider(FilesSearchProvider::class); | |||
} | |||
public function boot(IBootContext $context): void { |
@@ -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 | |||
) | |||
) | |||
); | |||
} | |||
} |
@@ -77,6 +77,8 @@ $application->registerRoutes($this, [ | |||
['name' => 'RecommendedApps#index', 'url' => '/core/apps/recommended', 'verb' => 'GET'], | |||
['name' => 'Svg#getSvgFromCore', 'url' => '/svg/core/{folder}/{fileName}', 'verb' => 'GET'], | |||
['name' => 'Svg#getSvgFromApp', 'url' => '/svg/{app}/{fileName}', 'verb' => 'GET'], | |||
['name' => 'UnifiedSearch#getProviders', 'url' => '/search/providers', 'verb' => 'GET'], | |||
['name' => 'UnifiedSearch#search', 'url' => '/search/providers/{providerId}/search', 'verb' => 'GET'], | |||
['name' => 'Css#getCss', 'url' => '/css/{appName}/{fileName}', 'verb' => 'GET'], | |||
['name' => 'Js#getJs', 'url' => '/js/{appName}/{fileName}', 'verb' => 'GET'], | |||
['name' => 'contactsMenu#index', 'url' => '/contactsmenu/contacts', 'verb' => 'POST'], |
@@ -432,9 +432,13 @@ return array( | |||
'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\\ASearchResultEntry' => $baseDir . '/lib/public/Search/ASearchResultEntry.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', | |||
'OCP\\Search\\Provider' => $baseDir . '/lib/public/Search/Provider.php', | |||
'OCP\\Search\\Result' => $baseDir . '/lib/public/Search/Result.php', | |||
'OCP\\Search\\SearchResult' => $baseDir . '/lib/public/Search/SearchResult.php', | |||
'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => $baseDir . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php', | |||
'OCP\\Security\\Events\\GenerateSecurePasswordEvent' => $baseDir . '/lib/public/Security/Events/GenerateSecurePasswordEvent.php', | |||
'OCP\\Security\\Events\\ValidatePasswordPolicyEvent' => $baseDir . '/lib/public/Security/Events/ValidatePasswordPolicyEvent.php', | |||
@@ -853,6 +857,7 @@ return array( | |||
'OC\\Core\\Controller\\SetupController' => $baseDir . '/core/Controller/SetupController.php', | |||
'OC\\Core\\Controller\\SvgController' => $baseDir . '/core/Controller/SvgController.php', | |||
'OC\\Core\\Controller\\TwoFactorChallengeController' => $baseDir . '/core/Controller/TwoFactorChallengeController.php', | |||
'OC\\Core\\Controller\\UnifiedSearchController' => $baseDir . '/core/Controller/UnifiedSearchController.php', | |||
'OC\\Core\\Controller\\UserController' => $baseDir . '/core/Controller/UserController.php', | |||
'OC\\Core\\Controller\\WalledGardenController' => $baseDir . '/core/Controller/WalledGardenController.php', | |||
'OC\\Core\\Controller\\WebAuthnController' => $baseDir . '/core/Controller/WebAuthnController.php', | |||
@@ -1233,6 +1238,8 @@ return array( | |||
'OC\\Search\\Result\\File' => $baseDir . '/lib/private/Search/Result/File.php', | |||
'OC\\Search\\Result\\Folder' => $baseDir . '/lib/private/Search/Result/Folder.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\\Security\\Bruteforce\\Capabilities' => $baseDir . '/lib/private/Security/Bruteforce/Capabilities.php', | |||
'OC\\Security\\Bruteforce\\Throttler' => $baseDir . '/lib/private/Security/Bruteforce/Throttler.php', | |||
'OC\\Security\\CSP\\ContentSecurityPolicy' => $baseDir . '/lib/private/Security/CSP/ContentSecurityPolicy.php', |
@@ -461,9 +461,13 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c | |||
'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\\ASearchResultEntry' => __DIR__ . '/../../..' . '/lib/public/Search/ASearchResultEntry.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', | |||
'OCP\\Search\\Provider' => __DIR__ . '/../../..' . '/lib/public/Search/Provider.php', | |||
'OCP\\Search\\Result' => __DIR__ . '/../../..' . '/lib/public/Search/Result.php', | |||
'OCP\\Search\\SearchResult' => __DIR__ . '/../../..' . '/lib/public/Search/SearchResult.php', | |||
'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php', | |||
'OCP\\Security\\Events\\GenerateSecurePasswordEvent' => __DIR__ . '/../../..' . '/lib/public/Security/Events/GenerateSecurePasswordEvent.php', | |||
'OCP\\Security\\Events\\ValidatePasswordPolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/Events/ValidatePasswordPolicyEvent.php', | |||
@@ -882,6 +886,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c | |||
'OC\\Core\\Controller\\SetupController' => __DIR__ . '/../../..' . '/core/Controller/SetupController.php', | |||
'OC\\Core\\Controller\\SvgController' => __DIR__ . '/../../..' . '/core/Controller/SvgController.php', | |||
'OC\\Core\\Controller\\TwoFactorChallengeController' => __DIR__ . '/../../..' . '/core/Controller/TwoFactorChallengeController.php', | |||
'OC\\Core\\Controller\\UnifiedSearchController' => __DIR__ . '/../../..' . '/core/Controller/UnifiedSearchController.php', | |||
'OC\\Core\\Controller\\UserController' => __DIR__ . '/../../..' . '/core/Controller/UserController.php', | |||
'OC\\Core\\Controller\\WalledGardenController' => __DIR__ . '/../../..' . '/core/Controller/WalledGardenController.php', | |||
'OC\\Core\\Controller\\WebAuthnController' => __DIR__ . '/../../..' . '/core/Controller/WebAuthnController.php', | |||
@@ -1262,6 +1267,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c | |||
'OC\\Search\\Result\\File' => __DIR__ . '/../../..' . '/lib/private/Search/Result/File.php', | |||
'OC\\Search\\Result\\Folder' => __DIR__ . '/../../..' . '/lib/private/Search/Result/Folder.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\\Security\\Bruteforce\\Capabilities' => __DIR__ . '/../../..' . '/lib/private/Security/Bruteforce/Capabilities.php', | |||
'OC\\Security\\Bruteforce\\Throttler' => __DIR__ . '/../../..' . '/lib/private/Security/Bruteforce/Throttler.php', | |||
'OC\\Security\\CSP\\ContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/private/Security/CSP/ContentSecurityPolicy.php', |
@@ -25,6 +25,7 @@ declare(strict_types=1); | |||
namespace OC\AppFramework\Bootstrap; | |||
use OC\Search\SearchComposer; | |||
use OC\Support\CrashReport\Registry; | |||
use OC_App; | |||
use OCP\AppFramework\App; | |||
@@ -49,16 +50,21 @@ class Coordinator { | |||
/** @var IEventDispatcher */ | |||
private $eventDispatcher; | |||
/** @var SearchComposer */ | |||
private $searchComposer; | |||
/** @var ILogger */ | |||
private $logger; | |||
public function __construct(IServerContainer $container, | |||
Registry $registry, | |||
IEventDispatcher $eventListener, | |||
SearchComposer $searchComposer, | |||
ILogger $logger) { | |||
$this->serverContainer = $container; | |||
$this->registry = $registry; | |||
$this->eventDispatcher = $eventListener; | |||
$this->searchComposer = $searchComposer; | |||
$this->logger = $logger; | |||
} | |||
@@ -112,6 +118,7 @@ class Coordinator { | |||
$context->delegateEventListenerRegistrations($this->eventDispatcher); | |||
$context->delegateContainerRegistrations($apps); | |||
$context->delegateMiddlewareRegistrations($apps); | |||
$context->delegateSearchProviderRegistration($apps, $this->searchComposer); | |||
} | |||
public function bootApp(string $appId): void { |
@@ -26,6 +26,7 @@ declare(strict_types=1); | |||
namespace OC\AppFramework\Bootstrap; | |||
use Closure; | |||
use OC\Search\SearchComposer; | |||
use OC\Support\CrashReport\Registry; | |||
use OCP\AppFramework\App; | |||
use OCP\AppFramework\Bootstrap\IRegistrationContext; | |||
@@ -56,6 +57,9 @@ class RegistrationContext { | |||
/** @var array[] */ | |||
private $middlewares = []; | |||
/** @var array[] */ | |||
private $searchProviders = []; | |||
/** @var ILogger */ | |||
private $logger; | |||
@@ -130,6 +134,13 @@ class RegistrationContext { | |||
$class | |||
); | |||
} | |||
public function registerSearchProvider(string $class): void { | |||
$this->context->registerSearchProvider( | |||
$this->appId, | |||
$class | |||
); | |||
} | |||
}; | |||
} | |||
@@ -188,6 +199,13 @@ class RegistrationContext { | |||
]; | |||
} | |||
public function registerSearchProvider(string $appId, string $class) { | |||
$this->searchProviders[] = [ | |||
'appId' => $appId, | |||
'class' => $class, | |||
]; | |||
} | |||
/** | |||
* @param App[] $apps | |||
*/ | |||
@@ -327,4 +345,21 @@ class RegistrationContext { | |||
} | |||
} | |||
} | |||
/** | |||
* @param App[] $apps | |||
*/ | |||
public function delegateSearchProviderRegistration(array $apps, SearchComposer $searchComposer): void { | |||
foreach ($this->searchProviders as $registration) { | |||
try { | |||
$searchComposer->registerProvider($registration['class']); | |||
} catch (Throwable $e) { | |||
$appId = $registration['appId']; | |||
$this->logger->logException($e, [ | |||
'message' => "Error during search provider registration of $appId: " . $e->getMessage(), | |||
'level' => ILogger::ERROR, | |||
]); | |||
} | |||
} | |||
} | |||
} |
@@ -32,6 +32,7 @@ use OC\Files\Filesystem; | |||
/** | |||
* Provide search results from the 'files' app | |||
* @deprecated 20.0.0 | |||
*/ | |||
class File extends \OCP\Search\Provider { | |||
@@ -39,6 +40,7 @@ class File extends \OCP\Search\Provider { | |||
* Search for files and folders matching the given query | |||
* @param string $query | |||
* @return \OCP\Search\Result | |||
* @deprecated 20.0.0 | |||
*/ | |||
public function search($query) { | |||
$files = Filesystem::search($query); |
@@ -27,15 +27,17 @@ namespace OC\Search\Result; | |||
/** | |||
* A found audio file | |||
* @deprecated 20.0.0 | |||
*/ | |||
class Audio extends File { | |||
/** | |||
* Type name; translated in templates | |||
* @var string | |||
* @deprecated 20.0.0 | |||
*/ | |||
public $type = 'audio'; | |||
/** | |||
* @TODO add ID3 information | |||
*/ |
@@ -31,36 +31,42 @@ use OCP\Files\Folder; | |||
/** | |||
* A found file | |||
* @deprecated 20.0.0 | |||
*/ | |||
class File extends \OCP\Search\Result { | |||
/** | |||
* Type name; translated in templates | |||
* @var string | |||
* @deprecated 20.0.0 | |||
*/ | |||
public $type = 'file'; | |||
/** | |||
* Path to file | |||
* @var string | |||
* @deprecated 20.0.0 | |||
*/ | |||
public $path; | |||
/** | |||
* Size, in bytes | |||
* @var int | |||
* @deprecated 20.0.0 | |||
*/ | |||
public $size; | |||
/** | |||
* Date modified, in human readable form | |||
* @var string | |||
* @deprecated 20.0.0 | |||
*/ | |||
public $modified; | |||
/** | |||
* File mime type | |||
* @var string | |||
* @deprecated 20.0.0 | |||
*/ | |||
public $mime_type; | |||
@@ -68,12 +74,14 @@ class File extends \OCP\Search\Result { | |||
* File permissions: | |||
* | |||
* @var string | |||
* @deprecated 20.0.0 | |||
*/ | |||
public $permissions; | |||
/** | |||
* Create a new file search result | |||
* @param FileInfo $data file data given by provider | |||
* @deprecated 20.0.0 | |||
*/ | |||
public function __construct(FileInfo $data) { | |||
$path = $this->getRelativePath($data->getPath()); | |||
@@ -97,6 +105,7 @@ class File extends \OCP\Search\Result { | |||
/** | |||
* @var Folder $userFolderCache | |||
* @deprecated 20.0.0 | |||
*/ | |||
protected static $userFolderCache = null; | |||
@@ -105,6 +114,7 @@ class File extends \OCP\Search\Result { | |||
* eg /user/files/foo.txt -> /foo.txt | |||
* @param string $path | |||
* @return string relative path | |||
* @deprecated 20.0.0 | |||
*/ | |||
protected function getRelativePath($path) { | |||
if (!isset(self::$userFolderCache)) { |
@@ -27,12 +27,14 @@ namespace OC\Search\Result; | |||
/** | |||
* A found folder | |||
* @deprecated 20.0.0 | |||
*/ | |||
class Folder extends File { | |||
/** | |||
* Type name; translated in templates | |||
* @var string | |||
* @deprecated 20.0.0 | |||
*/ | |||
public $type = 'folder'; | |||
} |
@@ -27,15 +27,17 @@ namespace OC\Search\Result; | |||
/** | |||
* A found image file | |||
* @deprecated 20.0.0 | |||
*/ | |||
class Image extends File { | |||
/** | |||
* Type name; translated in templates | |||
* @var string | |||
* @deprecated 20.0.0 | |||
*/ | |||
public $type = 'image'; | |||
/** | |||
* @TODO add EXIF information | |||
*/ |
@@ -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; | |||
} | |||
} |
@@ -115,4 +115,19 @@ interface IRegistrationContext { | |||
* @since 20.0.0 | |||
*/ | |||
public function registerMiddleware(string $class): void; | |||
/** | |||
* Register a search provider for the unified search | |||
* | |||
* It is allowed to register more than one provider per app as the search | |||
* results can go into distinct sections, e.g. "Files" and "Files shared | |||
* with you" in the Files app. | |||
* | |||
* @param string $class | |||
* | |||
* @return void | |||
* | |||
* @since 20.0.0 | |||
*/ | |||
public function registerSearchProvider(string $class): void; | |||
} |
@@ -29,6 +29,7 @@ namespace OCP; | |||
/** | |||
* Small Interface for Search | |||
* @since 7.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
interface ISearch { | |||
@@ -40,6 +41,7 @@ interface ISearch { | |||
* @param int $size | |||
* @return array An array of OCP\Search\Result's | |||
* @since 8.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
public function searchPaged($query, array $inApps = [], $page = 1, $size = 30); | |||
@@ -48,6 +50,7 @@ interface ISearch { | |||
* @param string $class class name of a OCP\Search\Provider | |||
* @param array $options optional | |||
* @since 7.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
public function registerProvider($class, array $options = []); | |||
@@ -55,12 +58,14 @@ interface ISearch { | |||
* Remove one existing search provider | |||
* @param string $provider class name of a OCP\Search\Provider | |||
* @since 7.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
public function removeProvider($provider); | |||
/** | |||
* Remove all registered search providers | |||
* @since 7.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
public function clearProviders(); | |||
} |
@@ -359,6 +359,7 @@ interface IServerContainer extends IContainer { | |||
* | |||
* @return \OCP\ISearch | |||
* @since 7.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
public function getSearch(); | |||
@@ -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(); | |||
} |
@@ -30,12 +30,14 @@ namespace OCP\Search; | |||
/** | |||
* Provides a template for search functionality throughout ownCloud; | |||
* @since 8.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
abstract class PagedProvider extends Provider { | |||
/** | |||
* show all results | |||
* @since 8.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
public const SIZE_ALL = 0; | |||
@@ -43,6 +45,7 @@ abstract class PagedProvider extends Provider { | |||
* Constructor | |||
* @param array $options | |||
* @since 8.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
public function __construct($options) { | |||
parent::__construct($options); | |||
@@ -53,6 +56,7 @@ abstract class PagedProvider extends Provider { | |||
* @param string $query | |||
* @return array An array of OCP\Search\Result's | |||
* @since 8.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
public function search($query) { | |||
// old apps might assume they get all results, so we use SIZE_ALL | |||
@@ -66,6 +70,7 @@ abstract class PagedProvider extends Provider { | |||
* @param int $size 0 = SIZE_ALL | |||
* @return array An array of OCP\Search\Result's | |||
* @since 8.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
abstract public function searchPaged($query, $page, $size); | |||
} |
@@ -30,11 +30,13 @@ namespace OCP\Search; | |||
/** | |||
* Provides a template for search functionality throughout ownCloud; | |||
* @since 7.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
abstract class Provider { | |||
/** | |||
* @since 8.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
public const OPTION_APPS = 'apps'; | |||
@@ -42,6 +44,7 @@ abstract class Provider { | |||
* List of options | |||
* @var array | |||
* @since 7.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
protected $options; | |||
@@ -49,6 +52,7 @@ abstract class Provider { | |||
* Constructor | |||
* @param array $options as key => value | |||
* @since 7.0.0 - default value for $options was added in 8.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
public function __construct($options = []) { | |||
$this->options = $options; | |||
@@ -59,6 +63,7 @@ abstract class Provider { | |||
* @param string $key | |||
* @return mixed | |||
* @since 8.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
public function getOption($key) { | |||
if (is_array($this->options) && isset($this->options[$key])) { | |||
@@ -76,6 +81,7 @@ abstract class Provider { | |||
* @param string[] $apps | |||
* @return bool | |||
* @since 8.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
public function providesResultsFor(array $apps = []) { | |||
$forApps = $this->getOption(self::OPTION_APPS); | |||
@@ -87,6 +93,7 @@ abstract class Provider { | |||
* @param string $query | |||
* @return array An array of OCP\Search\Result's | |||
* @since 7.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
abstract public function search($query); | |||
} |
@@ -29,6 +29,7 @@ namespace OCP\Search; | |||
/** | |||
* The generic result of a search | |||
* @since 7.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
class Result { | |||
@@ -37,6 +38,7 @@ class Result { | |||
* corresponding application. | |||
* @var string | |||
* @since 7.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
public $id; | |||
@@ -45,6 +47,7 @@ class Result { | |||
* results. | |||
* @var string | |||
* @since 7.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
public $name; | |||
@@ -52,6 +55,7 @@ class Result { | |||
* URL to the application item. | |||
* @var string | |||
* @since 7.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
public $link; | |||
@@ -60,6 +64,7 @@ class Result { | |||
* as the class name (e.g. \OC\Search\File -> 'file') in lowercase. | |||
* @var string | |||
* @since 7.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
public $type = 'generic'; | |||
@@ -69,6 +74,7 @@ class Result { | |||
* @param string $name displayed text of result | |||
* @param string $link URL to the result within its app | |||
* @since 7.0.0 | |||
* @deprecated 20.0.0 | |||
*/ | |||
public function __construct($id = null, $name = null, $link = null) { | |||
$this->id = $id; |
@@ -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, | |||
]; | |||
} | |||
} |
@@ -26,6 +26,7 @@ declare(strict_types=1); | |||
namespace lib\AppFramework\Bootstrap; | |||
use OC\AppFramework\Bootstrap\Coordinator; | |||
use OC\Search\SearchComposer; | |||
use OC\Support\CrashReport\Registry; | |||
use OCP\App\IAppManager; | |||
use OCP\AppFramework\App; | |||
@@ -53,6 +54,9 @@ class CoordinatorTest extends TestCase { | |||
/** @var IEventDispatcher|MockObject */ | |||
private $eventDispatcher; | |||
/** @var SearchComposer|MockObject */ | |||
private $searchComposer; | |||
/** @var ILogger|MockObject */ | |||
private $logger; | |||
@@ -66,12 +70,14 @@ class CoordinatorTest extends TestCase { | |||
$this->serverContainer = $this->createMock(IServerContainer::class); | |||
$this->crashReporterRegistry = $this->createMock(Registry::class); | |||
$this->eventDispatcher = $this->createMock(IEventDispatcher::class); | |||
$this->searchComposer = $this->createMock(SearchComposer::class); | |||
$this->logger = $this->createMock(ILogger::class); | |||
$this->coordinator = new Coordinator( | |||
$this->serverContainer, | |||
$this->crashReporterRegistry, | |||
$this->eventDispatcher, | |||
$this->searchComposer, | |||
$this->logger | |||
); | |||
} |