Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F1817463
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Size
102 KB
Subscribers
None
View Options
diff --git a/authBackendDokuwiki.php b/authBackendDokuwiki.php
--- a/authBackendDokuwiki.php
+++ b/authBackendDokuwiki.php
@@ -1,23 +1,20 @@
<?php
/**
* DokuWiki SabreDAV Auth Backend
*
* Check a user ID / password combo against DokuWiki's auth system
*/
class DokuWikiSabreAuthBackend extends Sabre\DAV\Auth\Backend\AbstractBasic
{
protected function validateUserPass($username, $password)
{
global $auth;
global $conf;
$ret = $auth->checkPass($username, $password);
- if($conf['allowdebug'])
- {
- dbglog('---- DAVCAL authBackendDokuwiki.php init');
- dbglog('checkPass called for username '.$username.' with result '.$ret);
- }
+ dbglog('---- DAVCAL authBackendDokuwiki.php init');
+ dbglog('checkPass called for username '.$username.' with result '.$ret);
return $ret;
}
}
diff --git a/calendarBackendDokuwiki.php b/calendarBackendDokuwiki.php
--- a/calendarBackendDokuwiki.php
+++ b/calendarBackendDokuwiki.php
@@ -1,708 +1,729 @@
<?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
{
/**
* DokuWiki PlugIn Helper
*/
protected $hlp = null;
/**
/**
* 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 = 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',
);
/**
* Creates the backend
*
* @param \PDO $pdo
*/
function __construct(&$hlp)
{
$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)
{
-
+ dbglog('calendarBackendDokuwiki::getCalendarsForUser: '.$principalUri);
$fields = array_values($this->propertyMap);
$fields[] = 'id';
$fields[] = 'uri';
$fields[] = 'synctoken';
$fields[] = 'components';
$fields[] = 'principaluri';
$fields[] = 'transparent';
$idInfo = $this->hlp->getCalendarIdsForUser($principalUri);
$calendars = array();
foreach($idInfo as $id => $data)
{
$row = $this->hlp->getCalendarSettings($id);
$components = array();
if ($row['components'])
{
$components = explode(',', $row['components']);
}
$calendar = array(
'id' => $row['id'],
'uri' => $row['uri'],
'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'),
);
if($idInfo[$row['id']]['readonly'] === true)
$calendar['{http://sabredav.org/ns}read-only'] = '1';
foreach ($this->propertyMap as $xmlName => $dbName)
{
$calendar[$xmlName] = $row[$dbName];
}
$calendars[] = $calendar;
}
-
+ dbglog($calendars);
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)
{
-
+ dbglog('calendarBackendDokuwiki::createCalendar called, returning false');
return false;
}
/**
* 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)
{
-
+ dbglog('calendarBackendDokuwiki::updateCalendar for calendarId '.$calendarId);
$supportedProperties = array_keys($this->propertyMap);
$propPatch->handle($supportedProperties, function($mutations) use ($calendarId)
{
foreach ($mutations as $propertyName => $propertyValue)
{
switch ($propertyName)
{
case '{DAV:}displayname' :
+ dbglog('updateCalendarName');
$this->hlp->updateCalendarName($calendarId, $propertyValue);
break;
case '{urn:ietf:params:xml:ns:caldav}calendar-description':
+ dbglog('updateCalendarDescription');
$this->hlp->updateCalendarDescription($calendarId, $propertyValue);
break;
case '{urn:ietf:params:xml:ns:caldav}calendar-timezone':
+ dbglog('updateCalendarTimezone');
$this->hlp->updateCalendarTimezone($calendarId, $propertyValue);
break;
default :
break;
}
}
return true;
});
}
/**
* Delete a calendar and all it's objects
*
* @param string $calendarId
* @return void
*/
function deleteCalendar($calendarId)
{
+ dbglog('calendarBackendDokuwiki::deleteCalendar called, returning false');
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)
{
-
+ dbglog('calendarBackendDokuwiki::getCalendarObjects for calendarId '.$calendarId);
$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']),
);
}
-
+ dbglog($result);
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)
{
-
+ dbglog('calendarBackendDokuwiki::getCalendarObject for calendarId '.$calendarId.' and objectUri '.$objectUri);
$row = $this->hlp->getCalendarObjectByUri($calendarId, $objectUri);
-
+ dbglog($row);
if (!$row)
return null;
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)
{
-
+ dbglog('calendarBackendDokuwiki::getMultipleCalendarObjects for calendarId '.$calendarId);
+ dbglog($uris);
$arr = $this->hlp->getMultipleCalendarObjectsByUri($calendarId, $uris);
$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'],
'calendardata' => $row['calendardata'],
'component' => strtolower($row['componenttype']),
);
}
+ dbglog($result);
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)
{
-
+ dbglog('calendarBackendDokuwiki::createCalendarObject for calendarId '.$calendarId.' and objectUri '.$objectUri);
+ dbglog($calendarData);
$etag = $this->hlp->addCalendarEntryToCalendarByICS($calendarId, $objectUri, $calendarData);
+ dbglog($etag);
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)
{
-
+ dbglog('calendarBackendDokuwiki::updateCalendarObject for calendarId '.$calendarId.' and objectUri '.$objectUri);
+ dbglog($calendarData);
$etag = $this->hlp->editCalendarEntryToCalendarByICS($calendarId, $objectUri, $calendarData);
+ dbglog($etag);
return '"' . $etag. '"';
}
/**
* 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)
{
+ dbglog('calendarBackendDokuwiki::deleteCalendarObject for calendarId '.$calendarId.' and objectUri '.$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)
{
+ dbglog('calendarBackendDokuwiki::calendarQuery for calendarId '.$calendarId);
+ dbglog($filters);
$result = $this->hlp->calendarQuery($calendarId, $filters);
+ dbglog($result);
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)
{
+ dbglog('calendarBackendDokuwiki::getCalendarObjectByUID for principalUri '.$principalUri.' and uid '.$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)
{
+ dbglog('calendarBackendDokuwiki::getChangesForCalendar for calendarId '.$calendarId.' and syncToken '.$syncToken.' and syncLevel '.$syncLevel);
$result = $this->hlp->getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit);
+ dbglog($result);
return $result;
}
/**
* 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)
{
+ dbglog('calendarBackendDokuwiki::getSubscriptionsForUser with principalUri '.$principalUri.', returning empty array()');
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)
{
+ dbglog('calendarBackendDokuwiki::createSubscription for principalUri '.$principalUri.' and uri '.$uri.', returning null');
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)
{
+ dbglog('calendarBackendDokuwiki::updateSubscription with subscriptionId '.$subscriptionId.', returning false');
return;
}
/**
* Deletes a subscription
*
* @param mixed $subscriptionId
* @return void
*/
function deleteSubscription($subscriptionId)
{
-
+ dbglog('calendarBackendDokuwiki::deleteSubscription with subscriptionId '.$subscriptionId.', returning');
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)
{
-
+ dbglog('calendarBackendDokuwiki::getSchedulingObject with principalUri '.$principalUri.' and objectUri '.$objectUri.', returning null');
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)
{
+ dbglog('calendarBackendDokuwiki::getSchedulingObjects for principalUri '.$principalUri.', returning null');
return null;
}
/**
* Deletes a scheduling object
*
* @param string $principalUri
* @param string $objectUri
* @return void
*/
function deleteSchedulingObject($principalUri, $objectUri)
{
-
+ dbglog('calendarBackendDokuwiki::deleteSchedulingObject for principalUri '.$principalUri.' and objectUri '.$objectUri.', returning');
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)
{
-
+ dbglog('calendarBackendDokuwiki::createSchedulingObject with principalUri '.$principalUri.' and objectUri '.$objectUri.', returning');
return;
}
}
diff --git a/calendarserver.php b/calendarserver.php
--- a/calendarserver.php
+++ b/calendarserver.php
@@ -1,100 +1,95 @@
<?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');
+dbglog('---- DAVCAL calendarserver.php init');
$hlp = null;
$hlp =& plugin_load('helper', 'davcal');
if(is_null($hlp))
{
- if($conf['allowdebug'])
- dbglog('Error loading helper plugin');
+ dbglog('Error loading helper plugin');
die('Error loading helper plugin');
}
$baseUri = DOKU_BASE.'lib/plugins/davcal/'.basename(__FILE__).'/';
if($hlp->getConfig('disable_sync') === 1)
{
- if($conf['allowdebug'])
- dbglog('Synchronisation is disabled');
+ dbglog('Synchronisation is disabled');
die('Synchronisation is disabled');
}
//Mapping PHP errors to exceptions
function exception_error_handler($errno, $errstr, $errfile, $errline) {
- if($conf['allowdebug'])
- dbglog('Exception occured: '.$errstr);
+ 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($hlp);
$principalBackend = new DokuWikiSabrePrincipalBackend();
// Directory structure
$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()');
+dbglog('$server->exec()');
// And off we go!
$server->exec();
diff --git a/helper.php b/helper.php
--- a/helper.php
+++ b/helper.php
@@ -1,1763 +1,1771 @@
<?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');
+ dbglog('---- DAVCAL helper.php init');
if(!$this->sqlite)
{
- if($conf['allowdebug'])
- dbglog('This plugin requires the sqlite plugin. Please install it.');
+ 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');
+ 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();
}
/**
* Check the permission of a user for a given calendar ID
*
* @param string $id The calendar ID to check
* @return int AUTH_* constants
*/
public function checkCalendarPermission($id)
{
if(strpos($page, 'webdav://') === 0)
{
$wdc =& plugin_load('helper', 'webdavclient');
if(is_null($wdc))
return AUTH_NONE;
$connectionId = str_replace('webdav://', '', $page);
$settings = $wdc->getConnection($connectionId);
if($settings === false)
return AUTH_NONE;
if($settings['write'] === '1')
return AUTH_CREATE;
return AUTH_READ;
}
else
{
$calid = $this->getCalendarIdForPage($id);
// We return AUTH_READ if the calendar does not exist. This makes
// davcal happy when there are just included calendars
if($calid === false)
return AUTH_READ;
return auth_quickaclcheck($id);
}
}
/**
* 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)
{
// WebDAV Connections are always readable
if(strpos($page, 'webdav://') === 0)
{
$retList[$page] = $data;
}
elseif(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)
{
if(strpos($page, 'webdav://') === 0)
{
$wdc =& plugin_load('helper', 'webdavclient');
if(is_null($wdc))
continue;
$connectionId = str_replace('webdav://', '', $page);
$settings = $wdc->getConnection($connectionId);
if($settings === false)
continue;
$name = $settings['displayname'];
$write = ($settings['write'] === '1');
$calid = $connectionId;
$color = '#3a87ad';
}
else
{
$calid = $this->getCalendarIdForPage($page);
if($calid !== false)
{
$settings = $this->getCalendarSettings($calid);
$name = $settings['displayname'];
$color = $settings['calendarcolor'];
$write = (auth_quickaclcheck($page) > AUTH_READ);
}
else
{
continue;
}
}
$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';
}
$eventStr = $vcalendar->serialize();
if(strpos($id, 'webdav://') === 0)
{
$wdc =& plugin_load('helper', 'webdavclient');
if(is_null($wdc))
return false;
$connectionId = str_replace('webdav://', '', $id);
return $wdc->addCalendarEntry($connectionId, $eventStr);
}
else
{
// Actually add the values to the database
$calid = $this->getCalendarIdForPage($id);
$uri = uniqid('dokuwiki-').'.ics';
$now = new \DateTime();
$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 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();
$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());
}
// Load SabreDAV
require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
if(strpos($id, 'webdav://') === 0)
{
$wdc =& plugin_load('helper', 'webdavclient');
if(is_null($wdc))
return $data;
$connectionId = str_replace('webdav://', '', $id);
$arr = $wdc->getCalendarEntries($connectionId, $startDate, $endDate);
}
else
{
$calid = $this->getCalendarIdForPage($id);
if(is_null($color))
$color = $this->getCalendarColorForCalendar($calid);
// 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'];
if(strpos($id, 'webdav://') === 0)
{
$wdc =& plugin_load('helper', 'webdavclient');
if(is_null($wdc))
return false;
$event = $wdc->getCalendarEntryByUid($uid);
}
else
{
$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';
}
$eventStr = $vcal->serialize();
if(strpos($id, 'webdav://') === 0)
{
$connectionId = str_replace('webdav://', '', $id);
return $wdc->editCalendarEntry($connectionId, $uid, $eventStr);
}
else
{
$now = new DateTime();
// 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'];
if(strpos($id, 'webdav://') === 0)
{
$wdc =& plugin_load('helper', 'webdavclient');
if(is_null($wdc))
return false;
$connectionId = str_replace('webdav://', '', $id);
$result = $wdc->deleteCalendarEntry($connectionId, $uid);
return $result;
}
$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)
{
+ dbglog('davcal::helper::calendarQuery');
$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();
}
+ dbglog($query);
$res = $this->sqlite->query($query, $values);
$arr = $this->sqlite->res2arr($res);
+ dbglog($arr);
$result = array();
foreach($arr as $row)
{
if ($requirePostFilter)
{
+ dbglog('requirePostFilter for');
+ dbglog($row);
+ dbglog($filters);
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']))
{
+ dbglog('fetching object...');
$object = $this->getCalendarObjectByUri($object['calendarid'], $object['uri']);
}
+ dbglog('object to validate: ');
+ dbglog($object);
$vObject = \Sabre\VObject\Reader::read($object['calendardata']);
$validator = new \Sabre\CalDAV\CalendarQueryValidator();
- return $validator->validate($vObject, $filters);
+ $res = $validator->validate($vObject, $filters);
+ dbglog($res);
+ return $res;
}
/**
* 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-07-08
+date 2016-08-31
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
diff --git a/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php b/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php
--- a/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php
+++ b/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php
@@ -1,375 +1,379 @@
<?php
namespace Sabre\CalDAV;
use Sabre\VObject;
use DateTime;
/**
* CalendarQuery Validator
*
* This class is responsible for checking if an iCalendar object matches a set
* of filters. The main function to do this is 'validate'.
*
* This is used to determine which icalendar objects should be returned for a
* calendar-query REPORT request.
*
* @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 CalendarQueryValidator {
/**
* Verify if a list of filters applies to the calendar data object
*
* The list of filters must be formatted as parsed by \Sabre\CalDAV\CalendarQueryParser
*
* @param VObject\Component $vObject
* @param array $filters
* @return bool
*/
function validate(VObject\Component\VCalendar $vObject, array $filters) {
// The top level object is always a component filter.
// We'll parse it manually, as it's pretty simple.
if ($vObject->name !== $filters['name']) {
return false;
}
return
$this->validateCompFilters($vObject, $filters['comp-filters']) &&
$this->validatePropFilters($vObject, $filters['prop-filters']);
}
/**
* This method checks the validity of comp-filters.
*
* A list of comp-filters needs to be specified. Also the parent of the
* component we're checking should be specified, not the component to check
* itself.
*
* @param VObject\Component $parent
* @param array $filters
* @return bool
*/
protected function validateCompFilters(VObject\Component $parent, array $filters) {
foreach ($filters as $filter) {
- $isDefined = isset($parent->$filter['name']);
+ $filterName = $filter['name'];
+
+ $isDefined = isset($parent->$filterName);
if ($filter['is-not-defined']) {
if ($isDefined) {
return false;
} else {
continue;
}
}
if (!$isDefined) {
return false;
}
if ($filter['time-range']) {
- foreach ($parent->$filter['name'] as $subComponent) {
+ foreach ($parent->$filterName as $subComponent) {
if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
continue 2;
}
}
return false;
}
if (!$filter['comp-filters'] && !$filter['prop-filters']) {
continue;
}
// If there are sub-filters, we need to find at least one component
// for which the subfilters hold true.
- foreach ($parent->$filter['name'] as $subComponent) {
+ foreach ($parent->$filterName as $subComponent) {
if (
$this->validateCompFilters($subComponent, $filter['comp-filters']) &&
$this->validatePropFilters($subComponent, $filter['prop-filters'])) {
// We had a match, so this comp-filter succeeds
continue 2;
}
}
// If we got here it means there were sub-comp-filters or
// sub-prop-filters and there was no match. This means this filter
// needs to return false.
return false;
}
// If we got here it means we got through all comp-filters alive so the
// filters were all true.
return true;
}
/**
* This method checks the validity of prop-filters.
*
* A list of prop-filters needs to be specified. Also the parent of the
* property we're checking should be specified, not the property to check
* itself.
*
* @param VObject\Component $parent
* @param array $filters
* @return bool
*/
protected function validatePropFilters(VObject\Component $parent, array $filters) {
foreach ($filters as $filter) {
- $isDefined = isset($parent->$filter['name']);
+ $filterName = $filter['name'];
+
+ $isDefined = isset($parent->$filterName);
if ($filter['is-not-defined']) {
if ($isDefined) {
return false;
} else {
continue;
}
}
if (!$isDefined) {
return false;
}
if ($filter['time-range']) {
- foreach ($parent->$filter['name'] as $subComponent) {
+ foreach ($parent->$filterName as $subComponent) {
if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
continue 2;
}
}
return false;
}
if (!$filter['param-filters'] && !$filter['text-match']) {
continue;
}
// If there are sub-filters, we need to find at least one property
// for which the subfilters hold true.
- foreach ($parent->$filter['name'] as $subComponent) {
+ foreach ($parent->$filterName as $subComponent) {
if (
$this->validateParamFilters($subComponent, $filter['param-filters']) &&
(!$filter['text-match'] || $this->validateTextMatch($subComponent, $filter['text-match']))
) {
// We had a match, so this prop-filter succeeds
continue 2;
}
}
// If we got here it means there were sub-param-filters or
// text-match filters and there was no match. This means the
// filter needs to return false.
return false;
}
// If we got here it means we got through all prop-filters alive so the
// filters were all true.
return true;
}
/**
* This method checks the validity of param-filters.
*
* A list of param-filters needs to be specified. Also the parent of the
* parameter we're checking should be specified, not the parameter to check
* itself.
*
* @param VObject\Property $parent
* @param array $filters
* @return bool
*/
protected function validateParamFilters(VObject\Property $parent, array $filters) {
foreach ($filters as $filter) {
$isDefined = isset($parent[$filter['name']]);
if ($filter['is-not-defined']) {
if ($isDefined) {
return false;
} else {
continue;
}
}
if (!$isDefined) {
return false;
}
if (!$filter['text-match']) {
continue;
}
// If there are sub-filters, we need to find at least one parameter
// for which the subfilters hold true.
foreach ($parent[$filter['name']]->getParts() as $paramPart) {
if ($this->validateTextMatch($paramPart, $filter['text-match'])) {
// We had a match, so this param-filter succeeds
continue 2;
}
}
// If we got here it means there was a text-match filter and there
// were no matches. This means the filter needs to return false.
return false;
}
// If we got here it means we got through all param-filters alive so the
// filters were all true.
return true;
}
/**
* This method checks the validity of a text-match.
*
* A single text-match should be specified as well as the specific property
* or parameter we need to validate.
*
* @param VObject\Node|string $check Value to check against.
* @param array $textMatch
* @return bool
*/
protected function validateTextMatch($check, array $textMatch) {
if ($check instanceof VObject\Node) {
$check = $check->getValue();
}
$isMatching = \Sabre\DAV\StringUtil::textMatch($check, $textMatch['value'], $textMatch['collation']);
return ($textMatch['negate-condition'] xor $isMatching);
}
/**
* Validates if a component matches the given time range.
*
* This is all based on the rules specified in rfc4791, which are quite
* complex.
*
* @param VObject\Node $component
* @param DateTime $start
* @param DateTime $end
* @return bool
*/
protected function validateTimeRange(VObject\Node $component, $start, $end) {
if (is_null($start)) {
$start = new DateTime('1900-01-01');
}
if (is_null($end)) {
$end = new DateTime('3000-01-01');
}
switch ($component->name) {
case 'VEVENT' :
case 'VTODO' :
case 'VJOURNAL' :
return $component->isInTimeRange($start, $end);
case 'VALARM' :
// If the valarm is wrapped in a recurring event, we need to
// expand the recursions, and validate each.
//
// Our datamodel doesn't easily allow us to do this straight
// in the VALARM component code, so this is a hack, and an
// expensive one too.
if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) {
// Fire up the iterator!
$it = new VObject\Recur\EventIterator($component->parent->parent, (string)$component->parent->UID);
while ($it->valid()) {
$expandedEvent = $it->getEventObject();
// We need to check from these expanded alarms, which
// one is the first to trigger. Based on this, we can
// determine if we can 'give up' expanding events.
$firstAlarm = null;
if ($expandedEvent->VALARM !== null) {
foreach ($expandedEvent->VALARM as $expandedAlarm) {
$effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime();
if ($expandedAlarm->isInTimeRange($start, $end)) {
return true;
}
if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') {
// This is an alarm with a non-relative trigger
// time, likely created by a buggy client. The
// implication is that every alarm in this
// recurring event trigger at the exact same
// time. It doesn't make sense to traverse
// further.
} else {
// We store the first alarm as a means to
// figure out when we can stop traversing.
if (!$firstAlarm || $effectiveTrigger < $firstAlarm) {
$firstAlarm = $effectiveTrigger;
}
}
}
}
if (is_null($firstAlarm)) {
// No alarm was found.
//
// Or technically: No alarm that will change for
// every instance of the recurrence was found,
// which means we can assume there was no match.
return false;
}
if ($firstAlarm > $end) {
return false;
}
$it->next();
}
return false;
} else {
return $component->isInTimeRange($start, $end);
}
case 'VFREEBUSY' :
throw new \Sabre\DAV\Exception\NotImplemented('time-range filters are currently not supported on ' . $component->name . ' components');
case 'COMPLETED' :
case 'CREATED' :
case 'DTEND' :
case 'DTSTAMP' :
case 'DTSTART' :
case 'DUE' :
case 'LAST-MODIFIED' :
return ($start <= $component->getDateTime() && $end >= $component->getDateTime());
default :
throw new \Sabre\DAV\Exception\BadRequest('You cannot create a time-range filter on a ' . $component->name . ' component');
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Dec 21, 3:16 AM (3 w, 1 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
530184
Default Alt Text
(102 KB)
Attached To
rDAVCAL DokuWiki DAVCal PlugIn
Event Timeline
Log In to Comment