AutoCompleteController.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OC\Core\Controller;
  8. use OC\Core\ResponseDefinitions;
  9. use OCP\AppFramework\Http;
  10. use OCP\AppFramework\Http\Attribute\ApiRoute;
  11. use OCP\AppFramework\Http\Attribute\NoAdminRequired;
  12. use OCP\AppFramework\Http\DataResponse;
  13. use OCP\AppFramework\OCSController;
  14. use OCP\Collaboration\AutoComplete\AutoCompleteEvent;
  15. use OCP\Collaboration\AutoComplete\AutoCompleteFilterEvent;
  16. use OCP\Collaboration\AutoComplete\IManager;
  17. use OCP\Collaboration\Collaborators\ISearch;
  18. use OCP\EventDispatcher\IEventDispatcher;
  19. use OCP\IRequest;
  20. use OCP\Share\IShare;
  21. /**
  22. * @psalm-import-type CoreAutocompleteResult from ResponseDefinitions
  23. */
  24. class AutoCompleteController extends OCSController {
  25. public function __construct(
  26. string $appName,
  27. IRequest $request,
  28. private ISearch $collaboratorSearch,
  29. private IManager $autoCompleteManager,
  30. private IEventDispatcher $dispatcher,
  31. ) {
  32. parent::__construct($appName, $request);
  33. }
  34. /**
  35. * Autocomplete a query
  36. *
  37. * @param string $search Text to search for
  38. * @param string|null $itemType Type of the items to search for
  39. * @param string|null $itemId ID of the items to search for
  40. * @param string|null $sorter can be piped, top prio first, e.g.: "commenters|share-recipients"
  41. * @param list<int> $shareTypes Types of shares to search for
  42. * @param int $limit Maximum number of results to return
  43. *
  44. * @return DataResponse<Http::STATUS_OK, list<CoreAutocompleteResult>, array{}>
  45. *
  46. * 200: Autocomplete results returned
  47. */
  48. #[NoAdminRequired]
  49. #[ApiRoute(verb: 'GET', url: '/autocomplete/get', root: '/core')]
  50. public function get(string $search, ?string $itemType, ?string $itemId, ?string $sorter = null, array $shareTypes = [IShare::TYPE_USER], int $limit = 10): DataResponse {
  51. // if enumeration/user listings are disabled, we'll receive an empty
  52. // result from search() – thus nothing else to do here.
  53. [$results,] = $this->collaboratorSearch->search($search, $shareTypes, false, $limit, 0);
  54. $event = new AutoCompleteEvent([
  55. 'search' => $search,
  56. 'results' => $results,
  57. 'itemType' => $itemType,
  58. 'itemId' => $itemId,
  59. 'sorter' => $sorter,
  60. 'shareTypes' => $shareTypes,
  61. 'limit' => $limit,
  62. ]);
  63. $this->dispatcher->dispatch(IManager::class . '::filterResults', $event);
  64. $results = $event->getResults();
  65. $event = new AutoCompleteFilterEvent(
  66. $results,
  67. $search,
  68. $itemType,
  69. $itemId,
  70. $sorter,
  71. $shareTypes,
  72. $limit,
  73. );
  74. $this->dispatcher->dispatchTyped($event);
  75. $results = $event->getResults();
  76. $exactMatches = $results['exact'];
  77. unset($results['exact']);
  78. $results = array_merge_recursive($exactMatches, $results);
  79. if ($sorter !== null) {
  80. $sorters = array_reverse(explode('|', $sorter));
  81. $this->autoCompleteManager->runSorters($sorters, $results, [
  82. 'itemType' => $itemType,
  83. 'itemId' => $itemId,
  84. ]);
  85. }
  86. // transform to expected format
  87. $results = $this->prepareResultArray($results);
  88. return new DataResponse($results);
  89. }
  90. /**
  91. * @return list<CoreAutocompleteResult>
  92. */
  93. protected function prepareResultArray(array $results): array {
  94. $output = [];
  95. /** @var string $type */
  96. foreach ($results as $type => $subResult) {
  97. foreach ($subResult as $result) {
  98. /** @var ?string $icon */
  99. $icon = array_key_exists('icon', $result) ? $result['icon'] : null;
  100. /** @var string $label */
  101. $label = $result['label'];
  102. /** @var ?string $subline */
  103. $subline = array_key_exists('subline', $result) ? $result['subline'] : null;
  104. /** @var ?array{status: string, message: ?string, icon: ?string, clearAt: ?int} $status */
  105. $status = array_key_exists('status', $result) && is_array($result['status']) && !empty($result['status']) ? $result['status'] : null;
  106. /** @var ?string $shareWithDisplayNameUnique */
  107. $shareWithDisplayNameUnique = array_key_exists('shareWithDisplayNameUnique', $result) ? $result['shareWithDisplayNameUnique'] : null;
  108. $output[] = [
  109. 'id' => (string)$result['value']['shareWith'],
  110. 'label' => $label,
  111. 'icon' => $icon ?? '',
  112. 'source' => $type,
  113. 'status' => $status ?? '',
  114. 'subline' => $subline ?? '',
  115. 'shareWithDisplayNameUnique' => $shareWithDisplayNameUnique ?? '',
  116. ];
  117. }
  118. }
  119. return $output;
  120. }
  121. }