ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
PDO.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Sabre\CalDAV\Backend;
4 
5 use Sabre\CalDAV;
6 use Sabre\DAV;
9 use Sabre\VObject;
10 
21 class 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
170 SELECT {$this->calendarInstancesTableName}.id as id, $fields FROM {$this->calendarInstancesTableName}
171  LEFT JOIN {$this->calendarTableName} ON
172  {$this->calendarInstancesTableName}.calendarid = {$this->calendarTableName}.id
173 WHERE principaluri = ? ORDER BY calendarorder ASC
174 SQL
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
887 SELECT
888  calendar_instances.uri AS calendaruri, calendarobjects.uri as objecturi
889 FROM
890  $this->calendarObjectTableName AS calendarobjects
891 LEFT JOIN
892  $this->calendarInstancesTableName AS calendar_instances
893  ON calendarobjects.calendarid = calendar_instances.calendarid
894 WHERE
895  calendar_instances.principaluri = ?
896  AND
897  calendarobjects.uid = ?
898 SQL;
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('
1355 INSERT 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
1464 SELECT
1465  principaluri,
1466  access,
1467  share_href,
1468  share_displayname,
1469  share_invitestatus
1470 FROM {$this->calendarInstancesTableName}
1471 WHERE
1472  calendarid = ?
1473 SQL;
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 }
getSchedulingObjects($principalUri)
Returns all scheduling objects for the inbox collection.
Definition: PDO.php:1287
This class represents the {DAV:}sharee element.
Definition: Sharee.php:21
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
getSubscriptionsForUser($principalUri)
Returns a list of subscriptions for a principal.
Definition: PDO.php:1087
static parse($date, $referenceTz=null)
Parses either a Date or DateTime, or Duration value.
Adds support for sharing features to a CalDAV server.
updateCalendarObject($calendarId, $objectUri, $calendarData)
Updates an existing calendarobject, based on it&#39;s uri.
Definition: PDO.php:607
updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch)
Updates properties for a calendar.
Definition: PDO.php:305
This class represents a set of properties that are going to be updated.
Definition: PropPatch.php:20
$result
encodePath($path)
Encodes the path of a url.
Definition: functions.php:386
deleteSubscription($subscriptionId)
Deletes a subscription.
Definition: PDO.php:1235
$stmt
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
updateInvites($calendarId, array $sharees)
Updates the list of shares.
Definition: PDO.php:1343
createSchedulingObject($principalUri, $objectUri, $objectData)
Creates a new scheduling object.
Definition: PDO.php:1329
getMultipleCalendarObjects($calendarId, array $uris)
Returns a list of calendar objects.
Definition: PDO.php:507
const MAX_DATE
We need to specify a max date, because we need to stop somewhere
Definition: PDO.php:36
getSchedulingObject($principalUri, $objectUri)
Returns a single scheduling object.
Definition: PDO.php:1258
This class is used to determine new for a recurring event, when the next events occur.
getCalendarsForUser($principalUri)
Returns a list of calendars for a principal.
Definition: PDO.php:156
Main Exception class.
Definition: Exception.php:18
setPublishStatus($calendarId, $value)
Publishes a calendar.
Definition: PDO.php:1505
const NS_CALENDARSERVER
This is the namespace for the proprietary calendarserver extensions.
Definition: Plugin.php:38
Every CalDAV backend must at least implement this interface.
$values
addChange($calendarId, $objectUri, $operation)
Adds a change record to the calendarchanges table.
Definition: PDO.php:1040
updateSubscription($subscriptionId, DAV\PropPatch $propPatch)
Updates a subscription.
Definition: PDO.php:1192
getDenormalizedData($calendarData)
Parses some information from calendar objects, used for optimized calendar-queries.
Definition: PDO.php:640
deleteCalendar($calendarId)
Delete a calendar and all it&#39;s objects.
Definition: PDO.php:354
calendarQuery($calendarId, array $filters)
Performs a calendar-query on the contents of this calendar.
Definition: PDO.php:786
deleteSchedulingObject($principalUri, $objectUri)
Deletes a scheduling object.
Definition: PDO.php:1314
getCalendarObjects($calendarId)
Returns all calendar objects within a calendar.
Definition: PDO.php:428
WebDAV-sync support for CalDAV backends.
Definition: SyncSupport.php:21
$query
createCalendar($principalUri, $calendarUri, array $properties)
Creates a new calendar for a principal.
Definition: PDO.php:232
__construct(\PDO $pdo)
Creates the backend.
Definition: PDO.php:126
$row
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
static read($data, $options=0, $charset='UTF-8')
Parses a vCard or iCalendar object, and returns the top component.
Definition: Reader.php:42
createCalendarObject($calendarId, $objectUri, $calendarData)
Creates a new calendar object.
Definition: PDO.php:561
static getUUID()
Returns a pseudo-random v4 UUID.
Definition: UUIDUtil.php:26
Implementing this interface adds CalDAV Scheduling support to your caldav server, as defined in rfc66...
validateFilterForObject(array $object, array $filters)
This method validates if a filter (as passed to calendarQuery) matches the given object.
getCalendarObject($calendarId, $objectUri)
Returns information from a single calendar object, based on it&#39;s object uri.
Definition: PDO.php:470
createSubscription($principalUri, $uri, array $properties)
Creates a new subscription for a principal.
Definition: PDO.php:1139
const NS_CALDAV
This is the official CalDAV namespace.
Definition: Plugin.php:33
Abstract Calendaring backend.