FilesSearchProvider.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OCA\Files\Search;
  8. use InvalidArgumentException;
  9. use OC\Files\Search\SearchBinaryOperator;
  10. use OC\Files\Search\SearchComparison;
  11. use OC\Files\Search\SearchOrder;
  12. use OC\Files\Search\SearchQuery;
  13. use OC\Search\Filter\GroupFilter;
  14. use OC\Search\Filter\UserFilter;
  15. use OCP\Files\FileInfo;
  16. use OCP\Files\IMimeTypeDetector;
  17. use OCP\Files\IRootFolder;
  18. use OCP\Files\Node;
  19. use OCP\Files\Search\ISearchComparison;
  20. use OCP\Files\Search\ISearchOperator;
  21. use OCP\Files\Search\ISearchOrder;
  22. use OCP\IL10N;
  23. use OCP\IPreview;
  24. use OCP\IURLGenerator;
  25. use OCP\IUser;
  26. use OCP\Search\FilterDefinition;
  27. use OCP\Search\IFilter;
  28. use OCP\Search\IFilteringProvider;
  29. use OCP\Search\ISearchQuery;
  30. use OCP\Search\SearchResult;
  31. use OCP\Search\SearchResultEntry;
  32. use OCP\Share\IShare;
  33. class FilesSearchProvider implements IFilteringProvider {
  34. /** @var IL10N */
  35. private $l10n;
  36. /** @var IURLGenerator */
  37. private $urlGenerator;
  38. /** @var IMimeTypeDetector */
  39. private $mimeTypeDetector;
  40. /** @var IRootFolder */
  41. private $rootFolder;
  42. public function __construct(
  43. IL10N $l10n,
  44. IURLGenerator $urlGenerator,
  45. IMimeTypeDetector $mimeTypeDetector,
  46. IRootFolder $rootFolder,
  47. private IPreview $previewManager,
  48. ) {
  49. $this->l10n = $l10n;
  50. $this->urlGenerator = $urlGenerator;
  51. $this->mimeTypeDetector = $mimeTypeDetector;
  52. $this->rootFolder = $rootFolder;
  53. }
  54. /**
  55. * @inheritDoc
  56. */
  57. public function getId(): string {
  58. return 'files';
  59. }
  60. /**
  61. * @inheritDoc
  62. */
  63. public function getName(): string {
  64. return $this->l10n->t('Files');
  65. }
  66. /**
  67. * @inheritDoc
  68. */
  69. public function getOrder(string $route, array $routeParameters): int {
  70. if ($route === 'files.View.index') {
  71. // Before comments
  72. return -5;
  73. }
  74. return 5;
  75. }
  76. public function getSupportedFilters(): array {
  77. return [
  78. 'term',
  79. 'since',
  80. 'until',
  81. 'person',
  82. 'min-size',
  83. 'max-size',
  84. 'mime',
  85. 'type',
  86. 'path',
  87. 'is-favorite',
  88. 'title-only',
  89. ];
  90. }
  91. public function getAlternateIds(): array {
  92. return [];
  93. }
  94. public function getCustomFilters(): array {
  95. return [
  96. new FilterDefinition('min-size', FilterDefinition::TYPE_INT),
  97. new FilterDefinition('max-size', FilterDefinition::TYPE_INT),
  98. new FilterDefinition('mime', FilterDefinition::TYPE_STRING),
  99. new FilterDefinition('type', FilterDefinition::TYPE_STRING),
  100. new FilterDefinition('path', FilterDefinition::TYPE_STRING),
  101. new FilterDefinition('is-favorite', FilterDefinition::TYPE_BOOL),
  102. ];
  103. }
  104. public function search(IUser $user, ISearchQuery $query): SearchResult {
  105. $userFolder = $this->rootFolder->getUserFolder($user->getUID());
  106. $fileQuery = $this->buildSearchQuery($query, $user);
  107. return SearchResult::paginated(
  108. $this->l10n->t('Files'),
  109. array_map(function (Node $result) use ($userFolder) {
  110. $thumbnailUrl = $this->previewManager->isMimeSupported($result->getMimetype())
  111. ? $this->urlGenerator->linkToRouteAbsolute('core.Preview.getPreviewByFileId', ['x' => 32, 'y' => 32, 'fileId' => $result->getId()])
  112. : '';
  113. $icon = $result->getMimetype() === FileInfo::MIMETYPE_FOLDER
  114. ? 'icon-folder'
  115. : $this->mimeTypeDetector->mimeTypeIcon($result->getMimetype());
  116. $path = $userFolder->getRelativePath($result->getPath());
  117. // Use shortened link to centralize the various
  118. // files/folder url redirection in files.View.showFile
  119. $link = $this->urlGenerator->linkToRoute(
  120. 'files.View.showFile',
  121. ['fileid' => $result->getId()]
  122. );
  123. $searchResultEntry = new SearchResultEntry(
  124. $thumbnailUrl,
  125. $result->getName(),
  126. $this->formatSubline($path),
  127. $this->urlGenerator->getAbsoluteURL($link),
  128. $icon,
  129. );
  130. $searchResultEntry->addAttribute('fileId', (string)$result->getId());
  131. $searchResultEntry->addAttribute('path', $path);
  132. return $searchResultEntry;
  133. }, $userFolder->search($fileQuery)),
  134. $query->getCursor() + $query->getLimit()
  135. );
  136. }
  137. private function buildSearchQuery(ISearchQuery $query, IUser $user): SearchQuery {
  138. $comparisons = [];
  139. foreach ($query->getFilters() as $name => $filter) {
  140. $comparisons[] = match ($name) {
  141. 'term' => new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $filter->get() . '%'),
  142. 'since' => new SearchComparison(ISearchComparison::COMPARE_GREATER_THAN_EQUAL, 'mtime', $filter->get()->getTimestamp()),
  143. 'until' => new SearchComparison(ISearchComparison::COMPARE_LESS_THAN_EQUAL, 'mtime', $filter->get()->getTimestamp()),
  144. 'min-size' => new SearchComparison(ISearchComparison::COMPARE_GREATER_THAN_EQUAL, 'size', $filter->get()),
  145. 'max-size' => new SearchComparison(ISearchComparison::COMPARE_LESS_THAN_EQUAL, 'size', $filter->get()),
  146. 'mime' => new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', $filter->get()),
  147. 'type' => new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $filter->get() . '/%'),
  148. 'path' => new SearchComparison(ISearchComparison::COMPARE_LIKE, 'path', 'files/' . ltrim($filter->get(), '/') . '%'),
  149. 'person' => $this->buildPersonSearchQuery($filter),
  150. default => throw new InvalidArgumentException('Unsupported comparison'),
  151. };
  152. }
  153. return new SearchQuery(
  154. new SearchBinaryOperator(SearchBinaryOperator::OPERATOR_AND, $comparisons),
  155. $query->getLimit(),
  156. (int)$query->getCursor(),
  157. $query->getSortOrder() === ISearchQuery::SORT_DATE_DESC
  158. ? [new SearchOrder(ISearchOrder::DIRECTION_DESCENDING, 'mtime')]
  159. : [],
  160. $user
  161. );
  162. }
  163. private function buildPersonSearchQuery(IFilter $person): ISearchOperator {
  164. if ($person instanceof UserFilter) {
  165. return new SearchBinaryOperator(SearchBinaryOperator::OPERATOR_OR, [
  166. new SearchBinaryOperator(SearchBinaryOperator::OPERATOR_AND, [
  167. new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'share_with', $person->get()->getUID()),
  168. new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'share_type', IShare::TYPE_USER),
  169. ]),
  170. new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'owner', $person->get()->getUID()),
  171. ]);
  172. }
  173. if ($person instanceof GroupFilter) {
  174. return new SearchBinaryOperator(SearchBinaryOperator::OPERATOR_AND, [
  175. new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'share_with', $person->get()->getGID()),
  176. new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'share_type', IShare::TYPE_GROUP),
  177. ]);
  178. }
  179. throw new InvalidArgumentException('Unsupported filter type');
  180. }
  181. /**
  182. * Format subline for files
  183. *
  184. * @param string $path
  185. * @return string
  186. */
  187. private function formatSubline(string $path): string {
  188. // Do not show the location if the file is in root
  189. if (strrpos($path, '/') > 0) {
  190. $path = ltrim(dirname($path), '/');
  191. return $this->l10n->t('in %s', [$path]);
  192. } else {
  193. return '';
  194. }
  195. }
  196. }