Page MenuHomePhabricator

No OneTemporary

diff --git a/calendarBackendDokuwiki.php b/calendarBackendDokuwiki.php
--- a/calendarBackendDokuwiki.php
+++ b/calendarBackendDokuwiki.php
@@ -1,1226 +1,708 @@
<?php
use \Sabre\VObject;
use \Sabre\CalDAV;
use \Sabre\DAV;
use \Sabre\DAV\Exception\Forbidden;
/**
* PDO CalDAV backend for DokuWiki - based on Sabre's CalDAV backend
*
* This backend is used to store calendar-data in a PDO database, such as
* sqlite or MySQL
*
* @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
-class DokuWikiSabreCalendarBackend extends \Sabre\CalDAV\Backend\AbstractBackend {
+class DokuWikiSabreCalendarBackend extends \Sabre\CalDAV\Backend\AbstractBackend
+{
- /**
- * We need to specify a max date, because we need to stop *somewhere*
- *
- * On 32 bit system the maximum for a signed integer is 2147483647, so
- * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
- * in 2038-01-19 to avoid problems when the date is converted
- * to a unix timestamp.
- */
- const MAX_DATE = '2038-01-01';
-
- /**
- * pdo
- *
- * @var \PDO
- */
- protected $pdo;
/**
* DokuWiki PlugIn Helper
*/
protected $hlp = null;
/**
- * The table name that will be used for calendars
- *
- * @var string
- */
- public $calendarTableName = 'calendars';
-
-
- /**
- * The table name that will be used for DokuWiki Calendar-Page mapping
- *
- * @var string
- */
- public $calendarDokuwikiMappingTableName = 'pagetocalendarmapping';
- /**
- * The table name that will be used for calendar objects
- *
- * @var string
- */
- public $calendarObjectTableName = 'calendarobjects';
-
- /**
- * The table name that will be used for tracking changes in calendars.
- *
- * @var string
- */
- public $calendarChangesTableName = 'calendarchanges';
-
- /**
- * The table name that will be used inbox items.
- *
- * @var string
- */
- public $schedulingObjectTableName = 'schedulingobjects';
-
- /**
- * The table name that will be used for calendar subscriptions.
- *
- * @var string
- */
- public $calendarSubscriptionsTableName = 'calendarsubscriptions';
/**
* List of CalDAV properties, and how they map to database fieldnames
* Add your own properties by simply adding on to this array.
*
* Note that only string-based properties are supported here.
*
* @var array
*/
- public $propertyMap = [
+ public $propertyMap = array(
'{DAV:}displayname' => 'displayname',
'{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
'{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
- '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
- '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
- ];
-
- /**
- * List of subscription properties, and how they map to database fieldnames.
- *
- * @var array
- */
- public $subscriptionPropertyMap = [
- '{DAV:}displayname' => 'displayname',
- '{http://apple.com/ns/ical/}refreshrate' => 'refreshrate',
- '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
- '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
- '{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos',
- '{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms',
- '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
- ];
+ //'{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
+ //'{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
+ );
/**
* Creates the backend
*
* @param \PDO $pdo
*/
- function __construct(\PDO $pdo) {
+ function __construct(&$hlp)
+ {
- $this->pdo = $pdo;
- $this->hlp =& plugin_load('helper', 'davcal');
+ $this->hlp = $hlp;
}
/**
* Returns a list of calendars for a principal.
*
* Every project is an array with the following keys:
* * id, a unique id that will be used by other functions to modify the
* calendar. This can be the same as the uri or a database key.
* * uri. This is just the 'base uri' or 'filename' of the calendar.
* * principaluri. The owner of the calendar. Almost always the same as
* principalUri passed to this method.
*
* Furthermore it can contain webdav properties in clark notation. A very
* common one is '{DAV:}displayname'.
*
* Many clients also require:
* {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
* For this property, you can just return an instance of
* Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet.
*
* If you return {http://sabredav.org/ns}read-only and set the value to 1,
* ACL will automatically be put in read-only mode.
*
* @param string $principalUri
* @return array
*/
- function getCalendarsForUser($principalUri) {
+ function getCalendarsForUser($principalUri)
+ {
$fields = array_values($this->propertyMap);
$fields[] = 'id';
$fields[] = 'uri';
$fields[] = 'synctoken';
$fields[] = 'components';
$fields[] = 'principaluri';
$fields[] = 'transparent';
$idInfo = $this->hlp->getCalendarIdsForUser($principalUri);
- $idFilter = array_keys($idInfo);
- // Making fields a comma-delimited list
- $fields = implode(', ', $fields);
- $stmt = $this->pdo->prepare("SELECT " . $fields . " FROM " . $this->calendarTableName . " ORDER BY calendarorder ASC");
- $stmt->execute();
-
- $calendars = [];
- while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
- // Filter Calendars by the array returned by the DokuWiki Auth system
- if(array_search($row['id'], $idFilter) === false)
- continue;
- $components = [];
- if ($row['components']) {
+ $calendars = array();
+ foreach($idInfo as $id => $data)
+ {
+ $row = $this->hlp->getCalendarSettings($id);
+ $components = array();
+ if ($row['components'])
+ {
$components = explode(',', $row['components']);
}
- $calendar = [
+ $calendar = array(
'id' => $row['id'],
'uri' => $row['uri'],
- 'principaluri' => $principalUri,//$row['principaluri'], // Overwrite principaluri from database, we actually don't need it.
+ 'principaluri' => $principalUri,//Overwrite principaluri from database, we actually don't need it.
'{' . CalDAV\Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ? $row['synctoken'] : '0'),
'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
'{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet($components),
- '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
- ];
+ //'{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
+ );
if($idInfo[$row['id']]['readonly'] === true)
$calendar['{http://sabredav.org/ns}read-only'] = '1';
- foreach ($this->propertyMap as $xmlName => $dbName) {
+ foreach ($this->propertyMap as $xmlName => $dbName)
+ {
$calendar[$xmlName] = $row[$dbName];
}
$calendars[] = $calendar;
}
return $calendars;
}
/**
* Creates a new calendar for a principal.
*
* If the creation was a success, an id must be returned that can be used
* to reference this calendar in other methods, such as updateCalendar.
*
* @param string $principalUri
* @param string $calendarUri
* @param array $properties
* @return string
*/
- function createCalendar($principalUri, $calendarUri, array $properties) {
+ function createCalendar($principalUri, $calendarUri, array $properties)
+ {
return false;
- /*
- $fieldNames = [
- 'principaluri',
- 'uri',
- 'synctoken',
- 'transparent',
- ];
- $values = [
- ':principaluri' => $principalUri,
- ':uri' => $calendarUri,
- ':synctoken' => 1,
- ':transparent' => 0,
- ];
-
- // Default value
- $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
- $fieldNames[] = 'components';
- if (!isset($properties[$sccs])) {
- $values[':components'] = 'VEVENT,VTODO';
- } else {
- if (!($properties[$sccs] instanceof CalDAV\Xml\Property\SupportedCalendarComponentSet)) {
- throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet');
- }
- $values[':components'] = implode(',', $properties[$sccs]->getValue());
- }
- $transp = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp';
- if (isset($properties[$transp])) {
- $values[':transparent'] = $properties[$transp]->getValue() === 'transparent';
- }
-
- foreach ($this->propertyMap as $xmlName => $dbName) {
- if (isset($properties[$xmlName])) {
-
- $values[':' . $dbName] = $properties[$xmlName];
- $fieldNames[] = $dbName;
- }
- }
-
- $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");
- $stmt->execute($values);
-
- return $this->pdo->lastInsertId();
- */
}
/**
* Updates properties for a calendar.
*
* The list of mutations is stored in a Sabre\DAV\PropPatch object.
* To do the actual updates, you must tell this object which properties
* you're going to process with the handle() method.
*
* Calling the handle method is like telling the PropPatch object "I
* promise I can handle updating this property".
*
* Read the PropPatch documenation for more info and examples.
*
* @param string $calendarId
* @param \Sabre\DAV\PropPatch $propPatch
* @return void
*/
- function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) {
+ function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch)
+ {
$supportedProperties = array_keys($this->propertyMap);
- $supportedProperties[] = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp';
+
+ $propPatch->handle($supportedProperties, function($mutations) use ($calendarId)
+ {
+ foreach ($mutations as $propertyName => $propertyValue)
+ {
- $propPatch->handle($supportedProperties, function($mutations) use ($calendarId) {
- $newValues = [];
- foreach ($mutations as $propertyName => $propertyValue) {
-
- switch ($propertyName) {
- case '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' :
- $fieldName = 'transparent';
- $newValues[$fieldName] = $propertyValue->getValue() === 'transparent';
+ switch ($propertyName)
+ {
+ case '{DAV:}displayname' :
+ $this->hlp->updateCalendarName($calendarId, $propertyValue);
+ break;
+ case '{urn:ietf:params:xml:ns:caldav}calendar-description':
+ $this->hlp->updateCalendarDescription($calendarId, $propertyValue);
+ break;
+ case '{urn:ietf:params:xml:ns:caldav}calendar-timezone':
+ $this->hlp->updateCalendarTimezone($calendarId, $propertyValue);
break;
default :
- $fieldName = $this->propertyMap[$propertyName];
- $newValues[$fieldName] = $propertyValue;
break;
}
}
- $valuesSql = [];
- foreach ($newValues as $fieldName => $value) {
- $valuesSql[] = $fieldName . ' = ?';
- }
-
- $stmt = $this->pdo->prepare("UPDATE " . $this->calendarTableName . " SET " . implode(', ', $valuesSql) . " WHERE id = ?");
- $newValues['id'] = $calendarId;
- $stmt->execute(array_values($newValues));
-
- $this->addChange($calendarId, "", 2);
-
return true;
});
}
/**
* Delete a calendar and all it's objects
*
* @param string $calendarId
* @return void
*/
- function deleteCalendar($calendarId) {
-
- /*
- $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?');
- $stmt->execute([$calendarId]);
-
- $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarTableName . ' WHERE id = ?');
- $stmt->execute([$calendarId]);
-
- $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarChangesTableName . ' WHERE calendarid = ?');
- $stmt->execute([$calendarId]);
- */
-
+ function deleteCalendar($calendarId)
+ {
+ return;
}
/**
* Returns all calendar objects within a calendar.
*
* Every item contains an array with the following keys:
* * calendardata - The iCalendar-compatible calendar data
* * uri - a unique key which will be used to construct the uri. This can
* be any arbitrary string, but making sure it ends with '.ics' is a
* good idea. This is only the basename, or filename, not the full
* path.
* * lastmodified - a timestamp of the last modification time
* * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
* ' "abcdef"')
* * size - The size of the calendar objects, in bytes.
* * component - optional, a string containing the type of object, such
* as 'vevent' or 'vtodo'. If specified, this will be used to populate
* the Content-Type header.
*
* Note that the etag is optional, but it's highly encouraged to return for
* speed reasons.
*
* The calendardata is also optional. If it's not returned
* 'getCalendarObject' will be called later, which *is* expected to return
* calendardata.
*
* If neither etag or size are specified, the calendardata will be
* used/fetched to determine these numbers. If both are specified the
* amount of times this is needed is reduced by a great degree.
*
* @param string $calendarId
* @return array
*/
- function getCalendarObjects($calendarId) {
+ function getCalendarObjects($calendarId)
+ {
- $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?');
- $stmt->execute([$calendarId]);
-
- $result = [];
- foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
- $result[] = [
+ $arr = $this->hlp->getCalendarObjects($calendarId);
+ $result = array();
+ foreach ($arr as $row)
+ {
+ $result[] = array(
'id' => $row['id'],
'uri' => $row['uri'],
'lastmodified' => $row['lastmodified'],
'etag' => '"' . $row['etag'] . '"',
'calendarid' => $row['calendarid'],
'size' => (int)$row['size'],
'component' => strtolower($row['componenttype']),
- ];
+ );
}
return $result;
}
/**
* Returns information from a single calendar object, based on it's object
* uri.
*
* The object uri is only the basename, or filename and not a full path.
*
* The returned array must have the same keys as getCalendarObjects. The
* 'calendardata' object is required here though, while it's not required
* for getCalendarObjects.
*
* This method must return null if the object did not exist.
*
* @param string $calendarId
* @param string $objectUri
* @return array|null
*/
- function getCalendarObject($calendarId, $objectUri) {
+ function getCalendarObject($calendarId, $objectUri)
+ {
+
+ $row = $this->hlp->getCalendarObjectByUri($calendarId, $objectUri);
- $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri = ?');
- $stmt->execute([$calendarId, $objectUri]);
- $row = $stmt->fetch(\PDO::FETCH_ASSOC);
+ if (!$row)
+ return null;
- if (!$row) return null;
-
- return [
+ return array(
'id' => $row['id'],
'uri' => $row['uri'],
'lastmodified' => $row['lastmodified'],
'etag' => '"' . $row['etag'] . '"',
'calendarid' => $row['calendarid'],
'size' => (int)$row['size'],
'calendardata' => $row['calendardata'],
'component' => strtolower($row['componenttype']),
- ];
+ );
}
/**
* Returns a list of calendar objects.
*
* This method should work identical to getCalendarObject, but instead
* return all the calendar objects in the list as an array.
*
* If the backend supports this, it may allow for some speed-ups.
*
* @param mixed $calendarId
* @param array $uris
* @return array
*/
- function getMultipleCalendarObjects($calendarId, array $uris) {
+ function getMultipleCalendarObjects($calendarId, array $uris)
+ {
- $query = 'SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri IN (';
- // Inserting a whole bunch of question marks
- $query .= implode(',', array_fill(0, count($uris), '?'));
- $query .= ')';
+ $arr = $this->hlp->getMultipleCalendarObjectsByUri($calendarId, $uris);
- $stmt = $this->pdo->prepare($query);
- $stmt->execute(array_merge([$calendarId], $uris));
+ $result = array();
+ foreach($arr as $row)
+ {
- $result = [];
- while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
-
- $result[] = [
+ $result[] = array(
'id' => $row['id'],
'uri' => $row['uri'],
'lastmodified' => $row['lastmodified'],
'etag' => '"' . $row['etag'] . '"',
'calendarid' => $row['calendarid'],
'size' => (int)$row['size'],
'calendardata' => $row['calendardata'],
'component' => strtolower($row['componenttype']),
- ];
+ );
}
return $result;
}
/**
* Creates a new calendar object.
*
* The object uri is only the basename, or filename and not a full path.
*
* It is possible return an etag from this function, which will be used in
* the response to this PUT request. Note that the ETag must be surrounded
* by double-quotes.
*
* However, you should only really return this ETag if you don't mangle the
* calendar-data. If the result of a subsequent GET to this object is not
* the exact same as this request body, you should omit the ETag.
*
* @param mixed $calendarId
* @param string $objectUri
* @param string $calendarData
* @return string|null
*/
- function createCalendarObject($calendarId, $objectUri, $calendarData) {
-
- $extraData = $this->getDenormalizedData($calendarData);
+ function createCalendarObject($calendarId, $objectUri, $calendarData)
+ {
- $stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarObjectTableName . ' (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES (?,?,?,?,?,?,?,?,?,?)');
- $stmt->execute([
- $calendarId,
- $objectUri,
- $calendarData,
- time(),
- $extraData['etag'],
- $extraData['size'],
- $extraData['componentType'],
- $extraData['firstOccurence'],
- $extraData['lastOccurence'],
- $extraData['uid'],
- ]);
- $this->addChange($calendarId, $objectUri, 1);
-
- return '"' . $extraData['etag'] . '"';
-
+ $etag = $this->hlp->addCalendarEntryToCalendarByICS($calendarId, $objectUri, $calendarData);
+
+ return '"' . $etag . '"';
}
/**
* Updates an existing calendarobject, based on it's uri.
*
* The object uri is only the basename, or filename and not a full path.
*
* It is possible return an etag from this function, which will be used in
* the response to this PUT request. Note that the ETag must be surrounded
* by double-quotes.
*
* However, you should only really return this ETag if you don't mangle the
* calendar-data. If the result of a subsequent GET to this object is not
* the exact same as this request body, you should omit the ETag.
*
* @param mixed $calendarId
* @param string $objectUri
* @param string $calendarData
* @return string|null
*/
- function updateCalendarObject($calendarId, $objectUri, $calendarData) {
-
- $extraData = $this->getDenormalizedData($calendarData);
+ function updateCalendarObject($calendarId, $objectUri, $calendarData)
+ {
- $stmt = $this->pdo->prepare('UPDATE ' . $this->calendarObjectTableName . ' SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ?, uid = ? WHERE calendarid = ? AND uri = ?');
- $stmt->execute([$calendarData, time(), $extraData['etag'], $extraData['size'], $extraData['componentType'], $extraData['firstOccurence'], $extraData['lastOccurence'], $extraData['uid'], $calendarId, $objectUri]);
-
- $this->addChange($calendarId, $objectUri, 2);
-
- return '"' . $extraData['etag'] . '"';
+ $etag = $this->hlp->editCalendarEntryToCalendarByICS($calendarId, $objectUri, $calendarData);
+ return '"' . $etag. '"';
}
- /**
- * Parses some information from calendar objects, used for optimized
- * calendar-queries.
- *
- * Returns an array with the following keys:
- * * etag - An md5 checksum of the object without the quotes.
- * * size - Size of the object in bytes
- * * componentType - VEVENT, VTODO or VJOURNAL
- * * firstOccurence
- * * lastOccurence
- * * uid - value of the UID property
- *
- * @param string $calendarData
- * @return array
- */
- protected function getDenormalizedData($calendarData) {
- $vObject = VObject\Reader::read($calendarData);
- $componentType = null;
- $component = null;
- $firstOccurence = null;
- $lastOccurence = null;
- $uid = null;
- foreach ($vObject->getComponents() as $component) {
- if ($component->name !== 'VTIMEZONE') {
- $componentType = $component->name;
- $uid = (string)$component->UID;
- break;
- }
- }
- if (!$componentType) {
- throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
- }
- if ($componentType === 'VEVENT') {
- $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp();
- // Finding the last occurence is a bit harder
- if (!isset($component->RRULE)) {
- if (isset($component->DTEND)) {
- $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
- } elseif (isset($component->DURATION)) {
- $endDate = clone $component->DTSTART->getDateTime();
- $endDate->add(VObject\DateTimeParser::parse($component->DURATION->getValue()));
- $lastOccurence = $endDate->getTimeStamp();
- } elseif (!$component->DTSTART->hasTime()) {
- $endDate = clone $component->DTSTART->getDateTime();
- $endDate->modify('+1 day');
- $lastOccurence = $endDate->getTimeStamp();
- } else {
- $lastOccurence = $firstOccurence;
- }
- } else {
- $it = new VObject\Recur\EventIterator($vObject, (string)$component->UID);
- $maxDate = new \DateTime(self::MAX_DATE);
- if ($it->isInfinite()) {
- $lastOccurence = $maxDate->getTimeStamp();
- } else {
- $end = $it->getDtEnd();
- while ($it->valid() && $end < $maxDate) {
- $end = $it->getDtEnd();
- $it->next();
-
- }
- $lastOccurence = $end->getTimeStamp();
- }
-
- }
- }
-
- return [
- 'etag' => md5($calendarData),
- 'size' => strlen($calendarData),
- 'componentType' => $componentType,
- 'firstOccurence' => $firstOccurence,
- 'lastOccurence' => $lastOccurence,
- 'uid' => $uid,
- ];
-
- }
/**
* Deletes an existing calendar object.
*
* The object uri is only the basename, or filename and not a full path.
*
* @param string $calendarId
* @param string $objectUri
* @return void
*/
- function deleteCalendarObject($calendarId, $objectUri) {
-
- $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri = ?');
- $stmt->execute([$calendarId, $objectUri]);
-
- $this->addChange($calendarId, $objectUri, 3);
+ function deleteCalendarObject($calendarId, $objectUri)
+ {
+ $this->hlp->deleteCalendarEntryForCalendarByUri($calendarId, $objectUri);
}
/**
* Performs a calendar-query on the contents of this calendar.
*
* The calendar-query is defined in RFC4791 : CalDAV. Using the
* calendar-query it is possible for a client to request a specific set of
* object, based on contents of iCalendar properties, date-ranges and
* iCalendar component types (VTODO, VEVENT).
*
* This method should just return a list of (relative) urls that match this
* query.
*
* The list of filters are specified as an array. The exact array is
* documented by \Sabre\CalDAV\CalendarQueryParser.
*
* Note that it is extremely likely that getCalendarObject for every path
* returned from this method will be called almost immediately after. You
* may want to anticipate this to speed up these requests.
*
* This method provides a default implementation, which parses *all* the
* iCalendar objects in the specified calendar.
*
* This default may well be good enough for personal use, and calendars
* that aren't very large. But if you anticipate high usage, big calendars
* or high loads, you are strongly adviced to optimize certain paths.
*
* The best way to do so is override this method and to optimize
* specifically for 'common filters'.
*
* Requests that are extremely common are:
* * requests for just VEVENTS
* * requests for just VTODO
* * requests with a time-range-filter on a VEVENT.
*
* ..and combinations of these requests. It may not be worth it to try to
* handle every possible situation and just rely on the (relatively
* easy to use) CalendarQueryValidator to handle the rest.
*
* Note that especially time-range-filters may be difficult to parse. A
* time-range filter specified on a VEVENT must for instance also handle
* recurrence rules correctly.
* A good example of how to interprete all these filters can also simply
* be found in \Sabre\CalDAV\CalendarQueryFilter. This class is as correct
* as possible, so it gives you a good idea on what type of stuff you need
* to think of.
*
* This specific implementation (for the PDO) backend optimizes filters on
* specific components, and VEVENT time-ranges.
*
* @param string $calendarId
* @param array $filters
* @return array
*/
- function calendarQuery($calendarId, array $filters) {
-
- $componentType = null;
- $requirePostFilter = true;
- $timeRange = null;
-
- // if no filters were specified, we don't need to filter after a query
- if (!$filters['prop-filters'] && !$filters['comp-filters']) {
- $requirePostFilter = false;
- }
-
- // Figuring out if there's a component filter
- if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
- $componentType = $filters['comp-filters'][0]['name'];
-
- // Checking if we need post-filters
- if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
- $requirePostFilter = false;
- }
- // There was a time-range filter
- if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) {
- $timeRange = $filters['comp-filters'][0]['time-range'];
-
- // If start time OR the end time is not specified, we can do a
- // 100% accurate mysql query.
- if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
- $requirePostFilter = false;
- }
- }
-
- }
-
- if ($requirePostFilter) {
- $query = "SELECT uri, calendardata FROM " . $this->calendarObjectTableName . " WHERE calendarid = :calendarid";
- } else {
- $query = "SELECT uri FROM " . $this->calendarObjectTableName . " WHERE calendarid = :calendarid";
- }
-
- $values = [
- 'calendarid' => $calendarId,
- ];
-
- if ($componentType) {
- $query .= " AND componenttype = :componenttype";
- $values['componenttype'] = $componentType;
- }
-
- if ($timeRange && $timeRange['start']) {
- $query .= " AND lastoccurence > :startdate";
- $values['startdate'] = $timeRange['start']->getTimeStamp();
- }
- if ($timeRange && $timeRange['end']) {
- $query .= " AND firstoccurence < :enddate";
- $values['enddate'] = $timeRange['end']->getTimeStamp();
- }
-
- $stmt = $this->pdo->prepare($query);
- $stmt->execute($values);
-
- $result = [];
- while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
- if ($requirePostFilter) {
- if (!$this->validateFilterForObject($row, $filters)) {
- continue;
- }
- }
- $result[] = $row['uri'];
-
- }
-
+ function calendarQuery($calendarId, array $filters)
+ {
+ $result = $this->hlp->calendarQuery($calendarId, $filters);
return $result;
-
}
/**
* Searches through all of a users calendars and calendar objects to find
* an object with a specific UID.
*
* This method should return the path to this object, relative to the
* calendar home, so this path usually only contains two parts:
*
* calendarpath/objectpath.ics
*
* If the uid is not found, return null.
*
* This method should only consider * objects that the principal owns, so
* any calendars owned by other principals that also appear in this
* collection should be ignored.
*
* @param string $principalUri
* @param string $uid
* @return string|null
*/
- function getCalendarObjectByUID($principalUri, $uid) {
-
- $query = <<<SQL
-SELECT
- calendars.uri AS calendaruri, calendarobjects.uri as objecturi
-FROM
- $this->calendarObjectTableName AS calendarobjects
-LEFT JOIN
- $this->calendarTableName AS calendars
- ON calendarobjects.calendarid = calendars.id
-WHERE
- calendars.principaluri = ?
- AND
- calendarobjects.uid = ?
-SQL;
-
- $stmt = $this->pdo->prepare($query);
- $stmt->execute([$principalUri, $uid]);
-
- if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
- return $row['calendaruri'] . '/' . $row['objecturi'];
+ function getCalendarObjectByUID($principalUri, $uid)
+ {
+ $calids = array_keys($this->hlp->getCalendarIsForUser($principalUri));
+ $event = $this->hlp->getEventWithUid($uid);
+
+ if(in_array($event['calendarid'], $calids))
+ {
+ $settings = $this->hlp->getCalendarSettings($event['calendarid']);
+ return $settings['uri'] . '/' . $event['uri'];
}
-
+ return null;
}
/**
* The getChanges method returns all the changes that have happened, since
* the specified syncToken in the specified calendar.
*
* This function should return an array, such as the following:
*
* [
* 'syncToken' => 'The current synctoken',
* 'added' => [
* 'new.txt',
* ],
* 'modified' => [
* 'modified.txt',
* ],
* 'deleted' => [
* 'foo.php.bak',
* 'old.txt'
* ]
* ];
*
* The returned syncToken property should reflect the *current* syncToken
* of the calendar, as reported in the {http://sabredav.org/ns}sync-token
* property this is needed here too, to ensure the operation is atomic.
*
* If the $syncToken argument is specified as null, this is an initial
* sync, and all members should be reported.
*
* The modified property is an array of nodenames that have changed since
* the last token.
*
* The deleted property is an array with nodenames, that have been deleted
* from collection.
*
* The $syncLevel argument is basically the 'depth' of the report. If it's
* 1, you only have to report changes that happened only directly in
* immediate descendants. If it's 2, it should also include changes from
* the nodes below the child collections. (grandchildren)
*
* The $limit argument allows a client to specify how many results should
* be returned at most. If the limit is not specified, it should be treated
* as infinite.
*
* If the limit (infinite or not) is higher than you're willing to return,
* you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
*
* If the syncToken is expired (due to data cleanup) or unknown, you must
* return null.
*
* The limit is 'suggestive'. You are free to ignore it.
*
* @param string $calendarId
* @param string $syncToken
* @param int $syncLevel
* @param int $limit
* @return array
*/
- function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) {
-
- // Current synctoken
- $stmt = $this->pdo->prepare('SELECT synctoken FROM ' . $this->calendarTableName . ' WHERE id = ?');
- $stmt->execute([ $calendarId ]);
- $currentToken = $stmt->fetchColumn(0);
-
- if (is_null($currentToken)) return null;
-
- $result = [
- 'syncToken' => $currentToken,
- 'added' => [],
- 'modified' => [],
- 'deleted' => [],
- ];
-
- if ($syncToken) {
-
- $query = "SELECT uri, operation FROM " . $this->calendarChangesTableName . " WHERE synctoken >= ? AND synctoken < ? AND calendarid = ? ORDER BY synctoken";
- if ($limit > 0) $query .= " LIMIT " . (int)$limit;
-
- // Fetching all changes
- $stmt = $this->pdo->prepare($query);
- $stmt->execute([$syncToken, $currentToken, $calendarId]);
-
- $changes = [];
-
- // This loop ensures that any duplicates are overwritten, only the
- // last change on a node is relevant.
- while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
-
- $changes[$row['uri']] = $row['operation'];
-
- }
-
- foreach ($changes as $uri => $operation) {
-
- switch ($operation) {
- case 1 :
- $result['added'][] = $uri;
- break;
- case 2 :
- $result['modified'][] = $uri;
- break;
- case 3 :
- $result['deleted'][] = $uri;
- break;
- }
-
- }
- } else {
- // No synctoken supplied, this is the initial sync.
- $query = "SELECT uri FROM " . $this->calendarObjectTableName . " WHERE calendarid = ?";
- $stmt = $this->pdo->prepare($query);
- $stmt->execute([$calendarId]);
-
- $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
- }
+ function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null)
+ {
+ $result = $this->hlp->getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit);
return $result;
-
- }
-
- /**
- * Adds a change record to the calendarchanges table.
- *
- * @param mixed $calendarId
- * @param string $objectUri
- * @param int $operation 1 = add, 2 = modify, 3 = delete.
- * @return void
- */
- protected function addChange($calendarId, $objectUri, $operation) {
-
- $stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarChangesTableName . ' (uri, synctoken, calendarid, operation) SELECT ?, synctoken, ?, ? FROM ' . $this->calendarTableName . ' WHERE id = ?');
- $stmt->execute([
- $objectUri,
- $calendarId,
- $operation,
- $calendarId
- ]);
- $stmt = $this->pdo->prepare('UPDATE ' . $this->calendarTableName . ' SET synctoken = synctoken + 1 WHERE id = ?');
- $stmt->execute([
- $calendarId
- ]);
-
}
/**
* Returns a list of subscriptions for a principal.
*
* Every subscription is an array with the following keys:
* * id, a unique id that will be used by other functions to modify the
* subscription. This can be the same as the uri or a database key.
* * uri. This is just the 'base uri' or 'filename' of the subscription.
* * principaluri. The owner of the subscription. Almost always the same as
* principalUri passed to this method.
* * source. Url to the actual feed
*
* Furthermore, all the subscription info must be returned too:
*
* 1. {DAV:}displayname
* 2. {http://apple.com/ns/ical/}refreshrate
* 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
* should not be stripped).
* 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
* should not be stripped).
* 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
* attachments should not be stripped).
* 7. {http://apple.com/ns/ical/}calendar-color
* 8. {http://apple.com/ns/ical/}calendar-order
* 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
* (should just be an instance of
* Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
* default components).
*
* @param string $principalUri
* @return array
*/
- function getSubscriptionsForUser($principalUri) {
-
- $fields = array_values($this->subscriptionPropertyMap);
- $fields[] = 'id';
- $fields[] = 'uri';
- $fields[] = 'source';
- $fields[] = 'principaluri';
- $fields[] = 'lastmodified';
-
- // Making fields a comma-delimited list
- $fields = implode(', ', $fields);
- $stmt = $this->pdo->prepare("SELECT " . $fields . " FROM " . $this->calendarSubscriptionsTableName . " WHERE principaluri = ? ORDER BY calendarorder ASC");
- $stmt->execute([$principalUri]);
-
- $subscriptions = [];
- while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
-
- $subscription = [
- 'id' => $row['id'],
- 'uri' => $row['uri'],
- 'principaluri' => $row['principaluri'],
- 'source' => $row['source'],
- 'lastmodified' => $row['lastmodified'],
-
- '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
- ];
-
- foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
- if (!is_null($row[$dbName])) {
- $subscription[$xmlName] = $row[$dbName];
- }
- }
-
- $subscriptions[] = $subscription;
-
- }
-
- return $subscriptions;
+ function getSubscriptionsForUser($principalUri)
+ {
+ return array();
}
/**
* Creates a new subscription for a principal.
*
* If the creation was a success, an id must be returned that can be used to reference
* this subscription in other methods, such as updateSubscription.
*
* @param string $principalUri
* @param string $uri
* @param array $properties
* @return mixed
*/
- function createSubscription($principalUri, $uri, array $properties) {
-
- $fieldNames = [
- 'principaluri',
- 'uri',
- 'source',
- 'lastmodified',
- ];
-
- if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
- throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
- }
-
- $values = [
- ':principaluri' => $principalUri,
- ':uri' => $uri,
- ':source' => $properties['{http://calendarserver.org/ns/}source']->getHref(),
- ':lastmodified' => time(),
- ];
-
- foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
- if (isset($properties[$xmlName])) {
-
- $values[':' . $dbName] = $properties[$xmlName];
- $fieldNames[] = $dbName;
- }
- }
-
- $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarSubscriptionsTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");
- $stmt->execute($values);
-
- return $this->pdo->lastInsertId();
+ function createSubscription($principalUri, $uri, array $properties)
+ {
+ return null;
}
/**
* Updates a subscription
*
* The list of mutations is stored in a Sabre\DAV\PropPatch object.
* To do the actual updates, you must tell this object which properties
* you're going to process with the handle() method.
*
* Calling the handle method is like telling the PropPatch object "I
* promise I can handle updating this property".
*
* Read the PropPatch documenation for more info and examples.
*
* @param mixed $subscriptionId
* @param \Sabre\DAV\PropPatch $propPatch
* @return void
*/
- function updateSubscription($subscriptionId, DAV\PropPatch $propPatch) {
-
- $supportedProperties = array_keys($this->subscriptionPropertyMap);
- $supportedProperties[] = '{http://calendarserver.org/ns/}source';
-
- $propPatch->handle($supportedProperties, function($mutations) use ($subscriptionId) {
-
- $newValues = [];
-
- foreach ($mutations as $propertyName => $propertyValue) {
-
- if ($propertyName === '{http://calendarserver.org/ns/}source') {
- $newValues['source'] = $propertyValue->getHref();
- } else {
- $fieldName = $this->subscriptionPropertyMap[$propertyName];
- $newValues[$fieldName] = $propertyValue;
- }
-
- }
-
- // Now we're generating the sql query.
- $valuesSql = [];
- foreach ($newValues as $fieldName => $value) {
- $valuesSql[] = $fieldName . ' = ?';
- }
-
- $stmt = $this->pdo->prepare("UPDATE " . $this->calendarSubscriptionsTableName . " SET " . implode(', ', $valuesSql) . ", lastmodified = ? WHERE id = ?");
- $newValues['lastmodified'] = time();
- $newValues['id'] = $subscriptionId;
- $stmt->execute(array_values($newValues));
-
- return true;
-
- });
-
+ function updateSubscription($subscriptionId, DAV\PropPatch $propPatch)
+ {
+ return;
}
/**
* Deletes a subscription
*
* @param mixed $subscriptionId
* @return void
*/
- function deleteSubscription($subscriptionId) {
+ function deleteSubscription($subscriptionId)
+ {
- $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarSubscriptionsTableName . ' WHERE id = ?');
- $stmt->execute([$subscriptionId]);
+ return;
}
/**
* Returns a single scheduling object.
*
* The returned array should contain the following elements:
* * uri - A unique basename for the object. This will be used to
* construct a full uri.
* * calendardata - The iCalendar object
* * lastmodified - The last modification date. Can be an int for a unix
* timestamp, or a PHP DateTime object.
* * etag - A unique token that must change if the object changed.
* * size - The size of the object, in bytes.
*
* @param string $principalUri
* @param string $objectUri
* @return array
*/
- function getSchedulingObject($principalUri, $objectUri) {
-
- $stmt = $this->pdo->prepare('SELECT uri, calendardata, lastmodified, etag, size FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ? AND uri = ?');
- $stmt->execute([$principalUri, $objectUri]);
- $row = $stmt->fetch(\PDO::FETCH_ASSOC);
-
- if (!$row) return null;
+ function getSchedulingObject($principalUri, $objectUri)
+ {
- return [
- 'uri' => $row['uri'],
- 'calendardata' => $row['calendardata'],
- 'lastmodified' => $row['lastmodified'],
- 'etag' => '"' . $row['etag'] . '"',
- 'size' => (int)$row['size'],
- ];
+ return null;
}
/**
* Returns all scheduling objects for the inbox collection.
*
* These objects should be returned as an array. Every item in the array
* should follow the same structure as returned from getSchedulingObject.
*
* The main difference is that 'calendardata' is optional.
*
* @param string $principalUri
* @return array
*/
- function getSchedulingObjects($principalUri) {
-
- $stmt = $this->pdo->prepare('SELECT id, calendardata, uri, lastmodified, etag, size FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ?');
- $stmt->execute([$principalUri]);
-
- $result = [];
- foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
- $result[] = [
- 'calendardata' => $row['calendardata'],
- 'uri' => $row['uri'],
- 'lastmodified' => $row['lastmodified'],
- 'etag' => '"' . $row['etag'] . '"',
- 'size' => (int)$row['size'],
- ];
- }
-
- return $result;
+ function getSchedulingObjects($principalUri)
+ {
+ return null;
}
/**
* Deletes a scheduling object
*
* @param string $principalUri
* @param string $objectUri
* @return void
*/
- function deleteSchedulingObject($principalUri, $objectUri) {
+ function deleteSchedulingObject($principalUri, $objectUri)
+ {
- $stmt = $this->pdo->prepare('DELETE FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ? AND uri = ?');
- $stmt->execute([$principalUri, $objectUri]);
-
+ return;
}
/**
* Creates a new scheduling object. This should land in a users' inbox.
*
* @param string $principalUri
* @param string $objectUri
* @param string $objectData
* @return void
*/
- function createSchedulingObject($principalUri, $objectUri, $objectData) {
+ function createSchedulingObject($principalUri, $objectUri, $objectData)
+ {
- $stmt = $this->pdo->prepare('INSERT INTO ' . $this->schedulingObjectTableName . ' (principaluri, calendardata, uri, lastmodified, etag, size) VALUES (?, ?, ?, ?, ?, ?)');
- $stmt->execute([$principalUri, $objectData, $objectUri, time(), md5($objectData), strlen($objectData) ]);
+ return;
}
}
diff --git a/calendarserver.php b/calendarserver.php
--- a/calendarserver.php
+++ b/calendarserver.php
@@ -1,112 +1,100 @@
<?php
/**
* DokuWiki DAVCal PlugIn - DAV Calendar Server PlugIn.
*
* This is heavily based on SabreDAV and features a DokuWiki connector.
*/
// Initialize DokuWiki
if(!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__).'/../../../');
if (!defined('DOKU_DISABLE_GZIP_OUTPUT')) define('DOKU_DISABLE_GZIP_OUTPUT', 1);
require_once(DOKU_INC.'inc/init.php');
session_write_close(); //close session
global $conf;
if($conf['allowdebug'])
dbglog('---- DAVCAL calendarserver.php init');
$hlp = null;
$hlp =& plugin_load('helper', 'davcal');
if(is_null($hlp))
{
if($conf['allowdebug'])
dbglog('Error loading helper plugin');
die('Error loading helper plugin');
}
$baseUri = DOKU_BASE.'lib/plugins/davcal/'.basename(__FILE__).'/';
-$sqlFile = $conf['metadir'].'/davcal.sqlite3';
-
-if(!file_exists($sqlFile))
-{
- if($conf['allowdebug'])
- dbglog('SQL File doesn\'t exist: '.$sqlFile);
- die('SQL File doesn\'t exist');
-}
if($hlp->getConfig('disable_sync') === 1)
{
if($conf['allowdebug'])
dbglog('Synchronisation is disabled');
die('Synchronisation is disabled');
}
-/* Database */
-$pdo = new PDO('sqlite:'.$sqlFile);
-$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
-
//Mapping PHP errors to exceptions
function exception_error_handler($errno, $errstr, $errfile, $errline) {
if($conf['allowdebug'])
dbglog('Exception occured: '.$errstr);
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
//set_error_handler("exception_error_handler");
// Files we need
require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
require_once(DOKU_PLUGIN.'davcal/authBackendDokuwiki.php');
require_once(DOKU_PLUGIN.'davcal/principalBackendDokuwiki.php');
require_once(DOKU_PLUGIN.'davcal/calendarBackendDokuwiki.php');
// Backends - our DokuWiki backends
$authBackend = new DokuWikiSabreAuthBackend();
-$calendarBackend = new DokuWikiSabreCalendarBackend($pdo);
+$calendarBackend = new DokuWikiSabreCalendarBackend($hlp);
$principalBackend = new DokuWikiSabrePrincipalBackend();
// Directory structure
-$tree = [
+$tree = array(
new Sabre\CalDAV\Principal\Collection($principalBackend),
new Sabre\CalDAV\CalendarRoot($principalBackend, $calendarBackend),
-];
+);
$server = new Sabre\DAV\Server($tree);
if (isset($baseUri))
$server->setBaseUri($baseUri);
/* Server Plugins */
$authPlugin = new Sabre\DAV\Auth\Plugin($authBackend);
$server->addPlugin($authPlugin);
$aclPlugin = new Sabre\DAVACL\Plugin();
$server->addPlugin($aclPlugin);
/* CalDAV support */
$caldavPlugin = new Sabre\CalDAV\Plugin();
$server->addPlugin($caldavPlugin);
/* Calendar subscription support */
//$server->addPlugin(
// new Sabre\CalDAV\Subscriptions\Plugin()
//);
/* Calendar scheduling support */
//$server->addPlugin(
// new Sabre\CalDAV\Schedule\Plugin()
//);
/* WebDAV-Sync plugin */
$server->addPlugin(new Sabre\DAV\Sync\Plugin());
// Support for html frontend
$browser = new Sabre\DAV\Browser\Plugin();
$server->addPlugin($browser);
if($conf['allowdebug'])
dbglog('$server->exec()');
// And off we go!
$server->exec();
diff --git a/helper.php b/helper.php
--- a/helper.php
+++ b/helper.php
@@ -1,1156 +1,1650 @@
<?php
/**
* Helper Class for the DAVCal plugin
* This helper does the actual work.
*
*/
// must be run within Dokuwiki
if(!defined('DOKU_INC')) die();
class helper_plugin_davcal extends DokuWiki_Plugin {
protected $sqlite = null;
protected $cachedValues = array();
/**
* Constructor to load the configuration and the SQLite plugin
*/
public function helper_plugin_davcal() {
$this->sqlite =& plugin_load('helper', 'sqlite');
global $conf;
if($conf['allowdebug'])
dbglog('---- DAVCAL helper.php init');
if(!$this->sqlite)
{
if($conf['allowdebug'])
dbglog('This plugin requires the sqlite plugin. Please install it.');
msg('This plugin requires the sqlite plugin. Please install it.');
return;
}
if(!$this->sqlite->init('davcal', DOKU_PLUGIN.'davcal/db/'))
{
if($conf['allowdebug'])
dbglog('Error initialising the SQLite DB for DAVCal');
return;
}
}
/**
* Retrieve meta data for a given page
*
* @param string $id optional The page ID
* @return array The metadata
*/
private function getMeta($id = null) {
global $ID;
global $INFO;
if ($id === null) $id = $ID;
if($ID === $id && $INFO['meta']) {
$meta = $INFO['meta'];
} else {
$meta = p_get_metadata($id);
}
return $meta;
}
/**
* Retrieve the meta data for a given page
*
* @param string $id optional The page ID
* @return array with meta data
*/
public function getCalendarMetaForPage($id = null)
{
if(is_null($id))
{
global $ID;
$id = $ID;
}
$meta = $this->getMeta($id);
if(isset($meta['plugin_davcal']))
return $meta['plugin_davcal'];
else
return array();
}
/**
* Filter calendar pages and return only those where the current
* user has at least read permission.
*
* @param array $calendarPages Array with calendar pages to check
* @return array with filtered calendar pages
*/
public function filterCalendarPagesByUserPermission($calendarPages)
{
$retList = array();
foreach($calendarPages as $page => $data)
{
if(auth_quickaclcheck($page) >= AUTH_READ)
{
$retList[$page] = $data;
}
}
return $retList;
}
/**
* Get all calendar pages used by a given page
* based on the stored metadata
*
* @param string $id optional The page id
* @return mixed The pages as array or false
*/
public function getCalendarPagesByMeta($id = null)
{
if(is_null($id))
{
global $ID;
$id = $ID;
}
$meta = $this->getCalendarMetaForPage($id);
if(isset($meta['id']))
{
// Filter the list of pages by permission
$pages = $this->filterCalendarPagesByUserPermission($meta['id']);
if(empty($pages))
return false;
return $pages;
}
return false;
}
/**
* Get a list of calendar names/pages/ids/colors
* for an array of page ids
*
* @param array $calendarPages The calendar pages to retrieve
* @return array The list
*/
public function getCalendarMapForIDs($calendarPages)
{
$data = array();
foreach($calendarPages as $page => $color)
{
$calid = $this->getCalendarIdForPage($page);
if($calid !== false)
{
$settings = $this->getCalendarSettings($calid);
$name = $settings['displayname'];
$write = (auth_quickaclcheck($page) > AUTH_READ);
$data[] = array('name' => $name, 'page' => $page, 'calid' => $calid,
'color' => $color, 'write' => $write);
}
}
return $data;
}
/**
* Get the saved calendar color for a given page.
*
* @param string $id optional The page ID
* @return mixed The color on success, otherwise false
*/
public function getCalendarColorForPage($id = null)
{
if(is_null($id))
{
global $ID;
$id = $ID;
}
$calid = $this->getCalendarIdForPage($id);
if($calid === false)
return false;
return $this->getCalendarColorForCalendar($calid);
}
/**
* Get the saved calendar color for a given calendar ID.
*
* @param string $id optional The calendar ID
* @return mixed The color on success, otherwise false
*/
public function getCalendarColorForCalendar($calid)
{
if(isset($this->cachedValues['calendarcolor'][$calid]))
return $this->cachedValues['calendarcolor'][$calid];
$row = $this->getCalendarSettings($calid);
if(!isset($row['calendarcolor']))
return false;
$color = $row['calendarcolor'];
$this->cachedValues['calendarcolor'][$calid] = $color;
return $color;
}
/**
* Get the user's principal URL for iOS sync
* @param string $user the user name
* @return the URL to the principal sync
*/
public function getPrincipalUrlForUser($user)
{
if(is_null($user))
return false;
$url = DOKU_URL.'lib/plugins/davcal/calendarserver.php/principals/'.$user;
return $url;
}
/**
* Set the calendar color for a given page.
*
* @param string $color The color definition
* @param string $id optional The page ID
* @return boolean True on success, otherwise false
*/
public function setCalendarColorForPage($color, $id = null)
{
if(is_null($id))
{
global $ID;
$id = $ID;
}
$calid = $this->getCalendarIdForPage($id);
if($calid === false)
return false;
$query = "UPDATE calendars SET calendarcolor = ? ".
" WHERE id = ?";
$res = $this->sqlite->query($query, $color, $calid);
if($res !== false)
{
$this->cachedValues['calendarcolor'][$calid] = $color;
return true;
}
return false;
}
/**
* Set the calendar name and description for a given page with a given
* page id.
* If the calendar doesn't exist, the calendar is created!
*
* @param string $name The name of the new calendar
* @param string $description The description of the new calendar
* @param string $id (optional) The ID of the page
* @param string $userid The userid of the creating user
*
* @return boolean True on success, otherwise false.
*/
public function setCalendarNameForPage($name, $description, $id = null, $userid = null)
{
if(is_null($id))
{
global $ID;
$id = $ID;
}
if(is_null($userid))
{
if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
{
$userid = $_SERVER['REMOTE_USER'];
}
else
{
$userid = uniqid('davcal-');
}
}
$calid = $this->getCalendarIdForPage($id);
if($calid === false)
return $this->createCalendarForPage($name, $description, $id, $userid);
$query = "UPDATE calendars SET displayname = ?, description = ? WHERE id = ?";
$res = $this->sqlite->query($query, $name, $description, $calid);
if($res !== false)
return true;
return false;
}
/**
+ * Update a calendar's displayname
+ *
+ * @param int $calid The calendar's ID
+ * @param string $name The new calendar name
+ *
+ * @return boolean True on success, otherwise false
+ */
+ public function updateCalendarName($calid, $name)
+ {
+ $query = "UPDATE calendars SET displayname = ? WHERE id = ?";
+ $res = $this->sqlite->query($query, $calid, $name);
+ if($res !== false)
+ {
+ $this->updateSyncTokenLog($calid, '', 'modified');
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Update the calendar description
+ *
+ * @param int $calid The calendar's ID
+ * @param string $description The new calendar's description
+ *
+ * @return boolean True on success, otherwise false
+ */
+ public function updateCalendarDescription($calid, $description)
+ {
+ $query = "UPDATE calendars SET description = ? WHERE id = ?";
+ $res = $this->sqlite->query($query, $calid, $description);
+ if($res !== false)
+ {
+ $this->updateSyncTokenLog($calid, '', 'modified');
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Update a calendar's timezone information
+ *
+ * @param int $calid The calendar's ID
+ * @param string $timezone The new timezone to set
+ *
+ * @return boolean True on success, otherwise false
+ */
+ public function updateCalendarTimezone($calid, $timezone)
+ {
+ $query = "UPDATE calendars SET timezone = ? WHERE id = ?";
+ $res = $this->sqlite->query($query, $calid, $timezone);
+ if($res !== false)
+ {
+ $this->updateSyncTokenLog($calid, '', 'modified');
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Save the personal settings to the SQLite database 'calendarsettings'.
*
* @param array $settings The settings array to store
* @param string $userid (optional) The userid to store
*
* @param boolean True on success, otherwise false
*/
public function savePersonalSettings($settings, $userid = null)
{
if(is_null($userid))
{
if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
{
$userid = $_SERVER['REMOTE_USER'];
}
else
{
return false;
}
}
$this->sqlite->query("BEGIN TRANSACTION");
$query = "DELETE FROM calendarsettings WHERE userid = ?";
$this->sqlite->query($query, $userid);
foreach($settings as $key => $value)
{
$query = "INSERT INTO calendarsettings (userid, key, value) VALUES (?, ?, ?)";
$res = $this->sqlite->query($query, $userid, $key, $value);
if($res === false)
return false;
}
$this->sqlite->query("COMMIT TRANSACTION");
$this->cachedValues['settings'][$userid] = $settings;
return true;
}
/**
* Retrieve the settings array for a given user id.
* Some sane defaults are returned, currently:
*
* timezone => local
* weeknumbers => 0
* workweek => 0
*
* @param string $userid (optional) The user id to retrieve
*
* @return array The settings array
*/
public function getPersonalSettings($userid = null)
{
// Some sane default settings
$settings = array(
'timezone' => $this->getConf('timezone'),
'weeknumbers' => $this->getConf('weeknumbers'),
'workweek' => $this->getConf('workweek'),
'monday' => $this->getConf('monday'),
'timeformat' => $this->getConf('timeformat')
);
if(is_null($userid))
{
if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
{
$userid = $_SERVER['REMOTE_USER'];
}
else
{
return $settings;
}
}
if(isset($this->cachedValues['settings'][$userid]))
return $this->cachedValues['settings'][$userid];
$query = "SELECT key, value FROM calendarsettings WHERE userid = ?";
$res = $this->sqlite->query($query, $userid);
$arr = $this->sqlite->res2arr($res);
foreach($arr as $row)
{
$settings[$row['key']] = $row['value'];
}
$this->cachedValues['settings'][$userid] = $settings;
return $settings;
}
/**
* Retrieve the calendar ID based on a page ID from the SQLite table
* 'pagetocalendarmapping'.
*
* @param string $id (optional) The page ID to retrieve the corresponding calendar
*
* @return mixed the ID on success, otherwise false
*/
public function getCalendarIdForPage($id = null)
{
if(is_null($id))
{
global $ID;
$id = $ID;
}
if(isset($this->cachedValues['calid'][$id]))
return $this->cachedValues['calid'][$id];
$query = "SELECT calid FROM pagetocalendarmapping WHERE page = ?";
$res = $this->sqlite->query($query, $id);
$row = $this->sqlite->res2row($res);
if(isset($row['calid']))
{
$calid = $row['calid'];
$this->cachedValues['calid'] = $calid;
return $calid;
}
return false;
}
/**
* Retrieve the complete calendar id to page mapping.
* This is necessary to be able to retrieve a list of
* calendars for a given user and check the access rights.
*
* @return array The mapping array
*/
public function getCalendarIdToPageMapping()
{
$query = "SELECT calid, page FROM pagetocalendarmapping";
$res = $this->sqlite->query($query);
$arr = $this->sqlite->res2arr($res);
return $arr;
}
/**
* Retrieve all calendar IDs a given user has access to.
* The user is specified by the principalUri, so the
* user name is actually split from the URI component.
*
* Access rights are checked against DokuWiki's ACL
* and applied accordingly.
*
* @param string $principalUri The principal URI to work on
*
* @return array An associative array of calendar IDs
*/
public function getCalendarIdsForUser($principalUri)
{
global $auth;
$user = explode('/', $principalUri);
$user = end($user);
$mapping = $this->getCalendarIdToPageMapping();
$calids = array();
$ud = $auth->getUserData($user);
$groups = $ud['grps'];
foreach($mapping as $row)
{
$id = $row['calid'];
$page = $row['page'];
$acl = auth_aclcheck($page, $user, $groups);
if($acl >= AUTH_READ)
{
$write = $acl > AUTH_READ;
$calids[$id] = array('readonly' => !$write);
}
}
return $calids;
}
/**
* Create a new calendar for a given page ID and set name and description
* accordingly. Also update the pagetocalendarmapping table on success.
*
* @param string $name The calendar's name
* @param string $description The calendar's description
* @param string $id (optional) The page ID to work on
* @param string $userid (optional) The user ID that created the calendar
*
* @return boolean True on success, otherwise false
*/
public function createCalendarForPage($name, $description, $id = null, $userid = null)
{
if(is_null($id))
{
global $ID;
$id = $ID;
}
if(is_null($userid))
{
if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
{
$userid = $_SERVER['REMOTE_USER'];
}
else
{
$userid = uniqid('davcal-');
}
}
$values = array('principals/'.$userid,
$name,
str_replace(array('/', ' ', ':'), '_', $id),
$description,
'VEVENT,VTODO',
0,
1);
$query = "INSERT INTO calendars (principaluri, displayname, uri, description, components, transparent, synctoken) ".
"VALUES (?, ?, ?, ?, ?, ?, ?)";
$res = $this->sqlite->query($query, $values[0], $values[1], $values[2], $values[3], $values[4], $values[5], $values[6]);
if($res === false)
return false;
// Get the new calendar ID
$query = "SELECT id FROM calendars WHERE principaluri = ? AND displayname = ? AND ".
"uri = ? AND description = ?";
$res = $this->sqlite->query($query, $values[0], $values[1], $values[2], $values[3]);
$row = $this->sqlite->res2row($res);
// Update the pagetocalendarmapping table with the new calendar ID
if(isset($row['id']))
{
$query = "INSERT INTO pagetocalendarmapping (page, calid) VALUES (?, ?)";
$res = $this->sqlite->query($query, $id, $row['id']);
return ($res !== false);
}
return false;
}
/**
+ * Add a new calendar entry to the given calendar. Calendar data is
+ * specified as ICS file, thus it needs to be parsed first.
+ *
+ * This is mainly needed for the sync support.
+ *
+ * @param int $calid The calendar's ID
+ * @param string $uri The new object URI
+ * @param string $ics The ICS file
+ *
+ * @return mixed The etag.
+ */
+ public function addCalendarEntryToCalendarByICS($calid, $uri, $ics)
+ {
+ $extraData = $this->getDenormalizedData($ics);
+
+ $query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES (?,?,?,?,?,?,?,?,?,?)";
+ $res = $this->sqlite->query($query,
+ $calid,
+ $uri,
+ $ics,
+ time(),
+ $extraData['etag'],
+ $extraData['size'],
+ $extraData['componentType'],
+ $extraData['firstOccurence'],
+ $extraData['lastOccurence'],
+ $extraData['uid']);
+ // If successfully, update the sync token database
+ if($res !== false)
+ {
+ $this->updateSyncTokenLog($calid, $uri, 'added');
+ }
+ return $extraData['etag'];
+ }
+
+ /**
+ * Edit a calendar entry by providing a new ICS file. This is mainly
+ * needed for the sync support.
+ *
+ * @param int $calid The calendar's IS
+ * @param string $uri The object's URI to modify
+ * @param string $ics The new object's ICS file
+ */
+ public function editCalendarEntryToCalendarByICS($calid, $uri, $ics)
+ {
+ $extraData = $this->getDenormalizedData($ics);
+
+ $query = "UPDATE calendarobjects SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ?, uid = ? WHERE calendarid = ? AND uri = ?";
+ $res = $this->sqlite->query($query,
+ $ics,
+ time(),
+ $extraData['etag'],
+ $extraData['size'],
+ $extraData['componentType'],
+ $extraData['firstOccurence'],
+ $extraData['lastOccurence'],
+ $extraData['uid'],
+ $calid,
+ $uri
+ );
+ if($res !== false)
+ {
+ $this->updateSyncTokenLog($calid, $uri, 'modified');
+ }
+ return $extraData['etag'];
+ }
+
+ /**
* Add a new iCal entry for a given page, i.e. a given calendar.
*
* The parameter array needs to contain
* detectedtz => The timezone as detected by the browser
* currenttz => The timezone in use by the calendar
* eventfrom => The event's start date
* eventfromtime => The event's start time
* eventto => The event's end date
* eventtotime => The event's end time
* eventname => The event's name
* eventdescription => The event's description
*
* @param string $id The page ID to work on
* @param string $user The user who created the calendar
* @param string $params A parameter array with values to create
*
* @return boolean True on success, otherwise false
*/
public function addCalendarEntryToCalendarForPage($id, $user, $params)
{
if($params['currenttz'] !== '' && $params['currenttz'] !== 'local')
$timezone = new \DateTimeZone($params['currenttz']);
elseif($params['currenttz'] === 'local')
$timezone = new \DateTimeZone($params['detectedtz']);
else
$timezone = new \DateTimeZone('UTC');
// Retrieve dates from settings
$startDate = explode('-', $params['eventfrom']);
$startTime = explode(':', $params['eventfromtime']);
$endDate = explode('-', $params['eventto']);
$endTime = explode(':', $params['eventtotime']);
// Load SabreDAV
require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
$vcalendar = new \Sabre\VObject\Component\VCalendar();
// Add VCalendar, UID and Event Name
$event = $vcalendar->add('VEVENT');
$uuid = \Sabre\VObject\UUIDUtil::getUUID();
$event->add('UID', $uuid);
$event->summary = $params['eventname'];
// Add a description if requested
$description = $params['eventdescription'];
if($description !== '')
$event->add('DESCRIPTION', $description);
// Add attachments
$attachments = $params['attachments'];
if(!is_null($attachments))
foreach($attachments as $attachment)
$event->add('ATTACH', $attachment);
// Create a timestamp for last modified, created and dtstamp values in UTC
$dtStamp = new \DateTime(null, new \DateTimeZone('UTC'));
$event->add('DTSTAMP', $dtStamp);
$event->add('CREATED', $dtStamp);
$event->add('LAST-MODIFIED', $dtStamp);
// Adjust the start date, based on the given timezone information
$dtStart = new \DateTime();
$dtStart->setTimezone($timezone);
$dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2]));
// Only add the time values if it's not an allday event
if($params['allday'] != '1')
$dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0);
// Adjust the end date, based on the given timezone information
$dtEnd = new \DateTime();
$dtEnd->setTimezone($timezone);
$dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2]));
// Only add the time values if it's not an allday event
if($params['allday'] != '1')
$dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0);
// According to the VCal spec, we need to add a whole day here
if($params['allday'] == '1')
$dtEnd->add(new \DateInterval('P1D'));
// Really add Start and End events
$dtStartEv = $event->add('DTSTART', $dtStart);
$dtEndEv = $event->add('DTEND', $dtEnd);
// Adjust the DATE format for allday events
if($params['allday'] == '1')
{
$dtStartEv['VALUE'] = 'DATE';
$dtEndEv['VALUE'] = 'DATE';
}
// Actually add the values to the database
$calid = $this->getCalendarIdForPage($id);
$uri = uniqid('dokuwiki-').'.ics';
$now = new DateTime();
$eventStr = $vcalendar->serialize();
$query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, componenttype, firstoccurence, lastoccurence, size, etag, uid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$res = $this->sqlite->query($query, $calid, $uri, $eventStr, $now->getTimestamp(), 'VEVENT',
$event->DTSTART->getDateTime()->getTimeStamp(), $event->DTEND->getDateTime()->getTimeStamp(),
strlen($eventStr), md5($eventStr), $uuid);
// If successfully, update the sync token database
if($res !== false)
{
$this->updateSyncTokenLog($calid, $uri, 'added');
return true;
}
return false;
}
/**
* Retrieve the calendar settings of a given calendar id
*
* @param string $calid The calendar ID
*
* @return array The calendar settings array
*/
public function getCalendarSettings($calid)
{
- $query = "SELECT principaluri, calendarcolor, displayname, uri, description, components, transparent, synctoken FROM calendars WHERE id= ? ";
+ $query = "SELECT id, principaluri, calendarcolor, displayname, uri, description, components, transparent, synctoken FROM calendars WHERE id= ? ";
$res = $this->sqlite->query($query, $calid);
$row = $this->sqlite->res2row($res);
return $row;
}
/**
* Retrieve all events that are within a given date range,
* based on the timezone setting.
*
* There is also support for retrieving recurring events,
* using Sabre's VObject Iterator. Recurring events are represented
* as individual calendar entries with the same UID.
*
* @param string $id The page ID to work with
* @param string $user The user ID to work with
* @param string $startDate The start date as a string
* @param string $endDate The end date as a string
* @param string $color (optional) The calendar's color
*
* @return array An array containing the calendar entries.
*/
public function getEventsWithinDateRange($id, $user, $startDate, $endDate, $timezone, $color = null)
{
if($timezone !== '' && $timezone !== 'local')
$timezone = new \DateTimeZone($timezone);
else
$timezone = new \DateTimeZone('UTC');
$data = array();
// Load SabreDAV
require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
$calid = $this->getCalendarIdForPage($id);
if(is_null($color))
$color = $this->getCalendarColorForCalendar($calid);
$query = "SELECT calendardata, componenttype, uid FROM calendarobjects WHERE calendarid = ?";
$startTs = null;
$endTs = null;
if($startDate !== null)
{
$startTs = new \DateTime($startDate);
$query .= " AND lastoccurence > ".$this->sqlite->quote_string($startTs->getTimestamp());
}
if($endDate !== null)
{
$endTs = new \DateTime($endDate);
$query .= " AND firstoccurence < ".$this->sqlite->quote_string($endTs->getTimestamp());
}
// Retrieve matching calendar objects
$res = $this->sqlite->query($query, $calid);
$arr = $this->sqlite->res2arr($res);
// Parse individual calendar entries
foreach($arr as $row)
{
if(isset($row['calendardata']))
{
$entry = array();
$vcal = \Sabre\VObject\Reader::read($row['calendardata']);
$recurrence = $vcal->VEVENT->RRULE;
// If it is a recurring event, pass it through Sabre's EventIterator
if($recurrence != null)
{
$rEvents = new \Sabre\VObject\Recur\EventIterator(array($vcal->VEVENT));
$rEvents->rewind();
while($rEvents->valid())
{
$event = $rEvents->getEventObject();
// If we are after the given time range, exit
if(($endTs !== null) && ($rEvents->getDtStart()->getTimestamp() > $endTs->getTimestamp()))
break;
// If we are before the given time range, continue
if(($startTs != null) && ($rEvents->getDtEnd()->getTimestamp() < $startTs->getTimestamp()))
{
$rEvents->next();
continue;
}
// If we are within the given time range, parse the event
$data[] = $this->convertIcalDataToEntry($event, $id, $timezone, $row['uid'], $color, true);
$rEvents->next();
}
}
else
$data[] = $this->convertIcalDataToEntry($vcal->VEVENT, $id, $timezone, $row['uid'], $color);
}
}
return $data;
}
/**
* Helper function that parses the iCal data of a VEVENT to a calendar entry.
*
* @param \Sabre\VObject\VEvent $event The event to parse
* @param \DateTimeZone $timezone The timezone object
* @param string $uid The entry's UID
* @param boolean $recurring (optional) Set to true to define a recurring event
*
* @return array The parse calendar entry
*/
private function convertIcalDataToEntry($event, $page, $timezone, $uid, $color, $recurring = false)
{
$entry = array();
$start = $event->DTSTART;
// Parse only if the start date/time is present
if($start !== null)
{
$dtStart = $start->getDateTime();
$dtStart->setTimezone($timezone);
// moment.js doesn't like times be given even if
// allDay is set to true
// This should fix T23
if($start['VALUE'] == 'DATE')
{
$entry['allDay'] = true;
$entry['start'] = $dtStart->format("Y-m-d");
}
else
{
$entry['allDay'] = false;
$entry['start'] = $dtStart->format(\DateTime::ATOM);
}
}
$end = $event->DTEND;
// Parse only if the end date/time is present
if($end !== null)
{
$dtEnd = $end->getDateTime();
$dtEnd->setTimezone($timezone);
if($end['VALUE'] == 'DATE')
$entry['end'] = $dtEnd->format("Y-m-d");
else
$entry['end'] = $dtEnd->format(\DateTime::ATOM);
}
$description = $event->DESCRIPTION;
if($description !== null)
$entry['description'] = (string)$description;
else
$entry['description'] = '';
$attachments = $event->ATTACH;
if($attachments !== null)
{
$entry['attachments'] = array();
foreach($attachments as $attachment)
$entry['attachments'][] = (string)$attachment;
}
$entry['title'] = (string)$event->summary;
$entry['id'] = $uid;
$entry['page'] = $page;
$entry['color'] = $color;
$entry['recurring'] = $recurring;
return $entry;
}
/**
* Retrieve an event by its UID
*
* @param string $uid The event's UID
*
* @return mixed The table row with the given event
*/
public function getEventWithUid($uid)
{
$query = "SELECT calendardata, calendarid, componenttype, uri FROM calendarobjects WHERE uid = ?";
$res = $this->sqlite->query($query, $uid);
$row = $this->sqlite->res2row($res);
return $row;
}
/**
+ * Retrieve information of a calendar's object, not including the actual
+ * calendar data! This is mainly neede for the sync support.
+ *
+ * @param int $calid The calendar ID
+ *
+ * @return mixed The result
+ */
+ public function getCalendarObjects($calid)
+ {
+ $query = "SELECT id, uri, lastmodified, etag, calendarid, size, componenttype FROM calendarobjects WHERE calendarid = ?";
+ $res = $this->sqlite->query($query, $calid);
+ $arr = $this->sqlite->res2arr($res);
+ return $arr;
+ }
+
+ /**
+ * Retrieve a single calendar object by calendar ID and URI
+ *
+ * @param int $calid The calendar's ID
+ * @param string $uri The object's URI
+ *
+ * @return mixed The result
+ */
+ public function getCalendarObjectByUri($calid, $uri)
+ {
+ $query = "SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM calendarobjects WHERE calendarid = ? AND uri = ?";
+ $res = $this->sqlite->query($query, $calid, $uri);
+ $row = $this->sqlite->res2row($res);
+ return $row;
+ }
+
+ /**
+ * Retrieve several calendar objects by specifying an array of URIs.
+ * This is mainly neede for sync.
+ *
+ * @param int $calid The calendar's ID
+ * @param array $uris An array of URIs
+ *
+ * @return mixed The result
+ */
+ public function getMultipleCalendarObjectsByUri($calid, $uris)
+ {
+ $query = "SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM calendarobjects WHERE calendarid = ? AND uri IN (";
+ // Inserting a whole bunch of question marks
+ $query .= implode(',', array_fill(0, count($uris), '?'));
+ $query .= ')';
+ $vals = array_merge(array($calid), $uris);
+
+ $res = $this->sqlite->query($query, $vals);
+ $arr = $this->sqlite->res2arr($res);
+ return $arr;
+ }
+
+ /**
* Retrieve all calendar events for a given calendar ID
*
* @param string $calid The calendar's ID
*
* @return array An array containing all calendar data
*/
public function getAllCalendarEvents($calid)
{
$query = "SELECT calendardata, uid, componenttype, uri FROM calendarobjects WHERE calendarid = ?";
$res = $this->sqlite->query($query, $calid);
$arr = $this->sqlite->res2arr($res);
return $arr;
}
/**
* Edit a calendar entry for a page, given by its parameters.
* The params array has the same format as @see addCalendarEntryForPage
*
* @param string $id The page's ID to work on
* @param string $user The user's ID to work on
* @param array $params The parameter array for the edited calendar event
*
* @return boolean True on success, otherwise false
*/
public function editCalendarEntryForPage($id, $user, $params)
{
if($params['currenttz'] !== '' && $params['currenttz'] !== 'local')
$timezone = new \DateTimeZone($params['currenttz']);
elseif($params['currenttz'] === 'local')
$timezone = new \DateTimeZone($params['detectedtz']);
else
$timezone = new \DateTimeZone('UTC');
// Parse dates
$startDate = explode('-', $params['eventfrom']);
$startTime = explode(':', $params['eventfromtime']);
$endDate = explode('-', $params['eventto']);
$endTime = explode(':', $params['eventtotime']);
// Retrieve the existing event based on the UID
$uid = $params['uid'];
$event = $this->getEventWithUid($uid);
// Load SabreDAV
require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
if(!isset($event['calendardata']))
return false;
$uri = $event['uri'];
$calid = $event['calendarid'];
// Parse the existing event
$vcal = \Sabre\VObject\Reader::read($event['calendardata']);
$vevent = $vcal->VEVENT;
// Set the new event values
$vevent->summary = $params['eventname'];
$dtStamp = new \DateTime(null, new \DateTimeZone('UTC'));
$description = $params['eventdescription'];
// Remove existing timestamps to overwrite them
$vevent->remove('DESCRIPTION');
$vevent->remove('DTSTAMP');
$vevent->remove('LAST-MODIFIED');
$vevent->remove('ATTACH');
// Add new time stamps and description
$vevent->add('DTSTAMP', $dtStamp);
$vevent->add('LAST-MODIFIED', $dtStamp);
if($description !== '')
$vevent->add('DESCRIPTION', $description);
// Add attachments
$attachments = $params['attachments'];
if(!is_null($attachments))
foreach($attachments as $attachment)
$vevent->add('ATTACH', $attachment);
// Setup DTSTART
$dtStart = new \DateTime();
$dtStart->setTimezone($timezone);
$dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2]));
if($params['allday'] != '1')
$dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0);
// Setup DTEND
$dtEnd = new \DateTime();
$dtEnd->setTimezone($timezone);
$dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2]));
if($params['allday'] != '1')
$dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0);
// According to the VCal spec, we need to add a whole day here
if($params['allday'] == '1')
$dtEnd->add(new \DateInterval('P1D'));
$vevent->remove('DTSTART');
$vevent->remove('DTEND');
$dtStartEv = $vevent->add('DTSTART', $dtStart);
$dtEndEv = $vevent->add('DTEND', $dtEnd);
// Remove the time for allday events
if($params['allday'] == '1')
{
$dtStartEv['VALUE'] = 'DATE';
$dtEndEv['VALUE'] = 'DATE';
}
$now = new DateTime();
$eventStr = $vcal->serialize();
// Actually write to the database
$query = "UPDATE calendarobjects SET calendardata = ?, lastmodified = ?, ".
"firstoccurence = ?, lastoccurence = ?, size = ?, etag = ? WHERE uid = ?";
$res = $this->sqlite->query($query, $eventStr, $now->getTimestamp(), $dtStart->getTimestamp(),
$dtEnd->getTimestamp(), strlen($eventStr), md5($eventStr), $uid);
if($res !== false)
{
$this->updateSyncTokenLog($calid, $uri, 'modified');
return true;
}
return false;
}
/**
+ * Delete an event from a calendar by calendar ID and URI
+ *
+ * @param int $calid The calendar's ID
+ * @param string $uri The object's URI
+ *
+ * @return true
+ */
+ public function deleteCalendarEntryForCalendarByUri($calid, $uri)
+ {
+ $query = "DELETE FROM calendarobjects WHERE calendarid = ? AND uri = ?";
+ $res = $this->sqlite->query($query, $calid, $uri);
+ if($res !== false)
+ {
+ $this->updateSyncTokenLog($calid, $uri, 'deleted');
+ }
+ return true;
+ }
+
+ /**
* Delete a calendar entry for a given page. Actually, the event is removed
* based on the entry's UID, so that page ID is no used.
*
* @param string $id The page's ID (unused)
* @param array $params The parameter array to work with
*
* @return boolean True
*/
public function deleteCalendarEntryForPage($id, $params)
{
$uid = $params['uid'];
$event = $this->getEventWithUid($uid);
$calid = $event['calendarid'];
$uri = $event['uri'];
$query = "DELETE FROM calendarobjects WHERE uid = ?";
$res = $this->sqlite->query($query, $uid);
if($res !== false)
{
$this->updateSyncTokenLog($calid, $uri, 'deleted');
}
return true;
}
/**
* Retrieve the current sync token for a calendar
*
* @param string $calid The calendar id
*
* @return mixed The synctoken or false
*/
public function getSyncTokenForCalendar($calid)
{
$row = $this->getCalendarSettings($calid);
if(isset($row['synctoken']))
return $row['synctoken'];
return false;
}
/**
* Helper function to convert the operation name to
* an operation code as stored in the database
*
* @param string $operationName The operation name
*
* @return mixed The operation code or false
*/
public function operationNameToOperation($operationName)
{
switch($operationName)
{
case 'added':
return 1;
break;
case 'modified':
return 2;
break;
case 'deleted':
return 3;
break;
}
return false;
}
/**
* Update the sync token log based on the calendar id and the
* operation that was performed.
*
* @param string $calid The calendar ID that was modified
* @param string $uri The calendar URI that was modified
* @param string $operation The operation that was performed
*
* @return boolean True on success, otherwise false
*/
private function updateSyncTokenLog($calid, $uri, $operation)
{
$currentToken = $this->getSyncTokenForCalendar($calid);
$operationCode = $this->operationNameToOperation($operation);
if(($operationCode === false) || ($currentToken === false))
return false;
$values = array($uri,
$currentToken,
$calid,
$operationCode
);
$query = "INSERT INTO calendarchanges (uri, synctoken, calendarid, operation) VALUES(?, ?, ?, ?)";
$res = $this->sqlite->query($query, $uri, $currentToken, $calid, $operationCode);
if($res === false)
return false;
$currentToken++;
$query = "UPDATE calendars SET synctoken = ? WHERE id = ?";
$res = $this->sqlite->query($query, $currentToken, $calid);
return ($res !== false);
}
/**
* Return the sync URL for a given Page, i.e. a calendar
*
* @param string $id The page's ID
* @param string $user (optional) The user's ID
*
* @return mixed The sync url or false
*/
public function getSyncUrlForPage($id, $user = null)
{
if(is_null($userid))
{
if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
{
$userid = $_SERVER['REMOTE_USER'];
}
else
{
return false;
}
}
$calid = $this->getCalendarIdForPage($id);
if($calid === false)
return false;
$calsettings = $this->getCalendarSettings($calid);
if(!isset($calsettings['uri']))
return false;
$syncurl = DOKU_URL.'lib/plugins/davcal/calendarserver.php/calendars/'.$user.'/'.$calsettings['uri'];
return $syncurl;
}
/**
* Return the private calendar's URL for a given page
*
* @param string $id the page ID
*
* @return mixed The private URL or false
*/
public function getPrivateURLForPage($id)
{
$calid = $this->getCalendarIdForPage($id);
if($calid === false)
return false;
return $this->getPrivateURLForCalendar($calid);
}
/**
* Return the private calendar's URL for a given calendar ID
*
* @param string $calid The calendar's ID
*
* @return mixed The private URL or false
*/
public function getPrivateURLForCalendar($calid)
{
if(isset($this->cachedValues['privateurl'][$calid]))
return $this->cachedValues['privateurl'][$calid];
$query = "SELECT url FROM calendartoprivateurlmapping WHERE calid = ?";
$res = $this->sqlite->query($query, $calid);
$row = $this->sqlite->res2row($res);
if(!isset($row['url']))
{
$url = uniqid("dokuwiki-").".ics";
$query = "INSERT INTO calendartoprivateurlmapping (url, calid) VALUES(?, ?)";
$res = $this->sqlite->query($query, $url, $calid);
if($res === false)
return false;
}
else
{
$url = $row['url'];
}
$url = DOKU_URL.'lib/plugins/davcal/ics.php/'.$url;
$this->cachedValues['privateurl'][$calid] = $url;
return $url;
}
/**
* Retrieve the calendar ID for a given private calendar URL
*
* @param string $url The private URL
*
* @return mixed The calendar ID or false
*/
public function getCalendarForPrivateURL($url)
{
$query = "SELECT calid FROM calendartoprivateurlmapping WHERE url = ?";
$res = $this->sqlite->query($query, $url);
$row = $this->sqlite->res2row($res);
if(!isset($row['calid']))
return false;
return $row['calid'];
}
/**
* Return a given calendar as ICS feed, i.e. all events in one ICS file.
*
* @param string $calid The calendar ID to retrieve
*
* @return mixed The calendar events as string or false
*/
public function getCalendarAsICSFeed($calid)
{
$calSettings = $this->getCalendarSettings($calid);
if($calSettings === false)
return false;
$events = $this->getAllCalendarEvents($calid);
if($events === false)
return false;
// Load SabreDAV
require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
$out = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//DAVCal//DAVCal for DokuWiki//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-CALNAME:";
$out .= $calSettings['displayname']."\r\n";
foreach($events as $event)
{
$vcal = \Sabre\VObject\Reader::read($event['calendardata']);
$evt = $vcal->VEVENT;
$out .= $evt->serialize();
}
$out .= "END:VCALENDAR\r\n";
return $out;
}
/**
* Retrieve a configuration option for the plugin
*
* @param string $key The key to query
* @return mixed The option set, null if not found
*/
public function getConfig($key)
{
return $this->getConf($key);
}
+ /**
+ * Parses some information from calendar objects, used for optimized
+ * calendar-queries. Taken nearly unmodified from Sabre's PDO backend
+ *
+ * Returns an array with the following keys:
+ * * etag - An md5 checksum of the object without the quotes.
+ * * size - Size of the object in bytes
+ * * componentType - VEVENT, VTODO or VJOURNAL
+ * * firstOccurence
+ * * lastOccurence
+ * * uid - value of the UID property
+ *
+ * @param string $calendarData
+ * @return array
+ */
+ protected function getDenormalizedData($calendarData)
+ {
+ require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
+
+ $vObject = \Sabre\VObject\Reader::read($calendarData);
+ $componentType = null;
+ $component = null;
+ $firstOccurence = null;
+ $lastOccurence = null;
+ $uid = null;
+ foreach ($vObject->getComponents() as $component)
+ {
+ if ($component->name !== 'VTIMEZONE')
+ {
+ $componentType = $component->name;
+ $uid = (string)$component->UID;
+ break;
+ }
+ }
+ if (!$componentType)
+ {
+ return false;
+ }
+ if ($componentType === 'VEVENT')
+ {
+ $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp();
+ // Finding the last occurence is a bit harder
+ if (!isset($component->RRULE))
+ {
+ if (isset($component->DTEND))
+ {
+ $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
+ }
+ elseif (isset($component->DURATION))
+ {
+ $endDate = clone $component->DTSTART->getDateTime();
+ $endDate->add(\Sabre\VObject\DateTimeParser::parse($component->DURATION->getValue()));
+ $lastOccurence = $endDate->getTimeStamp();
+ }
+ elseif (!$component->DTSTART->hasTime())
+ {
+ $endDate = clone $component->DTSTART->getDateTime();
+ $endDate->modify('+1 day');
+ $lastOccurence = $endDate->getTimeStamp();
+ }
+ else
+ {
+ $lastOccurence = $firstOccurence;
+ }
+ }
+ else
+ {
+ $it = new \Sabre\VObject\Recur\EventIterator($vObject, (string)$component->UID);
+ $maxDate = new \DateTime('2038-01-01');
+ if ($it->isInfinite())
+ {
+ $lastOccurence = $maxDate->getTimeStamp();
+ }
+ else
+ {
+ $end = $it->getDtEnd();
+ while ($it->valid() && $end < $maxDate)
+ {
+ $end = $it->getDtEnd();
+ $it->next();
+ }
+ $lastOccurence = $end->getTimeStamp();
+ }
+ }
+ }
+
+ return array(
+ 'etag' => md5($calendarData),
+ 'size' => strlen($calendarData),
+ 'componentType' => $componentType,
+ 'firstOccurence' => $firstOccurence,
+ 'lastOccurence' => $lastOccurence,
+ 'uid' => $uid,
+ );
+
+ }
+
+ /**
+ * Query a calendar by ID and taking several filters into account.
+ * This is heavily based on Sabre's PDO backend.
+ *
+ * @param int $calendarId The calendar's ID
+ * @param array $filters The filter array to apply
+ *
+ * @return mixed The result
+ */
+ public function calendarQuery($calendarId, $filters)
+ {
+ $componentType = null;
+ $requirePostFilter = true;
+ $timeRange = null;
+
+ // if no filters were specified, we don't need to filter after a query
+ if (!$filters['prop-filters'] && !$filters['comp-filters'])
+ {
+ $requirePostFilter = false;
+ }
+
+ // Figuring out if there's a component filter
+ if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined'])
+ {
+ $componentType = $filters['comp-filters'][0]['name'];
+
+ // Checking if we need post-filters
+ if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters'])
+ {
+ $requirePostFilter = false;
+ }
+ // There was a time-range filter
+ if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range']))
+ {
+ $timeRange = $filters['comp-filters'][0]['time-range'];
+
+ // If start time OR the end time is not specified, we can do a
+ // 100% accurate mysql query.
+ if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end']))
+ {
+ $requirePostFilter = false;
+ }
+ }
+
+ }
+
+ if ($requirePostFilter)
+ {
+ $query = "SELECT uri, calendardata FROM calendarobjects WHERE calendarid = ?";
+ }
+ else
+ {
+ $query = "SELECT uri FROM calendarobjects WHERE calendarid = ?";
+ }
+
+ $values = array(
+ $calendarId
+ );
+
+ if ($componentType)
+ {
+ $query .= " AND componenttype = ?";
+ $values[] = $componentType;
+ }
+
+ if ($timeRange && $timeRange['start'])
+ {
+ $query .= " AND lastoccurence > ?";
+ $values[] = $timeRange['start']->getTimeStamp();
+ }
+ if ($timeRange && $timeRange['end'])
+ {
+ $query .= " AND firstoccurence < ?";
+ $values[] = $timeRange['end']->getTimeStamp();
+ }
+
+ $res = $this->sqlite->query($query, $values);
+ $arr = $this->sqlite->res2arr($res);
+
+ $result = array();
+ foreach($arr as $row)
+ {
+ if ($requirePostFilter)
+ {
+ if (!$this->validateFilterForObject($row, $filters))
+ {
+ continue;
+ }
+ }
+ $result[] = $row['uri'];
+
+ }
+
+ return $result;
+ }
+
+ /**
+ * This method validates if a filter (as passed to calendarQuery) matches
+ * the given object. Taken from Sabre's PDO backend
+ *
+ * @param array $object
+ * @param array $filters
+ * @return bool
+ */
+ protected function validateFilterForObject($object, $filters)
+ {
+ require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
+ // Unfortunately, setting the 'calendardata' here is optional. If
+ // it was excluded, we actually need another call to get this as
+ // well.
+ if (!isset($object['calendardata']))
+ {
+ $object = $this->getCalendarObjectByUri($object['calendarid'], $object['uri']);
+ }
+
+ $vObject = \Sabre\VObject\Reader::read($object['calendardata']);
+ $validator = new \Sabre\CalDAV\CalendarQueryValidator();
+
+ return $validator->validate($vObject, $filters);
+
+ }
+
+ /**
+ * Retrieve changes for a given calendar based on the given syncToken.
+ *
+ * @param int $calid The calendar's ID
+ * @param int $syncToken The supplied sync token
+ * @param int $syncLevel The sync level
+ * @param int $limit The limit of changes
+ *
+ * @return array The result
+ */
+ public function getChangesForCalendar($calid, $syncToken, $syncLevel, $limit = null)
+ {
+ // Current synctoken
+ $currentToken = $this->getSyncTokenForCalendar($calid);
+
+ if ($currentToken === false) return null;
+
+ $result = array(
+ 'syncToken' => $currentToken,
+ 'added' => array(),
+ 'modified' => array(),
+ 'deleted' => array(),
+ );
+
+ if ($syncToken)
+ {
+
+ $query = "SELECT uri, operation FROM calendarchanges WHERE synctoken >= ? AND synctoken < ? AND calendarid = ? ORDER BY synctoken";
+ if ($limit > 0) $query .= " LIMIT " . (int)$limit;
+
+ // Fetching all changes
+ $res = $this->sqlite->query($query, $syncToken, $currentToken, $calid);
+ if($res === false)
+ return null;
+
+ $arr = $this->sqlite->res2arr($res);
+ $changes = array();
+
+ // This loop ensures that any duplicates are overwritten, only the
+ // last change on a node is relevant.
+ foreach($arr as $row)
+ {
+ $changes[$row['uri']] = $row['operation'];
+ }
+
+ foreach ($changes as $uri => $operation)
+ {
+ switch ($operation)
+ {
+ case 1 :
+ $result['added'][] = $uri;
+ break;
+ case 2 :
+ $result['modified'][] = $uri;
+ break;
+ case 3 :
+ $result['deleted'][] = $uri;
+ break;
+ }
+
+ }
+ }
+ else
+ {
+ // No synctoken supplied, this is the initial sync.
+ $query = "SELECT uri FROM calendarobjects WHERE calendarid = ?";
+ $res = $this->sqlite->query($query);
+ $arr = $this->sqlite->res2arr($res);
+
+ $result['added'] = $arr;
+ }
+ return $result;
+ }
+
}
diff --git a/plugin.info.txt b/plugin.info.txt
--- a/plugin.info.txt
+++ b/plugin.info.txt
@@ -1,7 +1,7 @@
base davcal
author Andreas Boehler
email dev@aboehler.at
-date 2016-05-06
+date 2016-05-11
name Calendar PlugIn with CalDAV sharing support
desc Create one calendar per page and share/subscribe via CalDAV
url http://www.dokuwiki.org/plugin:davcal

File Metadata

Mime Type
text/x-diff
Expires
Wed, Dec 25, 5:56 AM (3 d, 4 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
540420
Default Alt Text
(107 KB)

Event Timeline