Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down
11 changes: 11 additions & 0 deletions css/forms.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
2 changes: 2 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -42,6 +43,7 @@
$context->registerCapability(Capabilities::class);
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class);
$context->registerEventListener(DatasourceEvent::class, AnalyticsDatasourceListener::class);
$context->registerSearchProvider(SearchProvider::class);

Check warning on line 46 in lib/AppInfo/Application.php

View check run for this annotation

Codecov / codecov/patch

lib/AppInfo/Application.php#L46

Added line #L46 was not covered by tests
$context->registerUserMigrator(FormsMigrator::class);
}

Expand Down
53 changes: 39 additions & 14 deletions lib/Db/FormMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,46 +97,47 @@
* @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();

$memberships = $qbShares->expr()->orX();
// share type user and share with current user
$memberships->add(
$qbShares->expr()->andX(
$qbShares->expr()->eq('shares.share_type', $qbShares->createNamedParameter(IShare::TYPE_USER)),
$qbShares->expr()->eq('shares.share_with', $qbShares->createNamedParameter($userId, IQueryBuilder::PARAM_STR)),
$qbShares->expr()->eq('shares.share_type', $qbShares->createNamedParameter(IShare::TYPE_USER, IQueryBuilder::PARAM_STR, ':share_type_user')),
$qbShares->expr()->eq('shares.share_with', $qbShares->createNamedParameter($userId, IQueryBuilder::PARAM_STR, ':share_with_user')),
),
);
// share type group and one of the user groups
if (!empty($groups)) {
$memberships->add(
$qbShares->expr()->andX(
$qbShares->expr()->eq('shares.share_type', $qbShares->createNamedParameter(IShare::TYPE_GROUP)),
$qbShares->expr()->in('shares.share_with', $qbShares->createNamedParameter($groups, IQueryBuilder::PARAM_STR_ARRAY)),
$qbShares->expr()->eq('shares.share_type', $qbShares->createNamedParameter(IShare::TYPE_GROUP, IQueryBuilder::PARAM_STR, ':share_type_group')),
$qbShares->expr()->in('shares.share_with', $qbShares->createNamedParameter($groups, IQueryBuilder::PARAM_STR_ARRAY, ':share_with_groups')),

Check warning on line 120 in lib/Db/FormMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/Db/FormMapper.php#L119-L120

Added lines #L119 - L120 were not covered by tests
),
);
}
// share type team and one of the user teams
if (!empty($teams)) {
$memberships->add(
$qbShares->expr()->andX(
$qbShares->expr()->eq('shares.share_type', $qbShares->createNamedParameter(IShare::TYPE_CIRCLE)),
$qbShares->expr()->in('shares.share_with', $qbShares->createNamedParameter($teams, IQueryBuilder::PARAM_STR_ARRAY)),
$qbShares->expr()->eq('shares.share_type', $qbShares->createNamedParameter(IShare::TYPE_CIRCLE, IQueryBuilder::PARAM_STR, ':share_type_team')),
$qbShares->expr()->in('shares.share_with', $qbShares->createNamedParameter($teams, IQueryBuilder::PARAM_STR_ARRAY, ':share_with_teams')),

Check warning on line 129 in lib/Db/FormMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/Db/FormMapper.php#L128-L129

Added lines #L128 - L129 were not covered by tests
),
);
}

// build expression for publicy shared forms (default only directly shown)
// build expression for publicly shared forms (default only directly shown)
if ($filterShown) {
// Only shown
$access = $qbShares->expr()->in('access_enum', $qbShares->createNamedParameter(Constants::FORM_ACCESS_ARRAY_SHOWN, IQueryBuilder::PARAM_INT_ARRAY));
$access = $qbShares->expr()->in('access_enum', $qbShares->createNamedParameter(Constants::FORM_ACCESS_ARRAY_SHOWN, IQueryBuilder::PARAM_INT_ARRAY, ':access_shown'));
} else {
// All
$access = $qbShares->expr()->neq('access_enum', $qbShares->createNamedParameter(Constants::FORM_ACCESS_NOPUBLICSHARE, IQueryBuilder::PARAM_INT));
$access = $qbShares->expr()->neq('access_enum', $qbShares->createNamedParameter(Constants::FORM_ACCESS_NOPUBLICSHARE, IQueryBuilder::PARAM_INT, ':access_nopublicshare'));
}

