AppCalendar.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OCA\DAV\CalDAV\AppCalendar;
  8. use OCA\DAV\CalDAV\Integration\ExternalCalendar;
  9. use OCA\DAV\CalDAV\Plugin;
  10. use OCP\Calendar\ICalendar;
  11. use OCP\Calendar\ICreateFromString;
  12. use OCP\Constants;
  13. use Sabre\CalDAV\CalendarQueryValidator;
  14. use Sabre\CalDAV\ICalendarObject;
  15. use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet;
  16. use Sabre\DAV\Exception\Forbidden;
  17. use Sabre\DAV\Exception\NotFound;
  18. use Sabre\DAV\PropPatch;
  19. use Sabre\VObject\Component\VCalendar;
  20. use Sabre\VObject\Reader;
  21. class AppCalendar extends ExternalCalendar {
  22. protected ICalendar $calendar;
  23. public function __construct(
  24. string $appId,
  25. ICalendar $calendar,
  26. protected string $principal,
  27. ) {
  28. parent::__construct($appId, $calendar->getUri());
  29. $this->calendar = $calendar;
  30. }
  31. /**
  32. * Return permissions supported by the backend calendar
  33. * @return int Permissions based on \OCP\Constants
  34. */
  35. public function getPermissions(): int {
  36. // Make sure to only promote write support if the backend implement the correct interface
  37. if ($this->calendar instanceof ICreateFromString) {
  38. return $this->calendar->getPermissions();
  39. }
  40. return Constants::PERMISSION_READ;
  41. }
  42. public function getOwner(): ?string {
  43. return $this->principal;
  44. }
  45. public function getGroup(): ?string {
  46. return null;
  47. }
  48. public function getACL(): array {
  49. $acl = [
  50. [
  51. 'privilege' => '{DAV:}read',
  52. 'principal' => $this->getOwner(),
  53. 'protected' => true,
  54. ],
  55. [
  56. 'privilege' => '{DAV:}write-properties',
  57. 'principal' => $this->getOwner(),
  58. 'protected' => true,
  59. ]
  60. ];
  61. if ($this->getPermissions() & Constants::PERMISSION_CREATE) {
  62. $acl[] = [
  63. 'privilege' => '{DAV:}write',
  64. 'principal' => $this->getOwner(),
  65. 'protected' => true,
  66. ];
  67. }
  68. return $acl;
  69. }
  70. public function setACL(array $acl): void {
  71. throw new Forbidden('Setting ACL is not supported on this node');
  72. }
  73. public function getSupportedPrivilegeSet(): ?array {
  74. // Use the default one
  75. return null;
  76. }
  77. public function getLastModified(): ?int {
  78. // unknown
  79. return null;
  80. }
  81. public function delete(): void {
  82. // No method for deleting a calendar in OCP\Calendar\ICalendar
  83. throw new Forbidden('Deleting an entry is not implemented');
  84. }
  85. public function createFile($name, $data = null) {
  86. if ($this->calendar instanceof ICreateFromString) {
  87. if (is_resource($data)) {
  88. $data = stream_get_contents($data) ?: null;
  89. }
  90. $this->calendar->createFromString($name, is_null($data) ? '' : $data);
  91. return null;
  92. } else {
  93. throw new Forbidden('Creating a new entry is not allowed');
  94. }
  95. }
  96. public function getProperties($properties) {
  97. return [
  98. '{DAV:}displayname' => $this->calendar->getDisplayName() ?: $this->calendar->getKey(),
  99. '{http://apple.com/ns/ical/}calendar-color' => $this->calendar->getDisplayColor() ?: '#0082c9',
  100. '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VEVENT', 'VJOURNAL', 'VTODO']),
  101. ];
  102. }
  103. public function calendarQuery(array $filters) {
  104. $result = [];
  105. $objects = $this->getChildren();
  106. foreach ($objects as $object) {
  107. if ($this->validateFilterForObject($object, $filters)) {
  108. $result[] = $object->getName();
  109. }
  110. }
  111. return $result;
  112. }
  113. protected function validateFilterForObject(ICalendarObject $object, array $filters): bool {
  114. /** @var \Sabre\VObject\Component\VCalendar */
  115. $vObject = Reader::read($object->get());
  116. $validator = new CalendarQueryValidator();
  117. $result = $validator->validate($vObject, $filters);
  118. // Destroy circular references so PHP will GC the object.
  119. $vObject->destroy();
  120. return $result;
  121. }
  122. public function childExists($name): bool {
  123. try {
  124. $this->getChild($name);
  125. return true;
  126. } catch (NotFound $error) {
  127. return false;
  128. }
  129. }
  130. public function getChild($name) {
  131. // Try to get calendar by filename
  132. $children = $this->calendar->search($name, ['X-FILENAME']);
  133. if (count($children) === 0) {
  134. // If nothing found try to get by UID from filename
  135. $pos = strrpos($name, '.ics');
  136. $children = $this->calendar->search(substr($name, 0, $pos ?: null), ['UID']);
  137. }
  138. if (count($children) > 0) {
  139. return new CalendarObject($this, $this->calendar, new VCalendar($children));
  140. }
  141. throw new NotFound('Node not found');
  142. }
  143. /**
  144. * @return ICalendarObject[]
  145. */
  146. public function getChildren(): array {
  147. $objects = $this->calendar->search('');
  148. // We need to group by UID (actually by filename but we do not have that information)
  149. $result = [];
  150. foreach ($objects as $object) {
  151. $uid = (string)$object['UID'] ?: uniqid();
  152. if (!isset($result[$uid])) {
  153. $result[$uid] = [];
  154. }
  155. $result[$uid][] = $object;
  156. }
  157. return array_map(function (array $children) {
  158. return new CalendarObject($this, $this->calendar, new VCalendar($children));
  159. }, $result);
  160. }
  161. public function propPatch(PropPatch $propPatch): void {
  162. // no setDisplayColor or setDisplayName in \OCP\Calendar\ICalendar
  163. }
  164. }