EventsSearchProvider.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @copyright Copyright (c) 2020, Georg Ehrke
  5. *
  6. * @author Georg Ehrke <oc.list@georgehrke.com>
  7. * @author Joas Schilling <coding@schilljs.com>
  8. * @author John Molakvoæ <skjnldsv@protonmail.com>
  9. *
  10. * @license GNU AGPL version 3 or any later version
  11. *
  12. * This program is free software: you can redistribute it and/or modify
  13. * it under the terms of the GNU Affero General Public License as
  14. * published by the Free Software Foundation, either version 3 of the
  15. * License, or (at your option) any later version.
  16. *
  17. * This program is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. * GNU Affero General Public License for more details.
  21. *
  22. * You should have received a copy of the GNU Affero General Public License
  23. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  24. *
  25. */
  26. namespace OCA\DAV\Search;
  27. use OCA\DAV\CalDAV\CalDavBackend;
  28. use OCP\IUser;
  29. use OCP\Search\ISearchQuery;
  30. use OCP\Search\SearchResult;
  31. use OCP\Search\SearchResultEntry;
  32. use Sabre\VObject\Component;
  33. use Sabre\VObject\DateTimeParser;
  34. use Sabre\VObject\Property;
  35. /**
  36. * Class EventsSearchProvider
  37. *
  38. * @package OCA\DAV\Search
  39. */
  40. class EventsSearchProvider extends ACalendarSearchProvider {
  41. /**
  42. * @var string[]
  43. */
  44. private static $searchProperties = [
  45. 'SUMMARY',
  46. 'LOCATION',
  47. 'DESCRIPTION',
  48. 'ATTENDEE',
  49. 'ORGANIZER',
  50. 'CATEGORIES',
  51. ];
  52. /**
  53. * @var string[]
  54. */
  55. private static $searchParameters = [
  56. 'ATTENDEE' => ['CN'],
  57. 'ORGANIZER' => ['CN'],
  58. ];
  59. /**
  60. * @var string
  61. */
  62. private static $componentType = 'VEVENT';
  63. /**
  64. * @inheritDoc
  65. */
  66. public function getId(): string {
  67. return 'calendar';
  68. }
  69. /**
  70. * @inheritDoc
  71. */
  72. public function getName(): string {
  73. return $this->l10n->t('Events');
  74. }
  75. /**
  76. * @inheritDoc
  77. */
  78. public function getOrder(string $route, array $routeParameters): int {
  79. if ($route === 'calendar.View.index') {
  80. return -1;
  81. }
  82. return 30;
  83. }
  84. /**
  85. * @inheritDoc
  86. */
  87. public function search(IUser $user,
  88. ISearchQuery $query): SearchResult {
  89. if (!$this->appManager->isEnabledForUser('calendar', $user)) {
  90. return SearchResult::complete($this->getName(), []);
  91. }
  92. $principalUri = 'principals/users/' . $user->getUID();
  93. $calendarsById = $this->getSortedCalendars($principalUri);
  94. $subscriptionsById = $this->getSortedSubscriptions($principalUri);
  95. $searchResults = $this->backend->searchPrincipalUri(
  96. $principalUri,
  97. $query->getTerm(),
  98. [self::$componentType],
  99. self::$searchProperties,
  100. self::$searchParameters,
  101. [
  102. 'limit' => $query->getLimit(),
  103. 'offset' => $query->getCursor(),
  104. ]
  105. );
  106. $formattedResults = \array_map(function (array $eventRow) use ($calendarsById, $subscriptionsById):SearchResultEntry {
  107. $component = $this->getPrimaryComponent($eventRow['calendardata'], self::$componentType);
  108. $title = (string)($component->SUMMARY ?? $this->l10n->t('Untitled event'));
  109. $subline = $this->generateSubline($component);
  110. if ($eventRow['calendartype'] === CalDavBackend::CALENDAR_TYPE_CALENDAR) {
  111. $calendar = $calendarsById[$eventRow['calendarid']];
  112. } else {
  113. $calendar = $subscriptionsById[$eventRow['calendarid']];
  114. }
  115. $resourceUrl = $this->getDeepLinkToCalendarApp($calendar['principaluri'], $calendar['uri'], $eventRow['uri']);
  116. return new SearchResultEntry('', $title, $subline, $resourceUrl, 'icon-calendar-dark', false);
  117. }, $searchResults);
  118. return SearchResult::paginated(
  119. $this->getName(),
  120. $formattedResults,
  121. $query->getCursor() + count($formattedResults)
  122. );
  123. }
  124. /**
  125. * @param string $principalUri
  126. * @param string $calendarUri
  127. * @param string $calendarObjectUri
  128. * @return string
  129. */
  130. protected function getDeepLinkToCalendarApp(string $principalUri,
  131. string $calendarUri,
  132. string $calendarObjectUri): string {
  133. $davUrl = $this->getDavUrlForCalendarObject($principalUri, $calendarUri, $calendarObjectUri);
  134. // This route will automatically figure out what recurrence-id to open
  135. return $this->urlGenerator->getAbsoluteURL(
  136. $this->urlGenerator->linkToRoute('calendar.view.index')
  137. . 'edit/'
  138. . base64_encode($davUrl)
  139. );
  140. }
  141. /**
  142. * @param string $principalUri
  143. * @param string $calendarUri
  144. * @param string $calendarObjectUri
  145. * @return string
  146. */
  147. protected function getDavUrlForCalendarObject(string $principalUri,
  148. string $calendarUri,
  149. string $calendarObjectUri): string {
  150. [,, $principalId] = explode('/', $principalUri, 3);
  151. return $this->urlGenerator->linkTo('', 'remote.php') . '/dav/calendars/'
  152. . $principalId . '/'
  153. . $calendarUri . '/'
  154. . $calendarObjectUri;
  155. }
  156. /**
  157. * @param Component $eventComponent
  158. * @return string
  159. */
  160. protected function generateSubline(Component $eventComponent): string {
  161. $dtStart = $eventComponent->DTSTART;
  162. $dtEnd = $this->getDTEndForEvent($eventComponent);
  163. $isAllDayEvent = $dtStart instanceof Property\ICalendar\Date;
  164. $startDateTime = new \DateTime($dtStart->getDateTime()->format(\DateTimeInterface::ATOM));
  165. $endDateTime = new \DateTime($dtEnd->getDateTime()->format(\DateTimeInterface::ATOM));
  166. if ($isAllDayEvent) {
  167. $endDateTime->modify('-1 day');
  168. if ($this->isDayEqual($startDateTime, $endDateTime)) {
  169. return $this->l10n->l('date', $startDateTime, ['width' => 'medium']);
  170. }
  171. $formattedStart = $this->l10n->l('date', $startDateTime, ['width' => 'medium']);
  172. $formattedEnd = $this->l10n->l('date', $endDateTime, ['width' => 'medium']);
  173. return "$formattedStart - $formattedEnd";
  174. }
  175. $formattedStartDate = $this->l10n->l('date', $startDateTime, ['width' => 'medium']);
  176. $formattedEndDate = $this->l10n->l('date', $endDateTime, ['width' => 'medium']);
  177. $formattedStartTime = $this->l10n->l('time', $startDateTime, ['width' => 'short']);
  178. $formattedEndTime = $this->l10n->l('time', $endDateTime, ['width' => 'short']);
  179. if ($this->isDayEqual($startDateTime, $endDateTime)) {
  180. return "$formattedStartDate $formattedStartTime - $formattedEndTime";
  181. }
  182. return "$formattedStartDate $formattedStartTime - $formattedEndDate $formattedEndTime";
  183. }
  184. /**
  185. * @param Component $eventComponent
  186. * @return Property
  187. */
  188. protected function getDTEndForEvent(Component $eventComponent):Property {
  189. if (isset($eventComponent->DTEND)) {
  190. $end = $eventComponent->DTEND;
  191. } elseif (isset($eventComponent->DURATION)) {
  192. $isFloating = $eventComponent->DTSTART->isFloating();
  193. $end = clone $eventComponent->DTSTART;
  194. $endDateTime = $end->getDateTime();
  195. $endDateTime = $endDateTime->add(DateTimeParser::parse($eventComponent->DURATION->getValue()));
  196. $end->setDateTime($endDateTime, $isFloating);
  197. } elseif (!$eventComponent->DTSTART->hasTime()) {
  198. $isFloating = $eventComponent->DTSTART->isFloating();
  199. $end = clone $eventComponent->DTSTART;
  200. $endDateTime = $end->getDateTime();
  201. $endDateTime = $endDateTime->modify('+1 day');
  202. $end->setDateTime($endDateTime, $isFloating);
  203. } else {
  204. $end = clone $eventComponent->DTSTART;
  205. }
  206. return $end;
  207. }
  208. /**
  209. * @param \DateTime $dtStart
  210. * @param \DateTime $dtEnd
  211. * @return bool
  212. */
  213. protected function isDayEqual(\DateTime $dtStart,
  214. \DateTime $dtEnd) {
  215. return $dtStart->format('Y-m-d') === $dtEnd->format('Y-m-d');
  216. }
  217. }