EventsSearchProvider.php 6.9 KB

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