ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
PDO.php
Go to the documentation of this file.
1<?php
2
4
6use Sabre\DAV;
10
21class PDO extends AbstractBackend
22 implements
27
36 const MAX_DATE = '2038-01-01';
37
43 protected $pdo;
44
50 public $calendarTableName = 'calendars';
51
60 public $calendarInstancesTableName = 'calendarinstances';
61
67 public $calendarObjectTableName = 'calendarobjects';
68
74 public $calendarChangesTableName = 'calendarchanges';
75
81 public $schedulingObjectTableName = 'schedulingobjects';
82
88 public $calendarSubscriptionsTableName = 'calendarsubscriptions';
89
98 public $propertyMap = [
99 '{DAV:}displayname' => 'displayname',
100 '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
101 '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
102 '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
103 '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
104 ];
105
112 '{DAV:}displayname' => 'displayname',
113 '{http://apple.com/ns/ical/}refreshrate' => 'refreshrate',
114 '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
115 '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
116 '{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos',
117 '{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms',
118 '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
119 ];
120
126 function __construct(\PDO $pdo) {
127
128 $this->pdo = $pdo;
129
130 }
131
156 function getCalendarsForUser($principalUri) {
157
158 $fields = array_values($this->propertyMap);
159 $fields[] = 'calendarid';
160 $fields[] = 'uri';
161 $fields[] = 'synctoken';
162 $fields[] = 'components';
163 $fields[] = 'principaluri';
164 $fields[] = 'transparent';
165 $fields[] = 'access';
166
167 // Making fields a comma-delimited list
168 $fields = implode(', ', $fields);
169 $stmt = $this->pdo->prepare(<<<SQL
170SELECT {$this->calendarInstancesTableName}.id as id, $fields FROM {$this->calendarInstancesTableName}
171 LEFT JOIN {$this->calendarTableName} ON
172 {$this->calendarInstancesTableName}.calendarid = {$this->calendarTableName}.id
173WHERE principaluri = ? ORDER BY calendarorder ASC
174SQL
175 );
176 $stmt->execute([$principalUri]);
177
178 $calendars = [];
179 while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
180
181 $components = [];
182 if ($row['components']) {
183 $components = explode(',', $row['components']);
184 }
185
186 $calendar = [
187 'id' => [(int)$row['calendarid'], (int)$row['id']],
188 'uri' => $row['uri'],
189 'principaluri' => $row['principaluri'],
190 '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ? $row['synctoken'] : '0'),
191 '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
192 '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet($components),
193 '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
194 'share-resource-uri' => '/ns/share/' . $row['calendarid'],
195 ];
196
197 $calendar['share-access'] = (int)$row['access'];
198 // 1 = owner, 2 = readonly, 3 = readwrite
199 if ($row['access'] > 1) {
200 // We need to find more information about the original owner.
201 //$stmt2 = $this->pdo->prepare('SELECT principaluri FROM ' . $this->calendarInstancesTableName . ' WHERE access = 1 AND id = ?');
202 //$stmt2->execute([$row['id']]);
203
204 // read-only is for backwards compatbility. Might go away in
205 // the future.
206 $calendar['read-only'] = (int)$row['access'] === \Sabre\DAV\Sharing\Plugin::ACCESS_READ;
207 }
208
209 foreach ($this->propertyMap as $xmlName => $dbName) {
210 $calendar[$xmlName] = $row[$dbName];
211 }
212
213 $calendars[] = $calendar;
214
215 }
216
217 return $calendars;
218
219 }
220
232 function createCalendar($principalUri, $calendarUri, array $properties) {
233
234 $fieldNames = [
235 'principaluri',
236 'uri',
237 'transparent',
238 'calendarid',
239 ];
240 $values = [
241 ':principaluri' => $principalUri,
242 ':uri' => $calendarUri,
243 ':transparent' => 0,
244 ];
245
246
247 $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
248 if (!isset($properties[$sccs])) {
249 // Default value
250 $components = 'VEVENT,VTODO';
251 } else {
252 if (!($properties[$sccs] instanceof CalDAV\Xml\Property\SupportedCalendarComponentSet)) {
253 throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet');
254 }
255 $components = implode(',', $properties[$sccs]->getValue());
256 }
257 $transp = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp';
258 if (isset($properties[$transp])) {
259 $values[':transparent'] = $properties[$transp]->getValue() === 'transparent' ? 1 : 0;
260 }
261 $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarTableName . " (synctoken, components) VALUES (1, ?)");
262 $stmt->execute([$components]);
263
264 $calendarId = $this->pdo->lastInsertId(
265 $this->calendarTableName . '_id_seq'
266 );
267
268 $values[':calendarid'] = $calendarId;
269
270 foreach ($this->propertyMap as $xmlName => $dbName) {
271 if (isset($properties[$xmlName])) {
272
273 $values[':' . $dbName] = $properties[$xmlName];
274 $fieldNames[] = $dbName;
275 }
276 }
277
278 $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarInstancesTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");
279
280 $stmt->execute($values);
281
282 return [
283 $calendarId,
284 $this->pdo->lastInsertId($this->calendarInstancesTableName . '_id_seq')
285 ];
286
287 }
288
305 function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) {
306
307 if (!is_array($calendarId)) {
308 throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
309 }
310 list($calendarId, $instanceId) = $calendarId;
311
312 $supportedProperties = array_keys($this->propertyMap);
313 $supportedProperties[] = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp';
314
315 $propPatch->handle($supportedProperties, function($mutations) use ($calendarId, $instanceId) {
316 $newValues = [];
317 foreach ($mutations as $propertyName => $propertyValue) {
318
319 switch ($propertyName) {
320 case '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' :
321 $fieldName = 'transparent';
322 $newValues[$fieldName] = $propertyValue->getValue() === 'transparent';
323 break;
324 default :
325 $fieldName = $this->propertyMap[$propertyName];
326 $newValues[$fieldName] = $propertyValue;
327 break;
328 }
329
330 }
331 $valuesSql = [];
332 foreach ($newValues as $fieldName => $value) {
333 $valuesSql[] = $fieldName . ' = ?';
334 }
335
336 $stmt = $this->pdo->prepare("UPDATE " . $this->calendarInstancesTableName . " SET " . implode(', ', $valuesSql) . " WHERE id = ?");
337 $newValues['id'] = $instanceId;
338 $stmt->execute(array_values($newValues));
339
340 $this->addChange($calendarId, "", 2);
341
342 return true;
343
344 });
345
346 }
347
354 function deleteCalendar($calendarId) {
355
356 if (!is_array($calendarId)) {
357 throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
358 }
359 list($calendarId, $instanceId) = $calendarId;
360
361 $stmt = $this->pdo->prepare('SELECT access FROM ' . $this->calendarInstancesTableName . ' where id = ?');
362 $stmt->execute([$instanceId]);
363 $access = (int)$stmt->fetchColumn();
364
365 if ($access === \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER) {
366
371 $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?');
372 $stmt->execute([$calendarId]);
373
374 $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarChangesTableName . ' WHERE calendarid = ?');
375 $stmt->execute([$calendarId]);
376
377 $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarInstancesTableName . ' WHERE calendarid = ?');
378 $stmt->execute([$calendarId]);
379
380 $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarTableName . ' WHERE id = ?');
381 $stmt->execute([$calendarId]);
382
383 } else {
384
389 $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarInstancesTableName . ' WHERE id = ?');
390 $stmt->execute([$instanceId]);
391
392 }
393
394
395 }
396
428 function getCalendarObjects($calendarId) {
429
430 if (!is_array($calendarId)) {
431 throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
432 }
433 list($calendarId, $instanceId) = $calendarId;
434
435 $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?');
436 $stmt->execute([$calendarId]);
437
438 $result = [];
439 foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
440 $result[] = [
441 'id' => $row['id'],
442 'uri' => $row['uri'],
443 'lastmodified' => (int)$row['lastmodified'],
444 'etag' => '"' . $row['etag'] . '"',
445 'size' => (int)$row['size'],
446 'component' => strtolower($row['componenttype']),
447 ];
448 }
449
450 return $result;
451
452 }
453
470 function getCalendarObject($calendarId, $objectUri) {
471
472 if (!is_array($calendarId)) {
473 throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
474 }
475 list($calendarId, $instanceId) = $calendarId;
476
477 $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri = ?');
478 $stmt->execute([$calendarId, $objectUri]);
479 $row = $stmt->fetch(\PDO::FETCH_ASSOC);
480
481 if (!$row) return null;
482
483 return [
484 'id' => $row['id'],
485 'uri' => $row['uri'],
486 'lastmodified' => (int)$row['lastmodified'],
487 'etag' => '"' . $row['etag'] . '"',
488 'size' => (int)$row['size'],
489 'calendardata' => $row['calendardata'],
490 'component' => strtolower($row['componenttype']),
491 ];
492
493 }
494
507 function getMultipleCalendarObjects($calendarId, array $uris) {
508
509 if (!is_array($calendarId)) {
510 throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
511 }
512 list($calendarId, $instanceId) = $calendarId;
513
514 $result = [];
515 foreach (array_chunk($uris, 900) as $chunk) {
516 $query = 'SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri IN (';
517 // Inserting a whole bunch of question marks
518 $query .= implode(',', array_fill(0, count($chunk), '?'));
519 $query .= ')';
520
521 $stmt = $this->pdo->prepare($query);
522 $stmt->execute(array_merge([$calendarId], $chunk));
523
524 while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
525
526 $result[] = [
527 'id' => $row['id'],
528 'uri' => $row['uri'],
529 'lastmodified' => (int)$row['lastmodified'],
530 'etag' => '"' . $row['etag'] . '"',
531 'size' => (int)$row['size'],
532 'calendardata' => $row['calendardata'],
533 'component' => strtolower($row['componenttype']),
534 ];
535
536 }
537 }
538 return $result;
539
540 }
541
542
561 function createCalendarObject($calendarId, $objectUri, $calendarData) {
562
563 if (!is_array($calendarId)) {
564 throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
565 }
566 list($calendarId, $instanceId) = $calendarId;
567
568 $extraData = $this->getDenormalizedData($calendarData);
569
570 $stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarObjectTableName . ' (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES (?,?,?,?,?,?,?,?,?,?)');
571 $stmt->execute([
572 $calendarId,
573 $objectUri,
574 $calendarData,
575 time(),
576 $extraData['etag'],
577 $extraData['size'],
578 $extraData['componentType'],
579 $extraData['firstOccurence'],
580 $extraData['lastOccurence'],
581 $extraData['uid'],
582 ]);
583 $this->addChange($calendarId, $objectUri, 1);
584
585 return '"' . $extraData['etag'] . '"';
586
587 }
588
607 function updateCalendarObject($calendarId, $objectUri, $calendarData) {
608
609 if (!is_array($calendarId)) {
610 throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
611 }
612 list($calendarId, $instanceId) = $calendarId;
613
614 $extraData = $this->getDenormalizedData($calendarData);
615
616 $stmt = $this->pdo->prepare('UPDATE ' . $this->calendarObjectTableName . ' SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ?, uid = ? WHERE calendarid = ? AND uri = ?');
617 $stmt->execute([$calendarData, time(), $extraData['etag'], $extraData['size'], $extraData['componentType'], $extraData['firstOccurence'], $extraData['lastOccurence'], $extraData['uid'], $calendarId, $objectUri]);
618
619 $this->addChange($calendarId, $objectUri, 2);
620
621 return '"' . $extraData['etag'] . '"';
622
623 }
624
640 protected function getDenormalizedData($calendarData) {
641
642 $vObject = VObject\Reader::read($calendarData);
643 $componentType = null;
644 $component = null;
645 $firstOccurence = null;
646 $lastOccurence = null;
647 $uid = null;
648 foreach ($vObject->getComponents() as $component) {
649 if ($component->name !== 'VTIMEZONE') {
650 $componentType = $component->name;
651 $uid = (string)$component->UID;
652 break;
653 }
654 }
655 if (!$componentType) {
656 throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
657 }
658 if ($componentType === 'VEVENT') {
659 $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp();
660 // Finding the last occurence is a bit harder
661 if (!isset($component->RRULE)) {
662 if (isset($component->DTEND)) {
663 $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
664 } elseif (isset($component->DURATION)) {
665 $endDate = clone $component->DTSTART->getDateTime();
666 $endDate = $endDate->add(VObject\DateTimeParser::parse($component->DURATION->getValue()));
667 $lastOccurence = $endDate->getTimeStamp();
668 } elseif (!$component->DTSTART->hasTime()) {
669 $endDate = clone $component->DTSTART->getDateTime();
670 $endDate = $endDate->modify('+1 day');
671 $lastOccurence = $endDate->getTimeStamp();
672 } else {
673 $lastOccurence = $firstOccurence;
674 }
675 } else {
676 $it = new VObject\Recur\EventIterator($vObject, (string)$component->UID);
677 $maxDate = new \DateTime(self::MAX_DATE);
678 if ($it->isInfinite()) {
679 $lastOccurence = $maxDate->getTimeStamp();
680 } else {
681 $end = $it->getDtEnd();
682 while ($it->valid() && $end < $maxDate) {
683 $end = $it->getDtEnd();
684 $it->next();
685
686 }
687 $lastOccurence = $end->getTimeStamp();
688 }
689
690 }
691
692 // Ensure Occurence values are positive
693 if ($firstOccurence < 0) $firstOccurence = 0;
694 if ($lastOccurence < 0) $lastOccurence = 0;
695 }
696
697 // Destroy circular references to PHP will GC the object.
698 $vObject->destroy();
699
700 return [
701 'etag' => md5($calendarData),
702 'size' => strlen($calendarData),
703 'componentType' => $componentType,
704 'firstOccurence' => $firstOccurence,
705 'lastOccurence' => $lastOccurence,
706 'uid' => $uid,
707 ];
708
709 }
710
720 function deleteCalendarObject($calendarId, $objectUri) {
721
722 if (!is_array($calendarId)) {
723 throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
724 }
725 list($calendarId, $instanceId) = $calendarId;
726
727 $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri = ?');
728 $stmt->execute([$calendarId, $objectUri]);
729
730 $this->addChange($calendarId, $objectUri, 3);
731
732 }
733
786 function calendarQuery($calendarId, array $filters) {
787
788 if (!is_array($calendarId)) {
789 throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
790 }
791 list($calendarId, $instanceId) = $calendarId;
792
793 $componentType = null;
794 $requirePostFilter = true;
795 $timeRange = null;
796
797 // if no filters were specified, we don't need to filter after a query
798 if (!$filters['prop-filters'] && !$filters['comp-filters']) {
799 $requirePostFilter = false;
800 }
801
802 // Figuring out if there's a component filter
803 if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
804 $componentType = $filters['comp-filters'][0]['name'];
805
806 // Checking if we need post-filters
807 if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
808 $requirePostFilter = false;
809 }
810 // There was a time-range filter
811 if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) {
812 $timeRange = $filters['comp-filters'][0]['time-range'];
813
814 // If start time OR the end time is not specified, we can do a
815 // 100% accurate mysql query.
816 if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
817 $requirePostFilter = false;
818 }
819 }
820
821 }
822
823 if ($requirePostFilter) {
824 $query = "SELECT uri, calendardata FROM " . $this->calendarObjectTableName . " WHERE calendarid = :calendarid";
825 } else {
826 $query = "SELECT uri FROM " . $this->calendarObjectTableName . " WHERE calendarid = :calendarid";
827 }
828
829 $values = [
830 'calendarid' => $calendarId,
831 ];
832
833 if ($componentType) {
834 $query .= " AND componenttype = :componenttype";
835 $values['componenttype'] = $componentType;
836 }
837
838 if ($timeRange && $timeRange['start']) {
839 $query .= " AND lastoccurence > :startdate";
840 $values['startdate'] = $timeRange['start']->getTimeStamp();
841 }
842 if ($timeRange && $timeRange['end']) {
843 $query .= " AND firstoccurence < :enddate";
844 $values['enddate'] = $timeRange['end']->getTimeStamp();
845 }
846
847 $stmt = $this->pdo->prepare($query);
848 $stmt->execute($values);
849
850 $result = [];
851 while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
852 if ($requirePostFilter) {
853 if (!$this->validateFilterForObject($row, $filters)) {
854 continue;
855 }
856 }
857 $result[] = $row['uri'];
858
859 }
860
861 return $result;
862
863 }
864
884 function getCalendarObjectByUID($principalUri, $uid) {
885
886 $query = <<<SQL
887SELECT
888 calendar_instances.uri AS calendaruri, calendarobjects.uri as objecturi
889FROM
890 $this->calendarObjectTableName AS calendarobjects
891LEFT JOIN
892 $this->calendarInstancesTableName AS calendar_instances
893 ON calendarobjects.calendarid = calendar_instances.calendarid
894WHERE
895 calendar_instances.principaluri = ?
896 AND
897 calendarobjects.uid = ?
898SQL;
899
900 $stmt = $this->pdo->prepare($query);
901 $stmt->execute([$principalUri, $uid]);
902
903 if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
904 return $row['calendaruri'] . '/' . $row['objecturi'];
905 }
906
907 }
908
965 function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) {
966
967 if (!is_array($calendarId)) {
968 throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
969 }
970 list($calendarId, $instanceId) = $calendarId;
971
972 // Current synctoken
973 $stmt = $this->pdo->prepare('SELECT synctoken FROM ' . $this->calendarTableName . ' WHERE id = ?');
974 $stmt->execute([$calendarId]);
975 $currentToken = $stmt->fetchColumn(0);
976
977 if (is_null($currentToken)) return null;
978
979 $result = [
980 'syncToken' => $currentToken,
981 'added' => [],
982 'modified' => [],
983 'deleted' => [],
984 ];
985
986 if ($syncToken) {
987
988 $query = "SELECT uri, operation FROM " . $this->calendarChangesTableName . " WHERE synctoken >= ? AND synctoken < ? AND calendarid = ? ORDER BY synctoken";
989 if ($limit > 0) $query .= " LIMIT " . (int)$limit;
990
991 // Fetching all changes
992 $stmt = $this->pdo->prepare($query);
993 $stmt->execute([$syncToken, $currentToken, $calendarId]);
994
995 $changes = [];
996
997 // This loop ensures that any duplicates are overwritten, only the
998 // last change on a node is relevant.
999 while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1000
1001 $changes[$row['uri']] = $row['operation'];
1002
1003 }
1004
1005 foreach ($changes as $uri => $operation) {
1006
1007 switch ($operation) {
1008 case 1 :
1009 $result['added'][] = $uri;
1010 break;
1011 case 2 :
1012 $result['modified'][] = $uri;
1013 break;
1014 case 3 :
1015 $result['deleted'][] = $uri;
1016 break;
1017 }
1018
1019 }
1020 } else {
1021 // No synctoken supplied, this is the initial sync.
1022 $query = "SELECT uri FROM " . $this->calendarObjectTableName . " WHERE calendarid = ?";
1023 $stmt = $this->pdo->prepare($query);
1024 $stmt->execute([$calendarId]);
1025
1026 $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
1027 }
1028 return $result;
1029
1030 }
1031
1040 protected function addChange($calendarId, $objectUri, $operation) {
1041
1042 $stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarChangesTableName . ' (uri, synctoken, calendarid, operation) SELECT ?, synctoken, ?, ? FROM ' . $this->calendarTableName . ' WHERE id = ?');
1043 $stmt->execute([
1044 $objectUri,
1045 $calendarId,
1046 $operation,
1047 $calendarId
1048 ]);
1049 $stmt = $this->pdo->prepare('UPDATE ' . $this->calendarTableName . ' SET synctoken = synctoken + 1 WHERE id = ?');
1050 $stmt->execute([
1051 $calendarId
1052 ]);
1053
1054 }
1055
1087 function getSubscriptionsForUser($principalUri) {
1088
1089 $fields = array_values($this->subscriptionPropertyMap);
1090 $fields[] = 'id';
1091 $fields[] = 'uri';
1092 $fields[] = 'source';
1093 $fields[] = 'principaluri';
1094 $fields[] = 'lastmodified';
1095
1096 // Making fields a comma-delimited list
1097 $fields = implode(', ', $fields);
1098 $stmt = $this->pdo->prepare("SELECT " . $fields . " FROM " . $this->calendarSubscriptionsTableName . " WHERE principaluri = ? ORDER BY calendarorder ASC");
1099 $stmt->execute([$principalUri]);
1100
1101 $subscriptions = [];
1102 while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1103
1104 $subscription = [
1105 'id' => $row['id'],
1106 'uri' => $row['uri'],
1107 'principaluri' => $row['principaluri'],
1108 'source' => $row['source'],
1109 'lastmodified' => $row['lastmodified'],
1110
1111 '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
1112 ];
1113
1114 foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
1115 if (!is_null($row[$dbName])) {
1116 $subscription[$xmlName] = $row[$dbName];
1117 }
1118 }
1119
1120 $subscriptions[] = $subscription;
1121
1122 }
1123
1124 return $subscriptions;
1125
1126 }
1127
1139 function createSubscription($principalUri, $uri, array $properties) {
1140
1141 $fieldNames = [
1142 'principaluri',
1143 'uri',
1144 'source',
1145 'lastmodified',
1146 ];
1147
1148 if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
1149 throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
1150 }
1151
1152 $values = [
1153 ':principaluri' => $principalUri,
1154 ':uri' => $uri,
1155 ':source' => $properties['{http://calendarserver.org/ns/}source']->getHref(),
1156 ':lastmodified' => time(),
1157 ];
1158
1159 foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
1160 if (isset($properties[$xmlName])) {
1161
1162 $values[':' . $dbName] = $properties[$xmlName];
1163 $fieldNames[] = $dbName;
1164 }
1165 }
1166
1167 $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarSubscriptionsTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");
1168 $stmt->execute($values);
1169
1170 return $this->pdo->lastInsertId(
1171 $this->calendarSubscriptionsTableName . '_id_seq'
1172 );
1173
1174 }
1175
1192 function updateSubscription($subscriptionId, DAV\PropPatch $propPatch) {
1193
1194 $supportedProperties = array_keys($this->subscriptionPropertyMap);
1195 $supportedProperties[] = '{http://calendarserver.org/ns/}source';
1196
1197 $propPatch->handle($supportedProperties, function($mutations) use ($subscriptionId) {
1198
1199 $newValues = [];
1200
1201 foreach ($mutations as $propertyName => $propertyValue) {
1202
1203 if ($propertyName === '{http://calendarserver.org/ns/}source') {
1204 $newValues['source'] = $propertyValue->getHref();
1205 } else {
1206 $fieldName = $this->subscriptionPropertyMap[$propertyName];
1207 $newValues[$fieldName] = $propertyValue;
1208 }
1209
1210 }
1211
1212 // Now we're generating the sql query.
1213 $valuesSql = [];
1214 foreach ($newValues as $fieldName => $value) {
1215 $valuesSql[] = $fieldName . ' = ?';
1216 }
1217
1218 $stmt = $this->pdo->prepare("UPDATE " . $this->calendarSubscriptionsTableName . " SET " . implode(', ', $valuesSql) . ", lastmodified = ? WHERE id = ?");
1219 $newValues['lastmodified'] = time();
1220 $newValues['id'] = $subscriptionId;
1221 $stmt->execute(array_values($newValues));
1222
1223 return true;
1224
1225 });
1226
1227 }
1228
1235 function deleteSubscription($subscriptionId) {
1236
1237 $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarSubscriptionsTableName . ' WHERE id = ?');
1238 $stmt->execute([$subscriptionId]);
1239
1240 }
1241
1258 function getSchedulingObject($principalUri, $objectUri) {
1259
1260 $stmt = $this->pdo->prepare('SELECT uri, calendardata, lastmodified, etag, size FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ? AND uri = ?');
1261 $stmt->execute([$principalUri, $objectUri]);
1262 $row = $stmt->fetch(\PDO::FETCH_ASSOC);
1263
1264 if (!$row) return null;
1265
1266 return [
1267 'uri' => $row['uri'],
1268 'calendardata' => $row['calendardata'],
1269 'lastmodified' => $row['lastmodified'],
1270 'etag' => '"' . $row['etag'] . '"',
1271 'size' => (int)$row['size'],
1272 ];
1273
1274 }
1275
1287 function getSchedulingObjects($principalUri) {
1288
1289 $stmt = $this->pdo->prepare('SELECT id, calendardata, uri, lastmodified, etag, size FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ?');
1290 $stmt->execute([$principalUri]);
1291
1292 $result = [];
1293 foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
1294 $result[] = [
1295 'calendardata' => $row['calendardata'],
1296 'uri' => $row['uri'],
1297 'lastmodified' => $row['lastmodified'],
1298 'etag' => '"' . $row['etag'] . '"',
1299 'size' => (int)$row['size'],
1300 ];
1301 }
1302
1303 return $result;
1304
1305 }
1306
1314 function deleteSchedulingObject($principalUri, $objectUri) {
1315
1316 $stmt = $this->pdo->prepare('DELETE FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ? AND uri = ?');
1317 $stmt->execute([$principalUri, $objectUri]);
1318
1319 }
1320
1329 function createSchedulingObject($principalUri, $objectUri, $objectData) {
1330
1331 $stmt = $this->pdo->prepare('INSERT INTO ' . $this->schedulingObjectTableName . ' (principaluri, calendardata, uri, lastmodified, etag, size) VALUES (?, ?, ?, ?, ?, ?)');
1332 $stmt->execute([$principalUri, $objectData, $objectUri, time(), md5($objectData), strlen($objectData)]);
1333
1334 }
1335
1343 function updateInvites($calendarId, array $sharees) {
1344
1345 if (!is_array($calendarId)) {
1346 throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
1347 }
1348 $currentInvites = $this->getInvites($calendarId);
1349 list($calendarId, $instanceId) = $calendarId;
1350
1351 $removeStmt = $this->pdo->prepare("DELETE FROM " . $this->calendarInstancesTableName . " WHERE calendarid = ? AND share_href = ? AND access IN (2,3)");
1352 $updateStmt = $this->pdo->prepare("UPDATE " . $this->calendarInstancesTableName . " SET access = ?, share_displayname = ?, share_invitestatus = ? WHERE calendarid = ? AND share_href = ?");
1353
1354 $insertStmt = $this->pdo->prepare('
1355INSERT INTO ' . $this->calendarInstancesTableName . '
1356 (
1357 calendarid,
1358 principaluri,
1359 access,
1360 displayname,
1361 uri,
1362 description,
1363 calendarorder,
1364 calendarcolor,
1365 timezone,
1366 transparent,
1367 share_href,
1368 share_displayname,
1369 share_invitestatus
1370 )
1371 SELECT
1372 ?,
1373 ?,
1374 ?,
1375 displayname,
1376 ?,
1377 description,
1378 calendarorder,
1379 calendarcolor,
1380 timezone,
1381 1,
1382 ?,
1383 ?,
1384 ?
1385 FROM ' . $this->calendarInstancesTableName . ' WHERE id = ?');
1386
1387 foreach ($sharees as $sharee) {
1388
1389 if ($sharee->access === \Sabre\DAV\Sharing\Plugin::ACCESS_NOACCESS) {
1390 // if access was set no NOACCESS, it means access for an
1391 // existing sharee was removed.
1392 $removeStmt->execute([$calendarId, $sharee->href]);
1393 continue;
1394 }
1395
1396 if (is_null($sharee->principal)) {
1397 // If the server could not determine the principal automatically,
1398 // we will mark the invite status as invalid.
1399 $sharee->inviteStatus = \Sabre\DAV\Sharing\Plugin::INVITE_INVALID;
1400 } else {
1401 // Because sabre/dav does not yet have an invitation system,
1402 // every invite is automatically accepted for now.
1403 $sharee->inviteStatus = \Sabre\DAV\Sharing\Plugin::INVITE_ACCEPTED;
1404 }
1405
1406 foreach ($currentInvites as $oldSharee) {
1407
1408 if ($oldSharee->href === $sharee->href) {
1409 // This is an update
1410 $sharee->properties = array_merge(
1411 $oldSharee->properties,
1412 $sharee->properties
1413 );
1414 $updateStmt->execute([
1415 $sharee->access,
1416 isset($sharee->properties['{DAV:}displayname']) ? $sharee->properties['{DAV:}displayname'] : null,
1417 $sharee->inviteStatus ?: $oldSharee->inviteStatus,
1418 $calendarId,
1419 $sharee->href
1420 ]);
1421 continue 2;
1422 }
1423
1424 }
1425 // If we got here, it means it was a new sharee
1426 $insertStmt->execute([
1427 $calendarId,
1428 $sharee->principal,
1429 $sharee->access,
1430 \Sabre\DAV\UUIDUtil::getUUID(),
1431 $sharee->href,
1432 isset($sharee->properties['{DAV:}displayname']) ? $sharee->properties['{DAV:}displayname'] : null,
1433 $sharee->inviteStatus ?: \Sabre\DAV\Sharing\Plugin::INVITE_NORESPONSE,
1434 $instanceId
1435 ]);
1436
1437 }
1438
1439 }
1440
1456 function getInvites($calendarId) {
1457
1458 if (!is_array($calendarId)) {
1459 throw new \InvalidArgumentException('The value passed to getInvites() is expected to be an array with a calendarId and an instanceId');
1460 }
1461 list($calendarId, $instanceId) = $calendarId;
1462
1463 $query = <<<SQL
1464SELECT
1465 principaluri,
1466 access,
1467 share_href,
1468 share_displayname,
1469 share_invitestatus
1470FROM {$this->calendarInstancesTableName}
1471WHERE
1472 calendarid = ?
1473SQL;
1474
1475 $stmt = $this->pdo->prepare($query);
1476 $stmt->execute([$calendarId]);
1477
1478 $result = [];
1479 while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1480
1481 $result[] = new Sharee([
1482 'href' => isset($row['share_href']) ? $row['share_href'] : \Sabre\HTTP\encodePath($row['principaluri']),
1483 'access' => (int)$row['access'],
1485 'inviteStatus' => (int)$row['share_invitestatus'],
1486 'properties' =>
1487 !empty($row['share_displayname'])
1488 ? ['{DAV:}displayname' => $row['share_displayname']]
1489 : [],
1490 'principal' => $row['principaluri'],
1491 ]);
1492
1493 }
1494 return $result;
1495
1496 }
1497
1505 function setPublishStatus($calendarId, $value) {
1506
1507 throw new DAV\Exception\NotImplemented('Not implemented');
1508
1509 }
1510
1511}
$result
An exception for terminatinating execution or to throw for unit testing.
Abstract Calendaring backend.
validateFilterForObject(array $object, array $filters)
This method validates if a filter (as passed to calendarQuery) matches the given object.
PDO CalDAV backend.
Definition: PDO.php:26
getCalendarObjects($calendarId)
Returns all calendar objects within a calendar.
Definition: PDO.php:428
calendarQuery($calendarId, array $filters)
Performs a calendar-query on the contents of this calendar.
Definition: PDO.php:786
setPublishStatus($calendarId, $value)
Publishes a calendar.
Definition: PDO.php:1505
updateInvites($calendarId, array $sharees)
Updates the list of shares.
Definition: PDO.php:1343
addChange($calendarId, $objectUri, $operation)
Adds a change record to the calendarchanges table.
Definition: PDO.php:1040
deleteSubscription($subscriptionId)
Deletes a subscription.
Definition: PDO.php:1235
getSchedulingObject($principalUri, $objectUri)
Returns a single scheduling object.
Definition: PDO.php:1258
createSchedulingObject($principalUri, $objectUri, $objectData)
Creates a new scheduling object.
Definition: PDO.php:1329
updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch)
Updates properties for a calendar.
Definition: PDO.php:305
getSchedulingObjects($principalUri)
Returns all scheduling objects for the inbox collection.
Definition: PDO.php:1287
getSubscriptionsForUser($principalUri)
Returns a list of subscriptions for a principal.
Definition: PDO.php:1087
createCalendar($principalUri, $calendarUri, array $properties)
Creates a new calendar for a principal.
Definition: PDO.php:232
getCalendarsForUser($principalUri)
Returns a list of calendars for a principal.
Definition: PDO.php:156
getCalendarObject($calendarId, $objectUri)
Returns information from a single calendar object, based on it's object uri.
Definition: PDO.php:470
__construct(\PDO $pdo)
Creates the backend.
Definition: PDO.php:126
getCalendarObjectByUID($principalUri, $uid)
Searches through all of a users calendars and calendar objects to find an object with a specific UID.
Definition: PDO.php:884
deleteCalendar($calendarId)
Delete a calendar and all it's objects.
Definition: PDO.php:354
const MAX_DATE
We need to specify a max date, because we need to stop somewhere
Definition: PDO.php:36
updateSubscription($subscriptionId, DAV\PropPatch $propPatch)
Updates a subscription.
Definition: PDO.php:1192
updateCalendarObject($calendarId, $objectUri, $calendarData)
Updates an existing calendarobject, based on it's uri.
Definition: PDO.php:607
getMultipleCalendarObjects($calendarId, array $uris)
Returns a list of calendar objects.
Definition: PDO.php:507
createSubscription($principalUri, $uri, array $properties)
Creates a new subscription for a principal.
Definition: PDO.php:1139
createCalendarObject($calendarId, $objectUri, $calendarData)
Creates a new calendar object.
Definition: PDO.php:561
getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit=null)
The getChanges method returns all the changes that have happened, since the specified syncToken in th...
Definition: PDO.php:965
getDenormalizedData($calendarData)
Parses some information from calendar objects, used for optimized calendar-queries.
Definition: PDO.php:640
deleteSchedulingObject($principalUri, $objectUri)
Deletes a scheduling object.
Definition: PDO.php:1314
getInvites($calendarId)
Returns the list of people whom a calendar is shared with.
Definition: PDO.php:1456
deleteCalendarObject($calendarId, $objectUri)
Deletes an existing calendar object.
Definition: PDO.php:720
const NS_CALDAV
This is the official CalDAV namespace.
Definition: Plugin.php:33
const NS_CALENDARSERVER
This is the namespace for the proprietary calendarserver extensions.
Definition: Plugin.php:38
Main Exception class.
Definition: Exception.php:18
This class represents a set of properties that are going to be updated.
Definition: PropPatch.php:20
static getUUID()
Returns a pseudo-random v4 UUID.
Definition: UUIDUtil.php:26
This class represents the {DAV:}sharee element.
Definition: Sharee.php:21
static parse($date, $referenceTz=null)
Parses either a Date or DateTime, or Duration value.
static read($data, $options=0, $charset='UTF-8')
Parses a vCard or iCalendar object, and returns the top component.
Definition: Reader.php:42
This class is used to determine new for a recurring event, when the next events occur.
Implementing this interface adds CalDAV Scheduling support to your caldav server, as defined in rfc66...
Adds support for sharing features to a CalDAV server.
Every CalDAV backend must at least implement this interface.
WebDAV-sync support for CalDAV backends.
Definition: SyncSupport.php:21
$row
$stmt
encodePath($path)
Encodes the path of a url.
Definition: functions.php:386
$query
$values