summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Bruederli <bruederli@kolabsys.com>2013-03-14 16:48:10 (GMT)
committerThomas Bruederli <bruederli@kolabsys.com>2013-03-14 16:48:10 (GMT)
commit5515a4b398f4ccc2b6f32d810d4fb7eed47c3efa (patch)
tree48700a1f304ebe736d70d5a8889d619f4204579a
parent5ec426a349db1bf8720228b4df4d08388a59b8ab (diff)
downloadiRony-5515a4b398f4ccc2b6f32d810d4fb7eed47c3efa.tar.gz
- Use UIDs from IMAP annoations for collection IDs
- Implement CardDAV/AddressBook classes for proper ACL/sharing support - Enable Free-Busy queries to CalDAV
-rw-r--r--README.md8
-rw-r--r--lib/Kolab/CalDAV/Calendar.php5
-rw-r--r--lib/Kolab/CalDAV/CalendarBackend.php33
-rw-r--r--lib/Kolab/CalDAV/Plugin.php33
-rw-r--r--lib/Kolab/CalDAV/UserCalendars.php195
-rw-r--r--lib/Kolab/CardDAV/AddressBook.php152
-rw-r--r--lib/Kolab/CardDAV/AddressBookRoot.php99
-rw-r--r--lib/Kolab/CardDAV/ContactsBackend.php21
-rw-r--r--lib/Kolab/CardDAV/Plugin.php77
-rw-r--r--lib/Kolab/CardDAV/UserAddressBooks.php55
-rw-r--r--lib/Kolab/DAVACL/PrincipalBackend.php58
-rw-r--r--lib/Kolab/Utils/DAVBackend.php90
-rw-r--r--lib/Kolab/Utils/VObjectUtils.php21
-rw-r--r--public_html/.htaccess2
-rw-r--r--public_html/index.php6
15 files changed, 631 insertions, 224 deletions
diff --git a/README.md b/README.md
index 86d0c82..e115099 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
INTALLATION PROCEDURE
=====================
-This package uses [Composer][1] to install and maintain
-required PHP libraries as well as the Roundcube framework. The requirements
-are basically the same as for Roundcube so please read the INSTALLATION
-section in the Roundcube framework's [README][2] file.
+This package uses [Composer][1] to install and maintain required PHP libraries
+as well as the Roundcube framework. The requirements are basically the same as
+for Roundcube so please read the INSTALLATION section in the Roundcube
+framework's [README][2] file.
1. Install Composer
diff --git a/lib/Kolab/CalDAV/Calendar.php b/lib/Kolab/CalDAV/Calendar.php
index b86fd4e..96e73ef 100644
--- a/lib/Kolab/CalDAV/Calendar.php
+++ b/lib/Kolab/CalDAV/Calendar.php
@@ -39,9 +39,6 @@ class Calendar extends \Sabre\CalDAV\Calendar
public $storage;
public $ready = false;
- private $events = array();
- private $imap_folder = 'INBOX/Calendar';
-
/**
* Default constructor
@@ -51,8 +48,6 @@ class Calendar extends \Sabre\CalDAV\Calendar
parent::__construct($caldavBackend, $calendarInfo);
$this->id = $calendarInfo['id'];
- $this->imap_folder = urldecode($calendarInfo['id']);
-
$this->storage = $caldavBackend->get_storage_folder($this->id);
$this->ready = is_object($this->storage) && is_a($this->storage, 'kolab_storage_folder');
}
diff --git a/lib/Kolab/CalDAV/CalendarBackend.php b/lib/Kolab/CalDAV/CalendarBackend.php
index 7b0dee8..fe642bc 100644
--- a/lib/Kolab/CalDAV/CalendarBackend.php
+++ b/lib/Kolab/CalDAV/CalendarBackend.php
@@ -28,6 +28,7 @@ use \rcube;
use \rcube_charset;
use \kolab_storage;
use \libcalendaring;
+use Kolab\Utils\DAVBackend;
use Kolab\Utils\VObjectUtils;
use Sabre\CalDAV;
use Sabre\VObject;
@@ -67,7 +68,7 @@ class CalendarBackend extends CalDAV\Backend\AbstractBackend
asort($names, SORT_LOCALE_STRING);
foreach ($names as $utf7name => $name) {
- $id = urlencode($utf7name);
+ $id = DAVBackend::get_uid($folders[$utf7name]);
$folder = $this->folders[$id] = $folders[$utf7name];
$fdata = $folder->get_imap_data(); // fetch IMAP folder data for CTag generation
$this->calendars[$id] = array(
@@ -104,8 +105,7 @@ class CalendarBackend extends CalDAV\Backend\AbstractBackend
return $this->folders[$id];
}
else {
- $storage = kolab_storage::get_folder(urldecode($id));
- return !PEAR::isError($this->storage) ? $storage : null;
+ return DAVBackend::get_storage_folder($id, 'event');
}
}
@@ -169,7 +169,7 @@ class CalendarBackend extends CalDAV\Backend\AbstractBackend
console(__METHOD__, $calendarUri, $properties);
$props = array(
- 'name' => $calendarUri,
+ 'name' => 'Untitled',
'type' => 'event',
'subscribed' => true
);
@@ -177,7 +177,9 @@ class CalendarBackend extends CalDAV\Backend\AbstractBackend
foreach ($properties as $prop => $val) {
switch ($prop) {
case '{DAV:}displayname':
- // ignore for creating, URI is used
+ $parts = explode('/', $val);
+ $props['name'] = array_pop($parts);
+ $props['parent'] = join('/', $parts);
break;
case '{http://apple.com/ns/ical/}calendar-color':
@@ -189,7 +191,7 @@ class CalendarBackend extends CalDAV\Backend\AbstractBackend
}
}
- if (!empty($props['name']) && !kolab_storage::folder_update($props)) {
+ if (!empty($props['name']) && ($fname = kolab_storage::folder_update($props))) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
@@ -197,6 +199,11 @@ class CalendarBackend extends CalDAV\Backend\AbstractBackend
true, false);
return false;
}
+
+ // save UID in folder annotations
+ if ($folder = kolab_storage::get_folder($fname)) {
+ DAVBackend::set_uid($folder, $calendarUri);
+ }
}
/**
@@ -554,10 +561,18 @@ class CalendarBackend extends CalDAV\Backend\AbstractBackend
*/
public function calendarQuery($calendarId, array $filters)
{
- console(__METHOD__, $calendarId);
+ console(__METHOD__, $calendarId, $filters);
- // TODO: build kolab storage query from $filters
+ // build kolab storage query from $filters
$query = array();
+ foreach ((array)$filters['comp-filters'] as $filter) {
+ if ($filter['name'] != 'VEVENT')
+ continue;
+ if (is_array($filter['time-range'])) {
+ $query[] = array('dtstart', '<=', $filter['time-range']['end']);
+ $query[] = array('dtend', '>=', $filter['time-range']['start']);
+ }
+ }
$results = array();
if ($storage = $this->get_storage_folder($calendarId)) {
@@ -846,7 +861,7 @@ class CalendarBackend extends CalDAV\Backend\AbstractBackend
if ($event['location'])
$ve->add('LOCATION', $event['location']);
if ($event['description'])
- $ve->add('DESCRIPTION', $event['description']);
+ $ve->add('DESCRIPTION', strtr($event['description'], array("\r\n" => "\n", "\r" => "\n")));
if ($event['sequence'])
$ve->add('SEQUENCE', $event['sequence']);
diff --git a/lib/Kolab/CalDAV/Plugin.php b/lib/Kolab/CalDAV/Plugin.php
index 6041de2..70c0f0f 100644
--- a/lib/Kolab/CalDAV/Plugin.php
+++ b/lib/Kolab/CalDAV/Plugin.php
@@ -121,4 +121,37 @@ class Plugin extends CalDAV\Plugin
throw new DAV\Exception\BadRequest('iCalendar object must contain at least 1 of VEVENT, VTODO or VJOURNAL');
}
+ /**
+ * Returns free-busy information for a specific address. The returned
+ * data is an array containing the following properties:
+ *
+ * calendar-data : A VFREEBUSY VObject
+ * request-status : an iTip status code.
+ * href: The principal's email address, as requested
+ *
+ * @param string $email address
+ * @param \DateTime $start
+ * @param \DateTime $end
+ * @param VObject\Component $request
+ * @return array
+ */
+ protected function getFreeBusyForEmail($email, \DateTime $start, \DateTime $end, VObject\Component $request)
+ {
+ return parent::getFreeBusyForEmail($email, $start, $end, $request);
+
+ // TODO: pass-through the pre-generatd free/busy feed from Kolab's free/busy service
+
+ // not found:
+ return array(
+ 'request-status' => '3.7;Could not find principal',
+ 'href' => 'mailto:' . $email,
+ );
+
+ // success_
+ return array(
+ 'calendar-data' => $fbdata,
+ 'request-status' => '2.0;Success',
+ 'href' => 'mailto:' . $email,
+ );
+ }
} \ No newline at end of file
diff --git a/lib/Kolab/CalDAV/UserCalendars.php b/lib/Kolab/CalDAV/UserCalendars.php
index e7ca8d8..b00b1b6 100644
--- a/lib/Kolab/CalDAV/UserCalendars.php
+++ b/lib/Kolab/CalDAV/UserCalendars.php
@@ -26,6 +26,7 @@ namespace Kolab\CalDAV;
use Sabre\DAV;
use Sabre\DAVACL;
use Sabre\CalDAV\Backend;
+use Sabre\CalDAV\Schedule;
use Kolab\CalDAV\Calendar;
/**
@@ -35,102 +36,6 @@ use Kolab\CalDAV\Calendar;
class UserCalendars extends \Sabre\CalDAV\UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL
{
/**
- * CalDAV backend
- *
- * @var Sabre\CalDAV\Backend\BackendInterface
- */
- protected $caldavBackend;
-
- /**
- * Principal information
- *
- * @var array
- */
- protected $principalInfo;
-
- /**
- * Constructor
- *
- * @param Backend\BackendInterface $caldavBackend
- * @param mixed $userUri
- */
- public function __construct(\Sabre\CalDAV\Backend\BackendInterface $caldavBackend, $principalInfo)
- {
- $this->caldavBackend = $caldavBackend;
- $this->principalInfo = $principalInfo;
- }
-
- /**
- * Returns the name of this object
- *
- * @return string
- */
- public function getName()
- {
- list(,$name) = DAV\URLUtil::splitPath($this->principalInfo['uri']);
- return $name;
- }
-
- /**
- * Updates the name of this object
- *
- * @param string $name
- * @return void
- */
- public function setName($name)
- {
- // TODO: implement this
- throw new DAV\Exception\Forbidden();
- }
-
- /**
- * Deletes this object
- *
- * @return void
- */
- public function delete()
- {
- // TODO: implement this
- throw new DAV\Exception\Forbidden();
- }
-
- /**
- * Returns the last modification date
- *
- * @return int
- */
- public function getLastModified()
- {
- return null;
- }
-
- /**
- * Creates a new file under this object.
- *
- * This is currently not allowed
- *
- * @param string $filename
- * @param resource $data
- * @return void
- */
- public function createFile($filename, $data=null)
- {
- throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported');
- }
-
- /**
- * Creates a new directory under this object.
- *
- * @param string $filename
- * @return void
- */
- public function createDirectory($filename)
- {
- // TODO: implement this
- throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported');
- }
-
- /**
* Returns a list of calendars
*
* @return array
@@ -154,6 +59,9 @@ class UserCalendars extends \Sabre\CalDAV\UserCalendars implements DAV\IExtended
}
}
+ // add support for scheduling AKA free/busy
+ $objs[] = new Schedule\Outbox($this->principalInfo['uri']);
+
// TODO: add notification support (check with clients first, if anybody supports it)
if ($this->caldavBackend instanceof Backend\NotificationSupport) {
$objs[] = new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
@@ -163,61 +71,6 @@ class UserCalendars extends \Sabre\CalDAV\UserCalendars implements DAV\IExtended
}
/**
- * Creates a new calendar
- *
- * @param string $name
- * @param array $resourceType
- * @param array $properties
- * @return void
- */
- public function createExtendedCollection($name, array $resourceType, array $properties)
- {
- $isCalendar = false;
- foreach($resourceType as $rt) {
- switch ($rt) {
- case '{DAV:}collection' :
- case '{http://calendarserver.org/ns/}shared-owner' :
- // ignore
- break;
- case '{urn:ietf:params:xml:ns:caldav}calendar' :
- $isCalendar = true;
- break;
- default :
- throw new DAV\Exception\InvalidResourceType('Unknown resourceType: ' . $rt);
- }
- }
- if (!$isCalendar) {
- throw new DAV\Exception\InvalidResourceType('You can only create calendars in this collection');
- }
-
- $this->caldavBackend->createCalendar($this->principalInfo['uri'], $name, $properties);
- }
-
- /**
- * Returns the owner principal
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- public function getOwner()
- {
- return $this->principalInfo['uri'];
- }
-
- /**
- * Returns a group principal
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- public function getGroup()
- {
- return null;
- }
-
- /**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
@@ -275,44 +128,4 @@ class UserCalendars extends \Sabre\CalDAV\UserCalendars implements DAV\IExtended
throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
}
- /**
- * Returns the list of supported privileges for this node.
- *
- * The returned data structure is a list of nested privileges.
- * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
- * standard structure.
- *
- * If null is returned from this method, the default privilege set is used,
- * which is fine for most common usecases.
- *
- * @return array|null
- */
- public function getSupportedPrivilegeSet()
- {
- // TODO: implement this
- return null;
- }
-
- /**
- * This method is called when a user replied to a request to share.
- *
- * This method should return the url of the newly created calendar if the
- * share was accepted.
- *
- * @param string href The sharee who is replying (often a mailto: address)
- * @param int status One of the SharingPlugin::STATUS_* constants
- * @param string $calendarUri The url to the calendar thats being shared
- * @param string $inReplyTo The unique id this message is a response to
- * @param string $summary A description of the reply
- * @return null|string
- */
- public function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null)
- {
- if (!$this->caldavBackend instanceof Backend\SharingSupport) {
- throw new DAV\Exception\NotImplemented('Sharing support is not implemented by this backend.');
- }
-
- return $this->caldavBackend->shareReply($href, $status, $calendarUri, $inReplyTo, $summary);
- }
-
}
diff --git a/lib/Kolab/CardDAV/AddressBook.php b/lib/Kolab/CardDAV/AddressBook.php
new file mode 100644
index 0000000..e01ab12
--- /dev/null
+++ b/lib/Kolab/CardDAV/AddressBook.php
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * SabreDAV AddressBook derived class to encapsulate a Kolab storage folder
+ *
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace Kolab\CardDAV;
+
+use \PEAR;
+use Sabre\DAV;
+use Sabre\DAVACL;
+use Sabre\CardDAV\Backend;
+
+/**
+ * The AddressBook class represents a CardDAV addressbook, owned by a specific user
+ *
+ * The AddressBook can contain multiple vcards
+ *
+ * @copyright Copyright (C) 2007-2013 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class AddressBook extends \Sabre\CardDAV\AddressBook implements \Sabre\CardDAV\IAddressBook, DAV\IProperties, DAVACL\IACL
+{
+ public $id;
+ public $storage;
+ public $ready = false;
+
+
+ /**
+ * Constructor
+ *
+ * @param Backend\BackendInterface $carddavBackend
+ * @param array $addressBookInfo
+ */
+ public function __construct(Backend\BackendInterface $carddavBackend, array $addressBookInfo)
+ {
+ parent::__construct($carddavBackend, $addressBookInfo);
+
+ $this->id = $addressBookInfo['id'];
+ $this->storage = $carddavBackend->get_storage_folder($this->id);
+ $this->ready = is_object($this->storage) && is_a($this->storage, 'kolab_storage_folder');
+ }
+
+
+ /**
+ * Renames the addressbook
+ *
+ * @param string $newName
+ * @return void
+ */
+ public function setName($newName)
+ {
+ // TODO: implement this
+ throw new DAV\Exception\MethodNotAllowed('Renaming addressbooks is not yet supported');
+ }
+
+
+ /**
+ * Returns the owner principal
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ public function getOwner()
+ {
+ if ($this->storage->get_namespace() == 'personal') {
+ return $this->addressBookInfo['principaluri'];
+ }
+ else {
+ return 'principals/' . $this->storage->get_owner();
+ }
+ }
+
+ /**
+ * Returns a list of ACE's for this node.
+ *
+ * Each ACE has the following properties:
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
+ * currently the only supported privileges
+ * * 'principal', a url to the principal who owns the node
+ * * 'protected' (optional), indicating that this ACE is not allowed to
+ * be updated.
+ *
+ * @return array
+ */
+ public function getACL()
+ {
+ // return ACL information based on IMAP MYRIGHTS
+ $rights = $this->storage->get_myrights();
+ if ($rights && !PEAR::isError($rights)) {
+ // user has at least read access to calendar folders listed
+ $acl = array(
+ array(
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->addressBookInfo['principaluri'],
+ 'protected' => true,
+ ),
+ );
+
+ $owner = $this->getOwner();
+ $is_owner = $owner == $this->calendarInfo['principaluri'];
+
+ if ($is_owner || strpos($rights, 'i') !== false) {
+ $acl[] = array(
+ 'privilege' => '{DAV:}write',
+ 'principal' => $this->addressBookInfo['principaluri'],
+ 'protected' => true,
+ );
+ }
+
+ return $acl;
+ }
+ else {
+ // fallback to default ACL rules based on ownership
+ return parent::getACL();
+ }
+ }
+
+ /**
+ * Updates the ACL
+ *
+ * This method will receive a list of new ACE's.
+ *
+ * @param array $acl
+ * @return void
+ */
+ public function setACL(array $acl)
+ {
+ // TODO: implement this
+ throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
+ }
+
+}
diff --git a/lib/Kolab/CardDAV/AddressBookRoot.php b/lib/Kolab/CardDAV/AddressBookRoot.php
new file mode 100644
index 0000000..218c096
--- /dev/null
+++ b/lib/Kolab/CardDAV/AddressBookRoot.php
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * SabreDAV CalendarRootNode derived class for the Kolab.
+ *
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace Kolab\CardDAV;
+
+use Sabre\DAVACL;
+use Sabre\CardDav\Backend;
+
+/**
+ * AddressBook rootnode
+ *
+ * This object lists a collection of users, which can contain addressbooks.
+ *
+ * @copyright Copyright (C) 2007-2013 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class AddressBookRoot extends DAVACL\AbstractPrincipalCollection
+{
+ /**
+ * Principal Backend
+ *
+ * @var Sabre\DAVACL\PrincipalBackend\BackendInteface
+ */
+ protected $principalBackend;
+
+ /**
+ * CardDAV backend
+ *
+ * @var Backend\BackendInterface
+ */
+ protected $carddavBackend;
+
+ /**
+ * Constructor
+ *
+ * This constructor needs both a principal and a carddav backend.
+ *
+ * By default this class will show a list of addressbook collections for
+ * principals in the 'principals' collection. If your main principals are
+ * actually located in a different path, use the $principalPrefix argument
+ * to override this.
+ *
+ * @param DAVACL\PrincipalBackend\BackendInterface $principalBackend
+ * @param Backend\BackendInterface $carddavBackend
+ * @param string $principalPrefix
+ */
+ public function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, Backend\BackendInterface $carddavBackend, $principalPrefix = 'principals')
+ {
+ $this->carddavBackend = $carddavBackend;
+ parent::__construct($principalBackend, $principalPrefix);
+ }
+
+ /**
+ * Returns the name of the node
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return \Sabre\CardDAV\Plugin::ADDRESSBOOK_ROOT;
+ }
+
+ /**
+ * This method returns a node for a principal.
+ *
+ * The passed array contains principal information, and is guaranteed to
+ * at least contain a uri item. Other properties may or may not be
+ * supplied by the authentication backend.
+ *
+ * @param array $principal
+ * @return \Sabre\DAV\INode
+ */
+ public function getChildForPrincipal(array $principal)
+ {
+ return new UserAddressBooks($this->carddavBackend, $principal['uri']);
+ }
+
+}
diff --git a/lib/Kolab/CardDAV/ContactsBackend.php b/lib/Kolab/CardDAV/ContactsBackend.php
index 758f22a..7150f4e 100644
--- a/lib/Kolab/CardDAV/ContactsBackend.php
+++ b/lib/Kolab/CardDAV/ContactsBackend.php
@@ -29,6 +29,7 @@ use \kolab_storage;
use Sabre\DAV;
use Sabre\CardDAV;
use Sabre\VObject;
+use Kolab\Utils\DAVBackend;
use Kolab\Utils\VObjectUtils;
/**
@@ -66,17 +67,16 @@ class ContactsBackend extends CardDAV\Backend\AbstractBackend
asort($names, SORT_LOCALE_STRING);
foreach ($names as $utf7name => $name) {
- $id = urlencode($utf7name);
+ $id = DAVBackend::get_uid($folders[$utf7name]);
$folder = $this->folders[$id] = $folders[$utf7name];
$fdata = $folder->get_imap_data(); // fetch IMAP folder data for CTag generation
$this->sources[$id] = array(
'id' => $id,
'uri' => $id,
'{DAV:}displayname' => $name,
- '{http://calendarserver.org/ns/}getctag' => sprintf('%d-%d-%d', $fdata['UIDVALIDITY'], $fdata['HIGHESTMODSEQ'], time(), $fdata['UIDNEXT']),
+ '{http://calendarserver.org/ns/}getctag' => sprintf('%d-%d-%d', $fdata['UIDVALIDITY'], $fdata['HIGHESTMODSEQ'], $fdata['UIDNEXT']),
'{urn:ietf:params:xml:ns:caldav}supported-address-data' => new CardDAV\Property\SupportedAddressData(),
);
-
}
return $this->sources;
@@ -95,8 +95,7 @@ class ContactsBackend extends CardDAV\Backend\AbstractBackend
return $this->folders[$id];
}
else {
- $storage = kolab_storage::get_folder(urldecode($id));
- return !PEAR::isError($this->storage) ? $storage : null;
+ return DAVBackend::get_storage_folder($id, 'contact');
}
}
@@ -109,6 +108,8 @@ class ContactsBackend extends CardDAV\Backend\AbstractBackend
*/
public function getAddressBooksForUser($principalUri)
{
+ console(__METHOD__, $principalUri);
+
$this->_read_sources();
$addressBooks = array();
@@ -134,6 +135,8 @@ class ContactsBackend extends CardDAV\Backend\AbstractBackend
*/
public function updateAddressBook($addressBookId, array $mutations)
{
+ console(__METHOD__, $addressBookId, $mutations);
+
// TODO: implement this
return false;
}
@@ -148,6 +151,8 @@ class ContactsBackend extends CardDAV\Backend\AbstractBackend
*/
public function createAddressBook($principalUri, $url, array $properties)
{
+ console(__METHOD__, $principalUri, $url, $properties);
+
// TODO: implement this
}
@@ -159,6 +164,8 @@ class ContactsBackend extends CardDAV\Backend\AbstractBackend
*/
public function deleteAddressBook($addressBookId)
{
+ console(__METHOD__, $addressBookId);
+
// TODO: implement this
}
@@ -191,7 +198,6 @@ class ContactsBackend extends CardDAV\Backend\AbstractBackend
'id' => $contact['uid'],
'uri' => $contact['uid'] . '.vcf',
'lastmodified' => $contact['changed']->format('U'),
-// 'calendarid' => $addressbookId,
'etag' => self::_get_etag($contact),
'size' => $contact['_size'],
);
@@ -223,7 +229,6 @@ class ContactsBackend extends CardDAV\Backend\AbstractBackend
'id' => $contact['uid'],
'uri' => $contact['uid'] . '.vcf',
'lastmodified' => $contact['changed']->format('U'),
-// 'calendarid' => $calendarId,
'carddata' => $this->_to_vcard($contact),
'etag' => self::_get_etag($contact),
);
@@ -703,7 +708,7 @@ class ContactsBackend extends CardDAV\Backend\AbstractBackend
*/
private static function _get_etag($contact)
{
- return sprintf('"%s-%d"', substr(md5($contact['uid']), 0, 16), time(), $contact['_msguid']);
+ return sprintf('"%s-%d"', substr(md5($contact['uid']), 0, 16), $contact['_msguid']);
}
}
diff --git a/lib/Kolab/CardDAV/Plugin.php b/lib/Kolab/CardDAV/Plugin.php
new file mode 100644
index 0000000..6268989
--- /dev/null
+++ b/lib/Kolab/CardDAV/Plugin.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * Extended CardDAV plugin for the Kolab DAV server
+ *
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace Kolab\CardDAV;
+
+use Sabre\DAV;
+use Sabre\CardDAV;
+use Sabre\VObject;
+
+
+/**
+ * Extended CardDAV plugin to tweak data validation
+ */
+class Plugin extends CardDAV\Plugin
+{
+ // make already parsed vcard blocks available for later use
+ public static $parsed_vcard;
+
+
+ /**
+ * Checks if the submitted iCalendar data is in fact, valid.
+ *
+ * An exception is thrown if it's not.
+ *
+ * @param resource|string $data
+ * @return void
+ */
+ protected function validateVCard(&$data)
+ {
+ // If it's a stream, we convert it to a string first.
+ if (is_resource($data)) {
+ $data = stream_get_contents($data);
+ }
+
+ // Converting the data to unicode, if needed.
+ $data = DAV\StringUtil::ensureUTF8($data);
+
+ try {
+ $vobj = VObject\Reader::read($data, VObject\Reader::OPTION_FORGIVING | VObject\Reader::OPTION_IGNORE_INVALID_LINES);
+
+ if ($vobj->name == 'VCARD')
+ $this->parsed_vcard = $vobj;
+ }
+ catch (VObject\ParseException $e) {
+ throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid vcard data. Parse error: ' . $e->getMessage());
+ }
+
+ if ($vobj->name !== 'VCARD') {
+ throw new DAV\Exception\UnsupportedMediaType('This collection can only support vcard objects.');
+ }
+
+ if (!isset($vobj->UID)) {
+ throw new DAV\Exception\BadRequest('Every vcard must have a UID.');
+ }
+ }
+
+} \ No newline at end of file
diff --git a/lib/Kolab/CardDAV/UserAddressBooks.php b/lib/Kolab/CardDAV/UserAddressBooks.php
new file mode 100644
index 0000000..1b735ec
--- /dev/null
+++ b/lib/Kolab/CardDAV/UserAddressBooks.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * SabreDAV UserAddressBooks derived class for the Kolab.
+ *
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace Kolab\CardDAV;
+
+use Sabre\DAV;
+use Sabre\DAVACL;
+
+/**
+ * UserAddressBooks class
+ *
+ * The UserAddressBooks collection contains a list of addressbooks associated with a user
+ *
+ * @copyright Copyright (C) 2007-2013 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class UserAddressBooks extends \Sabre\CardDAV\UserAddressBooks implements DAV\IExtendedCollection, DAVACL\IACL
+{
+ /**
+ * Returns a list of addressbooks
+ *
+ * @return array
+ */
+ public function getChildren()
+ {
+ $addressbooks = $this->carddavBackend->getAddressbooksForUser($this->principalUri);
+ $objs = array();
+ foreach($addressbooks as $addressbook) {
+ $objs[] = new AddressBook($this->carddavBackend, $addressbook);
+ }
+ return $objs;
+ }
+
+}
diff --git a/lib/Kolab/DAVACL/PrincipalBackend.php b/lib/Kolab/DAVACL/PrincipalBackend.php
index 99812af..fcd7417 100644
--- a/lib/Kolab/DAVACL/PrincipalBackend.php
+++ b/lib/Kolab/DAVACL/PrincipalBackend.php
@@ -46,7 +46,7 @@ class PrincipalBackend implements \Sabre\DAVACL\PrincipalBackend\BackendInterfac
*/
public function getCurrentUser()
{
- console(__METHOD__, HTTPBasic::$current_user);
+ // console(__METHOD__, HTTPBasic::$current_user);
if (HTTPBasic::$current_user) {
$user_email = rcube::get_instance()->get_user_email();
@@ -106,7 +106,7 @@ class PrincipalBackend implements \Sabre\DAVACL\PrincipalBackend\BackendInterfac
*/
public function getPrincipalByPath($path)
{
- console(__METHOD__, $path);
+ // console(__METHOD__, $path);
list($prefix,$name) = explode('/', $path);
@@ -183,9 +183,61 @@ class PrincipalBackend implements \Sabre\DAVACL\PrincipalBackend\BackendInterfac
return 0;
}
+ /**
+ * This method is used to search for principals matching a set of
+ * properties.
+ *
+ * This search is specifically used by RFC3744's principal-property-search
+ * REPORT. You should at least allow searching on
+ * http://sabredav.org/ns}email-address.
+ *
+ * The actual search should be a unicode-non-case-sensitive search. The
+ * keys in searchProperties are the WebDAV property names, while the values
+ * are the property values to search on.
+ *
+ * If multiple properties are being searched on, the search should be
+ * AND'ed.
+ *
+ * This method should simply return an array with full principal uri's.
+ *
+ * If somebody attempted to search on a property the backend does not
+ * support, you should simply return 0 results.
+ *
+ * @param string $prefixPath
+ * @param array $searchProperties
+ * @return array
+ */
function searchPrincipals($prefixPath, array $searchProperties)
{
- return 0;
+ console(__METHOD__, $prefixPath, $searchProperties);
+
+ $email = null;
+ $results = array();
+ $current_user = $this->getCurrentUser();
+ foreach($searchProperties as $property => $value) {
+ // check search property against the current user
+ if ($current_user[$property] == $value) {
+ $results[] = $current_user['uri'];
+ continue;
+ }
+ switch($property) {
+ case '{http://sabredav.org/ns}email-address':
+ $email = $value;
+ break;
+
+ case '{DAV:}displayname':
+ default :
+ // Unsupported property
+ return array();
+ }
+ }
+
+ // we only support search by email
+ if (!empty($email)) {
+ // TODO: search via LDAP
+ }
+
+ return array_unique($results);
}
}
diff --git a/lib/Kolab/Utils/DAVBackend.php b/lib/Kolab/Utils/DAVBackend.php
new file mode 100644
index 0000000..822437b
--- /dev/null
+++ b/lib/Kolab/Utils/DAVBackend.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * Utility class prividing a simple API to PHP's APC cache
+ *
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace Kolab\Utils;
+
+use \kolab_storage;
+
+/**
+ *
+ */
+class DAVBackend
+{
+ const IMAP_UID_KEY = '/shared/vendor/kolab/dav-uid';
+ const IMAP_UID_KEY_PRIVATE = '/private/vendor/kolab/dav-uid';
+
+ /**
+ * Getter for a kolab_storage_folder with the given UID
+ *
+ * @param string Folder UID (saved in annotation)
+ * @param string Kolab folder type (for selecting candidates)
+ * @return object \kolab_storage_folder instance
+ */
+ public static function get_storage_folder($uid, $type)
+ {
+ foreach (kolab_storage::get_folders($type) as $folder) {
+ if (self::get_uid($folder) == $uid)
+ return $folder;
+ }
+
+ return null;
+ }
+
+ /**
+ * Helper method to extract folder UID metadata
+ *
+ * @param object \kolab_storage_folder Folder to get UID for
+ * @return string Folder's UID
+ */
+ public static function get_uid($folder)
+ {
+ // color is defined in folder METADATA
+ $metadata = $folder->get_metadata(array(self::IMAP_UID_KEY, self::IMAP_UID_KEY_PRIVATE));
+ if (($uid = $metadata[self::IMAP_UID_KEY]) || ($uid = $metadata[self::IMAP_UID_KEY_PRIVATE])) {
+ return $uid;
+ }
+
+ // generate a folder UID and set it to IMAP
+ $uid = rtrim(chunk_split(md5($folder->name), 12, '-'), '-');
+ self::set_uid($folder, $uid);
+
+ return $uid;
+ }
+
+ /**
+ * Helper method to set an UID value to the given IMAP folder instance
+ *
+ * @param object \kolab_storage_folder Folder to set UID
+ * @param string Folder's UID
+ * @return boolean True on succes, False on failure
+ */
+ public static function set_uid($folder, $uid)
+ {
+ if (!($success = $folder->set_metadata(array(self::IMAP_UID_KEY => $uid)))) {
+ $success = $folder->set_metadata(array(self::IMAP_UID_KEY_PRIVATE => $uid));
+ }
+
+ return $success;
+ }
+
+} \ No newline at end of file
diff --git a/lib/Kolab/Utils/VObjectUtils.php b/lib/Kolab/Utils/VObjectUtils.php
index dadd7be..755cf8d 100644
--- a/lib/Kolab/Utils/VObjectUtils.php
+++ b/lib/Kolab/Utils/VObjectUtils.php
@@ -1,5 +1,26 @@
<?php
+/**
+ * Utility class providing functions for VObject data encoding
+ *
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
namespace Kolab\Utils;
use Sabre\VObject\Property;
diff --git a/public_html/.htaccess b/public_html/.htaccess
index c63d795..de79a2c 100644
--- a/public_html/.htaccess
+++ b/public_html/.htaccess
@@ -1,6 +1,6 @@
RewriteEngine On
-RewriteBase /
+#RewriteBase /
RewriteRule ^\.well-known/caldav / [R,L]
RewriteRule ^\.well-known/carddav / [R,L]
diff --git a/public_html/index.php b/public_html/index.php
index 6d4464c..fa59084 100644
--- a/public_html/index.php
+++ b/public_html/index.php
@@ -105,7 +105,7 @@ $nodes = array(
// /calendars
new \Kolab\CalDAV\CalendarRootNode($principal_backend, $caldav_backend),
// /addressbook
- new \Sabre\CardDAV\AddressBookRoot($principal_backend, $carddav_backend),
+ new \Kolab\CardDAV\AddressBookRoot($principal_backend, $carddav_backend),
);
// the object tree needs in turn to be passed to the server class
@@ -114,9 +114,9 @@ $server->setBaseUri($base_uri);
// register some plugins
$server->addPlugin(new \Sabre\DAV\Auth\Plugin($auth_backend, 'KolabDAV'));
-$server->addPlugin(new \Sabre\DAVACL\Plugin()); // we'll add that later
+$server->addPlugin(new \Sabre\DAVACL\Plugin());
$server->addPlugin(new \Kolab\CalDAV\Plugin());
-$server->addPlugin(new \Sabre\CardDAV\Plugin());
+$server->addPlugin(new \Kolab\CardDAV\Plugin());
$server->addPlugin(new \Sabre\DAV\Browser\Plugin());
// finally, process the request