// Select all DISTINCT IDs of shared forms
Expand All @@ -145,7 +146,7 @@
->leftJoin('forms', $this->shareMapper->getTableName(), 'shares', $qbShares->expr()->eq('forms.id', 'shares.form_id'))
->where($memberships)
->orWhere($access)
->andWhere($qbShares->expr()->neq('forms.owner_id', $qbShares->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
->andWhere($qbShares->expr()->neq('forms.owner_id', $qbShares->createNamedParameter($userId, IQueryBuilder::PARAM_STR, ':owner_id')));

// Select the whole forms for the DISTINCT shared forms IDs
$qbForms->select('*')
Expand All @@ -156,16 +157,30 @@
->addOrderBy('last_updated', 'DESC')
->addOrderBy('created', 'DESC');

// We need to add the parameters from the shared forms IDs select to the final select query
$qbForms->setParameters($qbShares->getParameters(), $qbShares->getParameterTypes());
if ($queryTerm) {
$likeParameter = '%' . $this->db->escapeLikeParameter($queryTerm) . '%';
$qbForms->andWhere(
$qbForms->expr()->orX(
$qbForms->expr()->iLike('title', $qbForms->createNamedParameter($likeParameter, IQueryBuilder::PARAM_STR, ':query_term_title')),
$qbForms->expr()->iLike('description', $qbForms->createNamedParameter($likeParameter, IQueryBuilder::PARAM_STR, ':query_term_description'))
)
);

Check warning on line 167 in lib/Db/FormMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/Db/FormMapper.php#L161-L167

Added lines #L161 - L167 were not covered by tests
}

// Merge parameters and parameter types from $qbShares and $qbForms
$qbFormsParams = array_merge($qbShares->getParameters(), $qbForms->getParameters());
$qbFormsParamTypes = array_merge($qbShares->getParameterTypes(), $qbForms->getParameterTypes());

$qbForms->setParameters($qbFormsParams, $qbFormsParamTypes);

return $this->findEntities($qbForms);
}

/**
* @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 {

Check warning on line 183 in lib/Db/FormMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/Db/FormMapper.php#L183

Added line #L183 was not covered by tests
$qb = $this->db->getQueryBuilder();

$qb->select('*')
Expand All @@ -177,6 +192,16 @@
->addOrderBy('last_updated', 'DESC')
->addOrderBy('created', 'DESC');

if ($queryTerm) {
$likeParameter = '%' . $this->db->escapeLikeParameter($queryTerm) . '%';
$qb->andWhere(
$qb->expr()->orX(
$qb->expr()->iLike('title', $qb->createNamedParameter($likeParameter, IQueryBuilder::PARAM_STR, ':query_term_title')),
$qb->expr()->iLike('description', $qb->createNamedParameter($likeParameter, IQueryBuilder::PARAM_STR, ':query_term_description'))
)
);

Check warning on line 202 in lib/Db/FormMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/Db/FormMapper.php#L195-L202

Added lines #L195 - L202 were not covered by tests
}

return $this->findEntities($qb);
}

Expand Down
22 changes: 22 additions & 0 deletions lib/Search/FormsSearchResultEntry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Forms\Search;

use OCA\Forms\AppInfo\Application;
use OCA\Forms\Db\Form;
use OCP\IURLGenerator;
use OCP\Search\SearchResultEntry;

class FormsSearchResultEntry extends SearchResultEntry {
public function __construct(Form $form, IURLGenerator $urlGenerator) {
$formURL = $urlGenerator->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');

Check warning on line 20 in lib/Search/FormsSearchResultEntry.php

View check run for this annotation

Codecov / codecov/patch

lib/Search/FormsSearchResultEntry.php#L17-L20

Added lines #L17 - L20 were not covered by tests
}
}
67 changes: 67 additions & 0 deletions lib/Search/SearchProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Forms\Search;

use OCA\Forms\AppInfo\Application;
use OCA\Forms\Db\Form;
use OCA\Forms\Service\FormsService;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\Search\IProvider;
use OCP\Search\ISearchQuery;
use OCP\Search\SearchResult;

class SearchProvider implements IProvider {
/**
* @psalm-suppress PossiblyUnusedMethod
*/
public function __construct(

Check warning on line 25 in lib/Search/SearchProvider.php

View check run for this annotation

Codecov / codecov/patch

lib/Search/SearchProvider.php#L25

Added line #L25 was not covered by tests
private IL10N $l10n,
private IURLGenerator $urlGenerator,
private FormsService $formsService,
) {
}

Check warning on line 30 in lib/Search/SearchProvider.php

View check run for this annotation

Codecov / codecov/patch

lib/Search/SearchProvider.php#L30

Added line #L30 was not covered by tests

public function getId(): string {
return 'forms';

Check warning on line 33 in lib/Search/SearchProvider.php

View check run for this annotation

Codecov / codecov/patch

lib/Search/SearchProvider.php#L32-L33

Added lines #L32 - L33 were not covered by tests
}

public function getName(): string {
return $this->l10n->t('Forms');

Check warning on line 37 in lib/Search/SearchProvider.php

View check run for this annotation

Codecov / codecov/patch

lib/Search/SearchProvider.php#L36-L37

Added lines #L36 - L37 were not covered by tests
}

public function search(IUser $user, ISearchQuery $query): SearchResult {
$forms = $this->formsService->search($query);

Check warning on line 41 in lib/Search/SearchProvider.php

View check run for this annotation

Codecov / codecov/patch

lib/Search/SearchProvider.php#L40-L41

Added lines #L40 - L41 were not covered by tests

$results = array_map(function (Form $form) {
return [
'object' => $form,
'entry' => new FormsSearchResultEntry($form, $this->urlGenerator)
];
}, $forms);

Check warning on line 48 in lib/Search/SearchProvider.php

View check run for this annotation

Codecov / codecov/patch

lib/Search/SearchProvider.php#L43-L48

Added lines #L43 - L48 were not covered by tests

$resultEntries = array_map(function (array $result) {
return $result['entry'];
}, $results);

Check warning on line 52 in lib/Search/SearchProvider.php

View check run for this annotation

Codecov / codecov/patch

lib/Search/SearchProvider.php#L50-L52

Added lines #L50 - L52 were not covered by tests

return SearchResult::complete(
$this->l10n->t('Forms'),
$resultEntries
);

Check warning on line 57 in lib/Search/SearchProvider.php

View check run for this annotation

Codecov / codecov/patch

lib/Search/SearchProvider.php#L54-L57

Added lines #L54 - L57 were not covered by tests
}

public function getOrder(string $route, array $routeParameters): int {
if (str_contains($route, Application::APP_ID)) {

Check warning on line 61 in lib/Search/SearchProvider.php

View check run for this annotation

Codecov / codecov/patch

lib/Search/SearchProvider.php#L60-L61

Added lines #L60 - L61 were not covered by tests
// Active app, prefer my results
return -1;

Check warning on line 63 in lib/Search/SearchProvider.php

View check run for this annotation

Codecov / codecov/patch

lib/Search/SearchProvider.php#L63

Added line #L63 was not covered by tests
}
return 77;

Check warning on line 65 in lib/Search/SearchProvider.php

View check run for this annotation

Codecov / codecov/patch

lib/Search/SearchProvider.php#L65

Added line #L65 was not covered by tests
}
}
28 changes: 28 additions & 0 deletions lib/Service/FormsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -698,6 +699,33 @@
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());

Check warning on line 711 in lib/Service/FormsService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/FormsService.php#L708-L711

Added lines #L708 - L711 were not covered by tests

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) {

Check warning on line 723 in lib/Service/FormsService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/FormsService.php#L714-L723

Added lines #L714 - L723 were not covered by tests
// silent catch
}
return $formsList;

Check warning on line 726 in lib/Service/FormsService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/FormsService.php#L726

Added line #L726 was not covered by tests
}

public function getFilePath(Form $form): ?string {
$fileId = $form->getFileId();

Expand Down
Loading