Calendar.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. <?php
  2. declare(strict_types=1);
  3. namespace Sabre\CalDAV;
  4. use Sabre\DAV;
  5. use Sabre\DAV\PropPatch;
  6. use Sabre\DAVACL;
  7. /**
  8. * This object represents a CalDAV calendar.
  9. *
  10. * A calendar can contain multiple TODO and or Events. These are represented
  11. * as \Sabre\CalDAV\CalendarObject objects.
  12. *
  13. * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
  14. * @author Evert Pot (http://evertpot.com/)
  15. * @license http://sabre.io/license/ Modified BSD License
  16. */
  17. class Calendar implements ICalendar, DAV\IProperties, DAV\Sync\ISyncCollection, DAV\IMultiGet
  18. {
  19. use DAVACL\ACLTrait;
  20. /**
  21. * This is an array with calendar information.
  22. *
  23. * @var array
  24. */
  25. protected $calendarInfo;
  26. /**
  27. * CalDAV backend.
  28. *
  29. * @var Backend\BackendInterface
  30. */
  31. protected $caldavBackend;
  32. /**
  33. * Constructor.
  34. *
  35. * @param array $calendarInfo
  36. */
  37. public function __construct(Backend\BackendInterface $caldavBackend, $calendarInfo)
  38. {
  39. $this->caldavBackend = $caldavBackend;
  40. $this->calendarInfo = $calendarInfo;
  41. }
  42. /**
  43. * Returns the name of the calendar.
  44. *
  45. * @return string
  46. */
  47. public function getName()
  48. {
  49. return $this->calendarInfo['uri'];
  50. }
  51. /**
  52. * Updates properties on this node.
  53. *
  54. * This method received a PropPatch object, which contains all the
  55. * information about the update.
  56. *
  57. * To update specific properties, call the 'handle' method on this object.
  58. * Read the PropPatch documentation for more information.
  59. */
  60. public function propPatch(PropPatch $propPatch)
  61. {
  62. return $this->caldavBackend->updateCalendar($this->calendarInfo['id'], $propPatch);
  63. }
  64. /**
  65. * Returns the list of properties.
  66. *
  67. * @param array $requestedProperties
  68. *
  69. * @return array
  70. */
  71. public function getProperties($requestedProperties)
  72. {
  73. $response = [];
  74. foreach ($this->calendarInfo as $propName => $propValue) {
  75. if (!is_null($propValue) && '{' === $propName[0]) {
  76. $response[$propName] = $this->calendarInfo[$propName];
  77. }
  78. }
  79. return $response;
  80. }
  81. /**
  82. * Returns a calendar object.
  83. *
  84. * The contained calendar objects are for example Events or Todo's.
  85. *
  86. * @param string $name
  87. *
  88. * @return \Sabre\CalDAV\ICalendarObject
  89. */
  90. public function getChild($name)
  91. {
  92. $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name);
  93. if (!$obj) {
  94. throw new DAV\Exception\NotFound('Calendar object not found');
  95. }
  96. $obj['acl'] = $this->getChildACL();
  97. return new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj);
  98. }
  99. /**
  100. * Returns the full list of calendar objects.
  101. *
  102. * @return array
  103. */
  104. public function getChildren()
  105. {
  106. $objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']);
  107. $children = [];
  108. foreach ($objs as $obj) {
  109. $obj['acl'] = $this->getChildACL();
  110. $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj);
  111. }
  112. return $children;
  113. }
  114. /**
  115. * This method receives a list of paths in it's first argument.
  116. * It must return an array with Node objects.
  117. *
  118. * If any children are not found, you do not have to return them.
  119. *
  120. * @param string[] $paths
  121. *
  122. * @return array
  123. */
  124. public function getMultipleChildren(array $paths)
  125. {
  126. $objs = $this->caldavBackend->getMultipleCalendarObjects($this->calendarInfo['id'], $paths);
  127. $children = [];
  128. foreach ($objs as $obj) {
  129. $obj['acl'] = $this->getChildACL();
  130. $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj);
  131. }
  132. return $children;
  133. }
  134. /**
  135. * Checks if a child-node exists.
  136. *
  137. * @param string $name
  138. *
  139. * @return bool
  140. */
  141. public function childExists($name)
  142. {
  143. $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name);
  144. if (!$obj) {
  145. return false;
  146. } else {
  147. return true;
  148. }
  149. }
  150. /**
  151. * Creates a new directory.
  152. *
  153. * We actually block this, as subdirectories are not allowed in calendars.
  154. *
  155. * @param string $name
  156. */
  157. public function createDirectory($name)
  158. {
  159. throw new DAV\Exception\MethodNotAllowed('Creating collections in calendar objects is not allowed');
  160. }
  161. /**
  162. * Creates a new file.
  163. *
  164. * The contents of the new file must be a valid ICalendar string.
  165. *
  166. * @param string $name
  167. * @param resource $data
  168. *
  169. * @return string|null
  170. */
  171. public function createFile($name, $data = null)
  172. {
  173. if (is_resource($data)) {
  174. $data = stream_get_contents($data);
  175. }
  176. return $this->caldavBackend->createCalendarObject($this->calendarInfo['id'], $name, $data);
  177. }
  178. /**
  179. * Deletes the calendar.
  180. */
  181. public function delete()
  182. {
  183. $this->caldavBackend->deleteCalendar($this->calendarInfo['id']);
  184. }
  185. /**
  186. * Renames the calendar. Note that most calendars use the
  187. * {DAV:}displayname to display a name to display a name.
  188. *
  189. * @param string $newName
  190. */
  191. public function setName($newName)
  192. {
  193. throw new DAV\Exception\MethodNotAllowed('Renaming calendars is not yet supported');
  194. }
  195. /**
  196. * Returns the last modification date as a unix timestamp.
  197. */
  198. public function getLastModified()
  199. {
  200. return null;
  201. }
  202. /**
  203. * Returns the owner principal.
  204. *
  205. * This must be a url to a principal, or null if there's no owner
  206. *
  207. * @return string|null
  208. */
  209. public function getOwner()
  210. {
  211. return $this->calendarInfo['principaluri'];
  212. }
  213. /**
  214. * Returns a list of ACE's for this node.
  215. *
  216. * Each ACE has the following properties:
  217. * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
  218. * currently the only supported privileges
  219. * * 'principal', a url to the principal who owns the node
  220. * * 'protected' (optional), indicating that this ACE is not allowed to
  221. * be updated.
  222. *
  223. * @return array
  224. */
  225. public function getACL()
  226. {
  227. $acl = [
  228. [
  229. 'privilege' => '{DAV:}read',
  230. 'principal' => $this->getOwner(),
  231. 'protected' => true,
  232. ],
  233. [
  234. 'privilege' => '{DAV:}read',
  235. 'principal' => $this->getOwner().'/calendar-proxy-write',
  236. 'protected' => true,
  237. ],
  238. [
  239. 'privilege' => '{DAV:}read',
  240. 'principal' => $this->getOwner().'/calendar-proxy-read',
  241. 'protected' => true,
  242. ],
  243. [
  244. 'privilege' => '{'.Plugin::NS_CALDAV.'}read-free-busy',
  245. 'principal' => '{DAV:}authenticated',
  246. 'protected' => true,
  247. ],
  248. ];
  249. if (empty($this->calendarInfo['{http://sabredav.org/ns}read-only'])) {
  250. $acl[] = [
  251. 'privilege' => '{DAV:}write',
  252. 'principal' => $this->getOwner(),
  253. 'protected' => true,
  254. ];
  255. $acl[] = [
  256. 'privilege' => '{DAV:}write',
  257. 'principal' => $this->getOwner().'/calendar-proxy-write',
  258. 'protected' => true,
  259. ];
  260. }
  261. return $acl;
  262. }
  263. /**
  264. * This method returns the ACL's for calendar objects in this calendar.
  265. * The result of this method automatically gets passed to the
  266. * calendar-object nodes in the calendar.
  267. *
  268. * @return array
  269. */
  270. public function getChildACL()
  271. {
  272. $acl = [
  273. [
  274. 'privilege' => '{DAV:}read',
  275. 'principal' => $this->getOwner(),
  276. 'protected' => true,
  277. ],
  278. [
  279. 'privilege' => '{DAV:}read',
  280. 'principal' => $this->getOwner().'/calendar-proxy-write',
  281. 'protected' => true,
  282. ],
  283. [
  284. 'privilege' => '{DAV:}read',
  285. 'principal' => $this->getOwner().'/calendar-proxy-read',
  286. 'protected' => true,
  287. ],
  288. ];
  289. if (empty($this->calendarInfo['{http://sabredav.org/ns}read-only'])) {
  290. $acl[] = [
  291. 'privilege' => '{DAV:}write',
  292. 'principal' => $this->getOwner(),
  293. 'protected' => true,
  294. ];
  295. $acl[] = [
  296. 'privilege' => '{DAV:}write',
  297. 'principal' => $this->getOwner().'/calendar-proxy-write',
  298. 'protected' => true,
  299. ];
  300. }
  301. return $acl;
  302. }
  303. /**
  304. * Performs a calendar-query on the contents of this calendar.
  305. *
  306. * The calendar-query is defined in RFC4791 : CalDAV. Using the
  307. * calendar-query it is possible for a client to request a specific set of
  308. * object, based on contents of iCalendar properties, date-ranges and
  309. * iCalendar component types (VTODO, VEVENT).
  310. *
  311. * This method should just return a list of (relative) urls that match this
  312. * query.
  313. *
  314. * The list of filters are specified as an array. The exact array is
  315. * documented by Sabre\CalDAV\CalendarQueryParser.
  316. *
  317. * @return array
  318. */
  319. public function calendarQuery(array $filters)
  320. {
  321. return $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters);
  322. }
  323. /**
  324. * This method returns the current sync-token for this collection.
  325. * This can be any string.
  326. *
  327. * If null is returned from this function, the plugin assumes there's no
  328. * sync information available.
  329. *
  330. * @return string|null
  331. */
  332. public function getSyncToken()
  333. {
  334. if (
  335. $this->caldavBackend instanceof Backend\SyncSupport &&
  336. isset($this->calendarInfo['{DAV:}sync-token'])
  337. ) {
  338. return $this->calendarInfo['{DAV:}sync-token'];
  339. }
  340. if (
  341. $this->caldavBackend instanceof Backend\SyncSupport &&
  342. isset($this->calendarInfo['{http://sabredav.org/ns}sync-token'])
  343. ) {
  344. return $this->calendarInfo['{http://sabredav.org/ns}sync-token'];
  345. }
  346. }
  347. /**
  348. * The getChanges method returns all the changes that have happened, since
  349. * the specified syncToken and the current collection.
  350. *
  351. * This function should return an array, such as the following:
  352. *
  353. * [
  354. * 'syncToken' => 'The current synctoken',
  355. * 'added' => [
  356. * 'new.txt',
  357. * ],
  358. * 'modified' => [
  359. * 'modified.txt',
  360. * ],
  361. * 'deleted' => [
  362. * 'foo.php.bak',
  363. * 'old.txt'
  364. * ],
  365. * 'result_truncated' : true
  366. * ];
  367. *
  368. * The syncToken property should reflect the *current* syncToken of the
  369. * collection, as reported getSyncToken(). This is needed here too, to
  370. * ensure the operation is atomic.
  371. *
  372. * If the syncToken is specified as null, this is an initial sync, and all
  373. * members should be reported.
  374. *
  375. * If result is truncated due to server limitation or limit by client,
  376. * set result_truncated to true, otherwise set to false or do not add the key.
  377. *
  378. * The modified property is an array of nodenames that have changed since
  379. * the last token.
  380. *
  381. * The deleted property is an array with nodenames, that have been deleted
  382. * from collection.
  383. *
  384. * The second argument is basically the 'depth' of the report. If it's 1,
  385. * you only have to report changes that happened only directly in immediate
  386. * descendants. If it's 2, it should also include changes from the nodes
  387. * below the child collections. (grandchildren)
  388. *
  389. * The third (optional) argument allows a client to specify how many
  390. * results should be returned at most. If the limit is not specified, it
  391. * should be treated as infinite.
  392. *
  393. * If the limit (infinite or not) is higher than you're willing to return,
  394. * the result should be truncated to fit the limit.
  395. * Note that even when the result is truncated, syncToken must be consistent
  396. * with the truncated result, not the result before truncation.
  397. * (See RFC6578 Section 3.6 for detail)
  398. *
  399. * If the syncToken is expired (due to data cleanup) or unknown, you must
  400. * return null.
  401. *
  402. * The limit is 'suggestive'. You are free to ignore it.
  403. * TODO: RFC6578 Setion 3.7 says that the server must fail when the server
  404. * cannot truncate according to the limit, so it may not be just suggestive.
  405. *
  406. * @param string $syncToken
  407. * @param int $syncLevel
  408. * @param int $limit
  409. *
  410. * @return array|null
  411. */
  412. public function getChanges($syncToken, $syncLevel, $limit = null)
  413. {
  414. if (!$this->caldavBackend instanceof Backend\SyncSupport) {
  415. return null;
  416. }
  417. return $this->caldavBackend->getChangesForCalendar(
  418. $this->calendarInfo['id'],
  419. $syncToken,
  420. $syncLevel,
  421. $limit
  422. );
  423. }
  424. }