diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index 2b2a4aead..4e1e21098 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -72,7 +72,7 @@
use OCA\Circles\Notification\Notifier;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\DavService;
-use OCA\Circles\UnifiedSearch\UnifiedSearchProvider;
+use OCA\Circles\Search\UnifiedSearchProvider;
use OCP\App\ManagerEvent;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
@@ -90,8 +90,6 @@
use Symfony\Component\EventDispatcher\GenericEvent;
use Throwable;
-//use OCA\Files\App as FilesApp;
-
/**
* Class Application
*
diff --git a/lib/Db/CoreQueryBuilder.php b/lib/Db/CoreQueryBuilder.php
index 532d6e023..9bf406f18 100644
--- a/lib/Db/CoreQueryBuilder.php
+++ b/lib/Db/CoreQueryBuilder.php
@@ -391,9 +391,36 @@ public function filterCircle(Circle $circle): void {
return;
}
+ $expr = $this->expr();
+ $orX = $expr->orX();
if ($circle->getDisplayName() !== '') {
- $this->searchInDBField('display_name', '%' . $circle->getDisplayName() . '%');
+ $andX = $expr->andX();
+ foreach (explode(' ', $circle->getDisplayName()) as $word) {
+ $andX->add(
+ $expr->iLike(
+ $this->getDefaultSelectAlias() . '.' . 'display_name',
+ $this->createNamedParameter('%' . $word . '%')
+ )
+ );
+ }
+ $orX->add($andX);
}
+ if ($circle->getDescription() !== '') {
+ $orDescription = $expr->orX();
+ foreach (explode(' ', $circle->getDescription()) as $word) {
+ $orDescription->add(
+ $expr->iLike(
+ $this->getDefaultSelectAlias() . '.' . 'description',
+ $this->createNamedParameter('%' . $word . '%')
+ )
+ );
+ }
+ $orX->add($orDescription);
+ }
+ if ($orX->count() > 0) {
+ $this->andWhere($orX);
+ }
+
if ($circle->getSource() > 0) {
$this->limitInt('source', $circle->getSource());
}
diff --git a/lib/Search/UnifiedSearchProvider.php b/lib/Search/UnifiedSearchProvider.php
new file mode 100644
index 000000000..d9aca0ed2
--- /dev/null
+++ b/lib/Search/UnifiedSearchProvider.php
@@ -0,0 +1,153 @@
+
+ * @copyright 2021
+ * @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 .
+ *
+ */
+
+
+namespace OCA\Circles\Search;
+
+use Exception;
+use OCA\Circles\Exceptions\ParseMemberLevelException;
+use OCA\Circles\Model\Member;
+use OCA\Circles\Service\FederatedUserService;
+use OCA\Circles\Service\SearchService;
+use OCP\IL10N;
+use OCP\IUser;
+use OCP\Search\IProvider;
+use OCP\Search\ISearchQuery;
+use OCP\Search\SearchResult;
+
+class UnifiedSearchProvider implements IProvider {
+ public const PROVIDER_ID = 'circles';
+ public const ORDER = 9;
+
+
+ /** @var IL10N */
+ private $l10n;
+
+ /** @var FederatedUserService */
+ private $federatedUserService;
+
+ /** @var SearchService */
+ private $searchService;
+
+
+ /**
+ * @param IL10N $l10n
+ * @param FederatedUserService $federatedUserService
+ * @param SearchService $searchService
+ */
+ public function __construct(
+ IL10N $l10n,
+ FederatedUserService $federatedUserService,
+ SearchService $searchService
+ ) {
+ $this->l10n = $l10n;
+ $this->federatedUserService = $federatedUserService;
+ $this->searchService = $searchService;
+ }
+
+
+ /**
+ * return unique id of the provider
+ */
+ public function getId(): string {
+ return self::PROVIDER_ID;
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getName(): string {
+ return $this->l10n->t('Circles');
+ }
+
+
+ /**
+ * @param string $route
+ * @param array $routeParameters
+ *
+ * @return int
+ */
+ public function getOrder(string $route, array $routeParameters): int {
+ return self::ORDER;
+ }
+
+
+ /**
+ * @param IUser $user
+ * @param ISearchQuery $query
+ *
+ * @return SearchResult
+ */
+ public function search(IUser $user, ISearchQuery $query): SearchResult {
+ $term = $query->getTerm();
+ $options = $this->extractOptions($term);
+
+ $result = [];
+ try {
+ $this->federatedUserService->setLocalCurrentUser($user);
+ $result = $this->searchService->unifiedSearch($term, $options);
+ } catch (Exception $e) {
+ }
+
+ return SearchResult::paginated(
+ $this->l10n->t('Circles'),
+ $result,
+ ($query->getCursor() ?? 0) + $query->getLimit()
+ );
+ }
+
+
+ /**
+ * This is temporary, should be handled by core to extract Options from Term
+ *
+ * @param string $term
+ *
+ * @return array
+ */
+ private function extractOptions(string &$term): array {
+ $new = $options = [];
+ foreach (explode(' ', $term) as $word) {
+ $word = trim($word);
+ if (strtolower(substr($word, 0, 3)) === 'is:') {
+ try {
+ $options['level'] = Member::parseLevelString(substr($word, 3));
+ } catch (ParseMemberLevelException $e) {
+ }
+ } else {
+ $new[] = $word;
+ }
+ }
+
+ $term = trim(implode(' ', $new));
+
+ return $options;
+ }
+}
diff --git a/lib/Search/UnifiedSearchResult.php b/lib/Search/UnifiedSearchResult.php
new file mode 100644
index 000000000..eeec748d7
--- /dev/null
+++ b/lib/Search/UnifiedSearchResult.php
@@ -0,0 +1,173 @@
+
+ * @copyright 2021
+ * @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 .
+ *
+ */
+
+
+namespace OCA\Circles\Search;
+
+use OCP\Search\SearchResultEntry;
+
+class UnifiedSearchResult extends SearchResultEntry {
+
+
+ /**
+ * UnifiedSearchResult constructor.
+ *
+ * @param string $thumbnailUrl
+ * @param string $title
+ * @param string $subline
+ * @param string $resourceUrl
+ * @param string $icon
+ * @param bool $rounded
+ */
+ public function __construct(
+ string $thumbnailUrl = '',
+ string $title = '',
+ string $subline = '',
+ string $resourceUrl = '',
+ string $icon = '',
+ bool $rounded = false
+ ) {
+ parent::__construct($thumbnailUrl, $title, $subline, $resourceUrl, $icon, $rounded);
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getThumbnailUrl(): string {
+ return $this->thumbnailUrl;
+ }
+
+ /**
+ * @param string $thumbnailUrl
+ *
+ * @return UnifiedSearchResult
+ */
+ public function setThumbnailUrl(string $thumbnailUrl): self {
+ $this->thumbnailUrl = $thumbnailUrl;
+
+ return $this;
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getTitle(): string {
+ return $this->title;
+ }
+
+ /**
+ * @param string $title
+ *
+ * @return UnifiedSearchResult
+ */
+ public function setTitle(string $title): self {
+ $this->title = $title;
+
+ return $this;
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getSubline(): string {
+ return $this->subline;
+ }
+
+ /**
+ * @param string $subline
+ *
+ * @return UnifiedSearchResult
+ */
+ public function setSubline(string $subline): self {
+ $this->subline = $subline;
+
+ return $this;
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getResourceUrl(): string {
+ return $this->resourceUrl;
+ }
+
+ /**
+ * @param string $resourceUrl
+ *
+ * @return UnifiedSearchResult
+ */
+ public function setResourceUrl(string $resourceUrl): self {
+ $this->resourceUrl = $resourceUrl;
+
+ return $this;
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getIcon(): string {
+ return $this->icon;
+ }
+
+ /**
+ * @param string $icon
+ *
+ * @return UnifiedSearchResult
+ */
+ public function setIcon(string $icon): self {
+ $this->icon = $icon;
+
+ return $this;
+ }
+
+
+ /**
+ * @return bool
+ */
+ public function isRounded(): bool {
+ return $this->rounded;
+ }
+
+ /**
+ * @param bool $rounded
+ *
+ * @return UnifiedSearchResult
+ */
+ public function setRounded(bool $rounded): self {
+ $this->rounded = $rounded;
+
+ return $this;
+ }
+}
diff --git a/lib/Service/SearchService.php b/lib/Service/SearchService.php
index aaddf5992..0688f207b 100644
--- a/lib/Service/SearchService.php
+++ b/lib/Service/SearchService.php
@@ -32,26 +32,45 @@
namespace OCA\Circles\Service;
use OC;
+use OCA\Circles\AppInfo\Application;
+use OCA\Circles\Exceptions\InitiatorNotFoundException;
+use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\ISearch;
+use OCA\Circles\Model\Circle;
use OCA\Circles\Model\FederatedUser;
+use OCA\Circles\Model\Member;
+use OCA\Circles\Model\Probes\CircleProbe;
use OCA\Circles\Search\FederatedUsers;
+use OCA\Circles\Search\UnifiedSearchResult;
+use OCA\Circles\Tools\Traits\TArrayTools;
+use OCP\IURLGenerator;
-/**
- * Class SearchService
- *
- * @package OCA\Circles\Service
- */
class SearchService {
+ use TArrayTools;
+
+
public static $SERVICES = [
FederatedUsers::class
];
+ /** @var IURLGenerator */
+ private $urlGenerator;
+
+ /** @var CircleService */
+ private $circleService;
+
+
/**
- * SearchService constructor.
- *
+ * @param IURLGenerator $urlGenerator
+ * @param CircleService $circleService
*/
- public function __construct() {
+ public function __construct(
+ IURLGenerator $urlGenerator,
+ CircleService $circleService
+ ) {
+ $this->urlGenerator = $urlGenerator;
+ $this->circleService = $circleService;
}
@@ -63,13 +82,82 @@ public function __construct() {
public function search(string $needle): array {
$result = [];
- foreach (self::$SERVICES as $service) {
+ foreach (self::$SERVICES as $entry) {
/** @var ISearch $service */
- $service = OC::$server->get($service);
+ $service = OC::$server->get($entry);
$result = array_merge($result, $service->search($needle));
}
return $result;
}
+
+
+ /**
+ * @param string $term
+ * @param array $options
+ *
+ * @return UnifiedSearchResult[]
+ * @throws RequestBuilderException
+ */
+ public function unifiedSearch(string $term, array $options): array {
+ $result = [];
+ $probe = $this->generateSearchProbe($term, $options);
+
+ try {
+ $circles = $this->circleService->getCircles($probe);
+ } catch (InitiatorNotFoundException $e) {
+ return [];
+ }
+
+ $iconPath = $this->urlGenerator->imagePath(Application::APP_ID, 'circles.svg');
+ $icon = $this->urlGenerator->getAbsoluteURL($iconPath);
+ foreach ($circles as $circle) {
+ $result[] = new UnifiedSearchResult(
+ '',
+ $circle->getDisplayName(),
+ $circle->getDescription(),
+ $circle->getUrl(),
+ $icon
+ );
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * @param string $term
+ * @param array $options
+ *
+ * @return CircleProbe
+ */
+ private function generateSearchProbe(string $term, array $options): CircleProbe {
+ $probe = new CircleProbe();
+ switch ($this->getInt('level', $options)) {
+ case Member::LEVEL_MEMBER:
+ $probe->mustBeMember();
+ break;
+ case Member::LEVEL_MODERATOR:
+ $probe->mustBeModerator();
+ break;
+ case Member::LEVEL_ADMIN:
+ $probe->mustBeAdmin();
+ break;
+ case Member::LEVEL_OWNER:
+ $probe->mustBeOwner();
+ break;
+ }
+
+ $probe->filterHiddenCircles()
+ ->filterBackendCircles();
+
+ $circle = new Circle();
+ $circle->setDisplayName($term)
+ ->setDescription($term);
+
+ $probe->setFilterCircle($circle);
+
+ return $probe;
+ }
}