diff --git a/CHANGELOG.en.md b/CHANGELOG.en.md index 51419c06b..20609473a 100644 --- a/CHANGELOG.en.md +++ b/CHANGELOG.en.md @@ -5,6 +5,12 @@ # Changelog +## v5.0.0 - tbd + +- **Unified Search integration** + + You can now use the Unified Search to search forms based on the title and the description. + ## v4.3.0 - 2024-10-04 - **New question type: Files** diff --git a/css/forms.css b/css/forms.css index 9d66d1633..62b659990 100644 --- a/css/forms.css +++ b/css/forms.css @@ -9,3 +9,14 @@ html { scroll-padding-top: calc(var(--header-height) + 60px); } + +.icon-forms { + background-image: url(../img/forms-dark.svg); + filter: var(--background-invert-if-dark); +} + +.icon-forms-white, +.icon-forms.icon-white { + background-image: url(../img/forms.svg); + filter: var(--background-invert-if-dark); +} diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 1dbf407bc..319e48f3e 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -14,6 +14,7 @@ use OCA\Forms\FormsMigrator; use OCA\Forms\Listener\AnalyticsDatasourceListener; use OCA\Forms\Listener\UserDeletedListener; +use OCA\Forms\Search\SearchProvider; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; @@ -42,6 +43,7 @@ public function register(IRegistrationContext $context): void { $context->registerCapability(Capabilities::class); $context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class); $context->registerEventListener(DatasourceEvent::class, AnalyticsDatasourceListener::class); + $context->registerSearchProvider(SearchProvider::class); $context->registerUserMigrator(FormsMigrator::class); } diff --git a/lib/Db/FormMapper.php b/lib/Db/FormMapper.php index 89a032f3b..649494b3b 100644 --- a/lib/Db/FormMapper.php +++ b/lib/Db/FormMapper.php @@ -97,9 +97,10 @@ public function findByHash(string $hash): Form { * @param string[] $groups IDs of groups the user is memeber of * @param string[] $teams IDs of teams the user is memeber of * @param bool $filterShown Set to false to also include forms shared but not visible on sidebar + * @param string $queryTerm optional: The search query for universal search * @return Form[] */ - public function findSharedForms(string $userId, array $groups = [], array $teams = [], bool $filterShown = true): array { + public function findSharedForms(string $userId, array $groups = [], array $teams = [], bool $filterShown = true, ?string $queryTerm = null): array { $qbShares = $this->db->getQueryBuilder(); $qbForms = $this->db->getQueryBuilder(); @@ -156,6 +157,11 @@ public function findSharedForms(string $userId, array $groups = [], array $teams ->addOrderBy('last_updated', 'DESC') ->addOrderBy('created', 'DESC'); + if ($queryTerm) { + $qbForms->andWhere($qbForms->expr()->iLike('title', $qbForms->createNamedParameter('%' . $this->db->escapeLikeParameter($queryTerm) . '%')) . + ' OR ' . $qbForms->expr()->iLike('description', $qbForms->createNamedParameter('%' . $this->db->escapeLikeParameter($queryTerm) . '%'))); + } + // We need to add the parameters from the shared forms IDs select to the final select query $qbForms->setParameters($qbShares->getParameters(), $qbShares->getParameterTypes()); @@ -163,9 +169,10 @@ public function findSharedForms(string $userId, array $groups = [], array $teams } /** + * @param string $queryTerm optional: The search query for universal search * @return Form[] */ - public function findAllByOwnerId(string $ownerId): array { + public function findAllByOwnerId(string $ownerId, ?string $queryTerm = null): array { $qb = $this->db->getQueryBuilder(); $qb->select('*') @@ -177,6 +184,11 @@ public function findAllByOwnerId(string $ownerId): array { ->addOrderBy('last_updated', 'DESC') ->addOrderBy('created', 'DESC'); + if ($queryTerm) { + $qb->andWhere($qb->expr()->iLike('title', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($queryTerm) . '%')) . + ' OR ' . $qb->expr()->iLike('description', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($queryTerm) . '%'))); + } + return $this->findEntities($qb); } diff --git a/lib/Search/FormsSearchResultEntry.php b/lib/Search/FormsSearchResultEntry.php new file mode 100644 index 000000000..7ddd7cbb3 --- /dev/null +++ b/lib/Search/FormsSearchResultEntry.php @@ -0,0 +1,22 @@ +linkToRoute('forms.page.views', ['hash' => $form->getHash(), 'view' => 'submit']); + $iconURL = $urlGenerator->getAbsoluteURL(($urlGenerator->imagePath(Application::APP_ID, 'forms-dark.svg'))); + parent::__construct($iconURL, $form->getTitle(), $form->getDescription(), $formURL, 'icon-forms'); + } +} diff --git a/lib/Search/SearchProvider.php b/lib/Search/SearchProvider.php new file mode 100644 index 000000000..4b0c2e61b --- /dev/null +++ b/lib/Search/SearchProvider.php @@ -0,0 +1,67 @@ +l10n->t('Forms'); + } + + public function search(IUser $user, ISearchQuery $query): SearchResult { + $forms = $this->formsService->search($query); + + $results = array_map(function (Form $form) { + return [ + 'object' => $form, + 'entry' => new FormsSearchResultEntry($form, $this->urlGenerator) + ]; + }, $forms); + + $resultEntries = array_map(function (array $result) { + return $result['entry']; + }, $results); + + return SearchResult::complete( + $this->l10n->t('Forms'), + $resultEntries + ); + } + + public function getOrder(string $route, array $routeParameters): int { + if (str_contains($route, Application::APP_ID)) { + // Active app, prefer my results + return -1; + } + return 77; + } +} diff --git a/lib/Service/FormsService.php b/lib/Service/FormsService.php index d9be9a3c8..62a85799c 100644 --- a/lib/Service/FormsService.php +++ b/lib/Service/FormsService.php @@ -29,6 +29,7 @@ use OCP\IUser; use OCP\IUserManager; use OCP\IUserSession; +use OCP\Search\ISearchQuery; use OCP\Security\ISecureRandom; use OCP\Share\IShare; @@ -698,6 +699,33 @@ public function areExtraSettingsValid(array $extraSettings, string $questionType return true; } + /** + * Get list of forms + * + * @param ISearchQuery $query the query to search the forms + * @return Form[] list of forms that match the query + */ + public function search(ISearchQuery $query): array { + $formsList = []; + $groups = $this->groupManager->getUserGroupIds($this->currentUser); + $teams = $this->circlesService->getUserTeamIds($this->currentUser->getUID()); + + try { + $ownedForms = $this->formMapper->findAllByOwnerId($this->currentUser->getUID(), $query->getTerm()); + $sharedForms = $this->formMapper->findSharedForms( + $this->currentUser->getUID(), + $groups, + $teams, + true, + $query->getTerm() + ); + $formsList = array_merge($ownedForms, $sharedForms); + } catch (DoesNotExistException $e) { + // silent catch + } + return $formsList; + } + public function getFilePath(Form $form): ?string { $fileId = $form->getFileId();