first commit of new changes to CalDAV support
This commit is contained in:
parent
21e3fff576
commit
822b359185
16 changed files with 870 additions and 140 deletions
12
calendar.php
12
calendar.php
|
@ -928,6 +928,18 @@ $("#rcmfd_new_category").keypress(function(event) {
|
|||
}
|
||||
|
||||
switch ($action) {
|
||||
case "new-source": //we "misuse" create_calendar() to stay compatible with other drivers
|
||||
$cal['new-source'] = true;
|
||||
$success = $this->driver->create_calendar($cal);
|
||||
$reload = true;
|
||||
break;
|
||||
case "delete-source": //we "misuse" delete_calendar() to stay compatible with other drivers
|
||||
$cal['delete-source'] = true;
|
||||
$success = $this->driver->delete_calendar($cal);
|
||||
$reload = true;
|
||||
break;
|
||||
case "form-source-new": //we "misuse" calendar_editform() to stay compatible with other drivers
|
||||
case "form-source-delete": //we "misuse" calendar_editform() to stay compatible with other drivers
|
||||
case "form-new":
|
||||
case "form-edit":
|
||||
echo $this->ui->calendar_editform($action, $cal);
|
||||
|
|
|
@ -2884,6 +2884,64 @@ function rcube_calendar_ui(settings)
|
|||
return update_event_confirm('remove', event, { id:event.id, calendar:event.calendar, attendees:event.attendees });
|
||||
};
|
||||
|
||||
//opens a dialog to add caldav sources
|
||||
this.calendar_new_source = function() {
|
||||
var title = rcmail.gettext('addsources', 'calendar'),
|
||||
params = {action: 'form-source-new', _framed: 1},
|
||||
$dialog = $('<iframe>').attr('src', rcmail.url('calendar', params)),
|
||||
save_func = function() {
|
||||
var data,
|
||||
form = $dialog.contents().find('#calendarpropform');
|
||||
|
||||
// form is not loaded
|
||||
if (!form || !form.length)
|
||||
return false;
|
||||
|
||||
// post data to server
|
||||
data = form.serializeJSON();
|
||||
if (data.color)
|
||||
data.color = data.color.replace(/^#/, '');
|
||||
if (calendar.id)
|
||||
data.id = calendar.id;
|
||||
|
||||
me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
|
||||
rcmail.http_post('calendar', { action:'new-source', c:data });
|
||||
$dialog.dialog("close");
|
||||
};
|
||||
|
||||
rcmail.simple_dialog($dialog, title, save_func, {
|
||||
width: 600,
|
||||
height: 400
|
||||
});
|
||||
};
|
||||
|
||||
//opens a dialog to delete caldav sources
|
||||
this.calendar_delete_sources = function() {
|
||||
var title = rcmail.gettext('deletesources', 'calendar'),
|
||||
params = {action: 'form-source-delete', _framed: 1},
|
||||
$dialog = $('<iframe>').attr('src', rcmail.url('calendar', params)),
|
||||
save_func = function() {
|
||||
var data,
|
||||
form = $dialog.contents().find('#calendarpropform');
|
||||
|
||||
// form is not loaded
|
||||
if (!form || !form.length)
|
||||
return false;
|
||||
|
||||
// post data to server
|
||||
data = form.serializeJSON();
|
||||
|
||||
me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
|
||||
rcmail.http_post('calendar', { action:'delete-source', c:data });
|
||||
$dialog.dialog("close");
|
||||
};
|
||||
|
||||
rcmail.simple_dialog($dialog, title, save_func, {
|
||||
width: 600,
|
||||
height: 400
|
||||
});
|
||||
};
|
||||
|
||||
// opens a jquery UI dialog with event properties (or empty for creating a new calendar)
|
||||
this.calendar_edit_dialog = function(calendar)
|
||||
{
|
||||
|
@ -4168,6 +4226,8 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
|
|||
rcmail.register_command('print', function(){ cal.print_calendars(); }, true);
|
||||
|
||||
// configure list operations
|
||||
rcmail.register_command('calendar-sources-add', cal.calendar_new_source, true);
|
||||
rcmail.register_command('calendar-sources-delete', cal.calendar_delete_sources, true);
|
||||
rcmail.register_command('calendar-create', function(){ cal.calendar_edit_dialog(null); }, true);
|
||||
rcmail.register_command('calendar-edit', function(){ cal.calendar_edit_dialog(cal.calendars[cal.selected_calendar]); }, false);
|
||||
rcmail.register_command('calendar-remove', function(){ cal.calendar_remove(cal.calendars[cal.selected_calendar]); }, false);
|
||||
|
|
|
@ -21,13 +21,18 @@
|
|||
{
|
||||
"type": "composer",
|
||||
"url": "https://plugins.roundcube.net"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/JodliDev/libcalendaring"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.4.0",
|
||||
"php": ">=5.5",
|
||||
"roundcube/plugin-installer": ">=0.1.3",
|
||||
"kolab/libcalendaring": ">=3.4.0",
|
||||
"kolab/libkolab": ">=3.4.0"
|
||||
"jodlidev/libcalendaring": ">=3.4.0",
|
||||
"kolab/libkolab": ">=3.4.0",
|
||||
"sabre/dav": ">=4.1.5"
|
||||
},
|
||||
"extra": {
|
||||
"roundcube": {
|
||||
|
|
|
@ -25,8 +25,12 @@
|
|||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
// backend type (database, kolab)
|
||||
$config['calendar_driver'] = "database";
|
||||
// backend type (database, kolab, caldav)
|
||||
$config['calendar_driver'] = "caldav";
|
||||
|
||||
// Enable debugging output for iCAL/CalDAV drivers
|
||||
$config['calendar_caldav_debug'] = false;
|
||||
$config['calendar_ical_debug'] = false;
|
||||
|
||||
// default calendar view (agendaDay, agendaWeek, month)
|
||||
$config['calendar_default_view'] = "agendaWeek";
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* @version @package_version@
|
||||
* @author Daniel Morlock <daniel.morlock@awesome-it.de>
|
||||
* @author JodliDev <jodlidev@gmail.com>
|
||||
*
|
||||
* Copyright (C) Awesome IT GbR <info@awesome-it.de>
|
||||
*
|
||||
|
@ -20,23 +21,37 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `caldav_sources` (
|
||||
`source_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
|
||||
|
||||
`caldav_url` varchar(255) NOT NULL,
|
||||
`caldav_user` varchar(255) DEFAULT NULL,
|
||||
`caldav_pass` varchar(1024) DEFAULT NULL,
|
||||
|
||||
PRIMARY KEY(`source_id`),
|
||||
CONSTRAINT `fk_caldav_sources_user_id` FOREIGN KEY (`user_id`)
|
||||
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `caldav_calendars` (
|
||||
`calendar_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
|
||||
`source_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
|
||||
`name` varchar(255) NOT NULL,
|
||||
`color` varchar(8) NOT NULL,
|
||||
`showalarms` tinyint(1) NOT NULL DEFAULT '1',
|
||||
|
||||
`caldav_url` varchar(255) NOT NULL,
|
||||
`caldav_tag` varchar(255) DEFAULT NULL,
|
||||
`caldav_user` varchar(255) DEFAULT NULL,
|
||||
`caldav_pass` varchar(1024) DEFAULT NULL,
|
||||
`caldav_url` varchar(255) NOT NULL,
|
||||
`caldav_last_change` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
PRIMARY KEY(`calendar_id`),
|
||||
INDEX `caldav_user_name_idx` (`user_id`, `name`),
|
||||
CONSTRAINT `fk_caldav_calendars_user_id` FOREIGN KEY (`user_id`)
|
||||
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT `fk_caldav_calendars_sources` FOREIGN KEY (`source_id`)
|
||||
REFERENCES `caldav_sources`(`source_id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `caldav_events` (
|
||||
|
|
398
drivers/caldav/caldav_client.php
Normal file
398
drivers/caldav/caldav_client.php
Normal file
|
@ -0,0 +1,398 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* CalDAV Client
|
||||
*
|
||||
* @version @package_version@
|
||||
* @author Daniel Morlock <daniel.morlock@awesome-it.de>
|
||||
* @author JodliDev <jodlidev@gmail.com>
|
||||
*
|
||||
* Copyright (C) Awesome IT GbR <info@awesome-it.de>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
class caldav_client extends Sabre\DAV\Client
|
||||
{
|
||||
const CLARK_GETCTAG = '{http://calendarserver.org/ns/}getctag';
|
||||
const CLARK_GETETAG = '{DAV:}getetag';
|
||||
const CLARK_CALDATA = '{urn:ietf:params:xml:ns:caldav}calendar-data';
|
||||
|
||||
private $base_uri;
|
||||
private $path;
|
||||
private $libvcal;
|
||||
|
||||
/**
|
||||
* Default constructor for CalDAV client.
|
||||
*
|
||||
* @param string Caldav URI to appropriate calendar.
|
||||
* @param string Username for HTTP basic auth.
|
||||
* @param string Password for HTTP basic auth.
|
||||
*/
|
||||
public function __construct($uri, $user = null, $pass = null)
|
||||
{
|
||||
|
||||
// Include libvcalendar on demand ...
|
||||
if(!class_exists("libvcalendar"))
|
||||
require_once __DIR__ .'/../../../libcalendaring/libvcalendar.php';
|
||||
|
||||
|
||||
$this->libvcal = new libvcalendar();
|
||||
|
||||
$tokens = parse_url($uri);
|
||||
$this->base_uri = $tokens['scheme']."://".$tokens['host'].($tokens['port'] ? ":".$tokens['port'] : null);
|
||||
$this->path = $tokens['path'].($tokens['query'] ? "?".$tokens['query'] : null);
|
||||
|
||||
$settings = array(
|
||||
'baseUri' => $this->base_uri,
|
||||
'authType' => Sabre\DAV\Client::AUTH_BASIC
|
||||
);
|
||||
|
||||
$this->rc = rcmail::get_instance();
|
||||
|
||||
if ($user) $settings['userName'] = $user;
|
||||
if ($pass) $settings['password'] = $pass;
|
||||
|
||||
parent::__construct($settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches calendar ctag.
|
||||
*
|
||||
* @see http://code.google.com/p/sabredav/wiki/BuildingACalDAVClient#Retrieving_calendar_information
|
||||
* @return Calendar ctag or null on error.
|
||||
*/
|
||||
public function get_ctag()
|
||||
{
|
||||
try
|
||||
{
|
||||
$arr = $this->propFind($this->path, array(self::CLARK_GETCTAG));
|
||||
|
||||
if (isset($arr[self::CLARK_GETCTAG]))
|
||||
return $arr[self::CLARK_GETCTAG];
|
||||
}
|
||||
catch(Sabre\DAV\Exception $err)
|
||||
{
|
||||
rcube::raise_error(array(
|
||||
'code' => $err->getHTTPCode(),
|
||||
'type' => 'DAV',
|
||||
'file' => $err->getFile(),
|
||||
'line' => $err->getLine(),
|
||||
'message' => $err->getMessage()
|
||||
), true, false);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches event etags and urls.
|
||||
*
|
||||
* @see http://code.google.com/p/sabredav/wiki/BuildingACalDAVClient#Finding_out_if_anything_changed
|
||||
*
|
||||
* @param array Optional list of relative event URL's to retrieve specific etags. If not specified, all etags of the current calendar are returned.
|
||||
* @return array List of etag properties with keys:
|
||||
* url: Event ical path relative to the calendar URL.
|
||||
* etag: Current event etag.
|
||||
*/
|
||||
public function get_etags(array $event_urls = array())
|
||||
{
|
||||
$etags = array();
|
||||
|
||||
try
|
||||
{
|
||||
$arr = $this->prop_report($this->path, array(self::CLARK_GETETAG), $event_urls);
|
||||
foreach ($arr as $path => $data)
|
||||
{
|
||||
// Some caldav server return an empty calendar as event where etag is missing. Skip this!
|
||||
if($data[self::CLARK_GETETAG])
|
||||
{
|
||||
array_push($etags, array(
|
||||
"url" => $path,
|
||||
"etag" => str_replace('"', null, $data[self::CLARK_GETETAG])
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Sabre\DAV\Exception $err)
|
||||
{
|
||||
rcube::raise_error(array(
|
||||
'code' => $err->getHTTPCode(),
|
||||
'type' => 'DAV',
|
||||
'file' => $err->getFile(),
|
||||
'line' => $err->getLine(),
|
||||
'message' => $err->getMessage()
|
||||
), true, false);
|
||||
}
|
||||
|
||||
return $etags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches calendar events.
|
||||
*
|
||||
* @see http://code.google.com/p/sabredav/wiki/BuildingACalDAVClient#Downloading_objects
|
||||
* @param array $urls = array() Optional list of event URL's to fetch. If non is specified, all
|
||||
* events from the appropriate calendar will be fetched.
|
||||
* @return Array hash list that maps the events URL to the appropriate event properties.
|
||||
*/
|
||||
public function get_events($urls = array())
|
||||
{
|
||||
$events = array();
|
||||
|
||||
try
|
||||
{
|
||||
$vcals = $this->prop_report($this->path, array(
|
||||
self::CLARK_GETETAG,
|
||||
self::CLARK_CALDATA
|
||||
), $urls);
|
||||
|
||||
foreach ($vcals as $path => $response)
|
||||
{
|
||||
$vcal = $response[self::CLARK_CALDATA];
|
||||
foreach ($this->libvcal->import($vcal) as $event) {
|
||||
$events[$path] = $event;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Sabre\DAV\Exception $err)
|
||||
{
|
||||
rcube::raise_error(array(
|
||||
'code' => $err->getHTTPCode(),
|
||||
'type' => 'DAV',
|
||||
'file' => $err->getFile(),
|
||||
'line' => $err->getLine(),
|
||||
'message' => $err->getMessage()
|
||||
), true, false);
|
||||
}
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a REPORT request
|
||||
*
|
||||
* @param string $url
|
||||
* @param array $properties List of requested properties must be specified as an array, in clark
|
||||
* notation.
|
||||
* @param array $event_urls If specified, a multiget report request will be initiated with the
|
||||
* specified event urls.
|
||||
* @param int $depth = 1 Depth should be either 0 or 1. A depth of 1 will cause a request to be
|
||||
* made to the server to also return all child resources.
|
||||
* @return array Hash with ics event path as key and a hash array with properties and appropriate values.
|
||||
*/
|
||||
public function prop_report($url, array $properties, array $event_urls = array(), $depth = 1)
|
||||
{
|
||||
$parent_tag = sizeof($event_urls) > 0 ? "c:calendar-multiget" : "d:propfind";
|
||||
$method = sizeof($event_urls) > 0 ? 'REPORT' : 'PROPFIND';
|
||||
|
||||
$body = '<?xml version="1.0"?>'."\n".'<'.$parent_tag.' xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">'."\n";
|
||||
|
||||
$body .= ' <d:prop>'."\n";
|
||||
foreach ($properties as $property)
|
||||
{
|
||||
|
||||
list($namespace, $elementName) = Sabre\Xml\Service::parseClarkNotation($property);
|
||||
|
||||
if ($namespace === 'DAV:')
|
||||
{
|
||||
$body .= ' <d:'.$elementName.' />'."\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
$body .= ' <x:'.$elementName.' xmlns:x="'.$namespace.'"/>'."\n";
|
||||
}
|
||||
}
|
||||
$body .= ' </d:prop>'."\n";
|
||||
|
||||
// http://tools.ietf.org/html/rfc4791#page-90
|
||||
// http://www.bedework.org/trac/bedework/wiki/Bedework/DevDocs/Filters
|
||||
/*
|
||||
if($start && $end)
|
||||
{
|
||||
$body.= ' <c:filter>'."\n".
|
||||
' <c:comp-filter name="VCALENDAR">'."\n".
|
||||
' <c:comp-filter name="VEVENT">'."\n".
|
||||
' <c:time-range start="'.$start.'" end="'.$end.'" />'."\n".
|
||||
' </c:comp-filter>'."\n".
|
||||
' </c:comp-filter>'."\n".
|
||||
' </c:filter>' . "\n";
|
||||
}
|
||||
*/
|
||||
|
||||
foreach ($event_urls as $event_url)
|
||||
{
|
||||
$body .= '<d:href>'.$event_url.'</d:href>'."\n";
|
||||
}
|
||||
|
||||
$body .= '</'.$parent_tag.'>';
|
||||
|
||||
$response = $this->request($method, $url, $body, array(
|
||||
'Depth' => $depth,
|
||||
'Content-Type' => 'application/xml'
|
||||
));
|
||||
|
||||
$result = $this->parseMultiStatus($response['body']);
|
||||
|
||||
// If depth was 0, we only return the top item
|
||||
if ($depth === 0)
|
||||
{
|
||||
reset($result);
|
||||
$result = current($result);
|
||||
return isset($result[200]) ? $result[200] : array();
|
||||
}
|
||||
|
||||
$new_result = array();
|
||||
foreach ($result as $href => $status_list)
|
||||
{
|
||||
$new_result[$href] = isset($status_list[200]) ? $status_list[200] : array();
|
||||
}
|
||||
|
||||
return $new_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates or creates a calendar event.
|
||||
*
|
||||
* @see http://code.google.com/p/sabredav/wiki/BuildingACalDAVClient#Updating_a_calendar_object
|
||||
* @param string Event ics path for the event.
|
||||
* @param array Hash array with event properties.
|
||||
* @param string Current event etag to match against server data. Pass null for new events.
|
||||
* @return True on success, -1 if precondition failed i.e. local etag is not up to date, false on error.
|
||||
*/
|
||||
public function put_event($path, $event, $etag = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
$headers = array("Content-Type" => "text/calendar; charset=utf-8");
|
||||
if ($etag) $headers["If-Match"] = '"'.$etag.'"';
|
||||
|
||||
// Temporarily disable error reporting since libvcal seems not checking array key properly.
|
||||
// TODO: Remove this todo if we could ensure that those errors come not from incomplete event properties.
|
||||
$err_rep = error_reporting(E_ERROR);
|
||||
$vcal = $this->libvcal->export(array($event));
|
||||
if (is_array($vcal))
|
||||
$vcal = array_shift($vcal);
|
||||
error_reporting($err_rep);
|
||||
|
||||
$response = $this->request('PUT', $path, $vcal, $headers);
|
||||
|
||||
// Following http://code.google.com/p/sabredav/wiki/BuildingACalDAVClient#Creating_a_calendar_object, the
|
||||
// caldav server must not always return the new etag.
|
||||
|
||||
return $response["statusCode"] == 201 || // 201 (created, successfully created)
|
||||
$response["statusCode"] == 204; // 204 (no content, successfully updated)
|
||||
}
|
||||
catch(Sabre\DAV\Exception\PreconditionFailed $err)
|
||||
{
|
||||
// Event tag not up to date, must be updated first ...
|
||||
return -1;
|
||||
}
|
||||
catch(Sabre\DAV\Exception $err)
|
||||
{
|
||||
rcube::raise_error(array(
|
||||
'code' => $err->getHTTPCode(),
|
||||
'type' => 'DAV',
|
||||
'file' => $err->getFile(),
|
||||
'line' => $err->getLine(),
|
||||
'message' => $err->getMessage()
|
||||
), true, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes event of given URL.
|
||||
*
|
||||
* @see http://code.google.com/p/sabredav/wiki/BuildingACalDAVClient#Deleting_a_calendar_object
|
||||
* @param string Event ics path for the event.
|
||||
* @param string Current event etag to match against server data. Pass null to force removing the event.
|
||||
* @return True on success, -1 if precondition failed i.e. local etag is not up to date, false on error.
|
||||
**/
|
||||
public function remove_event($path, $etag = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
$headers = array("Content-Type" => "text/calendar; charset=utf-8");
|
||||
if ($etag) $headers["If-Match"] = '"'.$etag.'"';
|
||||
|
||||
$response = $this->request('DELETE', $path, null, $headers);
|
||||
return $response["statusCode"] == 204 || // 204 (no content, successfully deleted)
|
||||
$response["statusCode"] == 200; // 200 (OK, successfully deleted)
|
||||
}
|
||||
catch(Sabre\DAV\Exception\PreconditionFailed $err)
|
||||
{
|
||||
// Event tag not up to date, must be updated first ...
|
||||
return -1;
|
||||
}
|
||||
catch(Sabre\DAV\Exception $err)
|
||||
{
|
||||
rcube::raise_error(array(
|
||||
'code' => $err->getHTTPCode(),
|
||||
'type' => 'DAV',
|
||||
'file' => $err->getFile(),
|
||||
'line' => $err->getLine(),
|
||||
'message' => $err->getMessage()
|
||||
), true, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a propFind query to caldav server
|
||||
* @param string $path absolute or relative URL to Resource
|
||||
* @param array $props list of properties to use for the query. Properties must have clark-notation.
|
||||
* @param int $depth 0 means no recurse while 1 means recurse
|
||||
* @return array
|
||||
*/
|
||||
public function prop_find($path, $props, $depth)
|
||||
{
|
||||
try {
|
||||
$response = $this->propFind($path, $props, $depth);
|
||||
}
|
||||
catch(Sabre\DAV\Exception $err)
|
||||
{
|
||||
rcube::raise_error(array(
|
||||
'code' => $err->getHTTPCode(),
|
||||
'type' => 'DAV',
|
||||
'file' => $err->getFile(),
|
||||
'line' => $err->getLine(),
|
||||
'message' => $err->getMessage()
|
||||
), true, false);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function create_calendar($path, $name, $color) {
|
||||
$headers = array("Content-Type" => "application/xml; charset=utf-8");
|
||||
$body = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>
|
||||
<C:mkcalendar xmlns:D=\"DAV:\" xmlns:C=\"urn:ietf:params:xml:ns:caldav\" xmlns:ical=\"http://apple.com/ns/ical/\">
|
||||
<D:set>
|
||||
<D:prop>
|
||||
<D:displayname>$name</D:displayname>
|
||||
<ical:calendar-color>#$color</ical:calendar-color>
|
||||
</D:prop>
|
||||
</D:set>
|
||||
</C:mkcalendar>";
|
||||
|
||||
$response = $this->request('MKCALENDAR', $path, $body, $headers);
|
||||
return $response["statusCode"] === 201;
|
||||
}
|
||||
|
||||
public function delete_calendar() {
|
||||
$response = $this->request('DELETE', $this->base_uri .$this->path);
|
||||
return $response["statusCode"] === 204 || // 204 (no content, successfully deleted)
|
||||
$response["statusCode"] === 200; // 200 (OK, successfully deleted)
|
||||
}
|
||||
};
|
||||
?>
|
|
@ -4,6 +4,7 @@
|
|||
* CalDAV driver for the Calendar plugin
|
||||
*
|
||||
* @author Daniel Morlock <daniel.morlock@awesome-it.de>
|
||||
* @author JodliDev <jodlidev@gmail.com>
|
||||
*
|
||||
* Copyright (C) Awesome IT GbR <info@awesome-it.de>
|
||||
*
|
||||
|
@ -21,8 +22,8 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
require_once (dirname(__FILE__).'/caldav_sync.php');
|
||||
require_once (dirname(__FILE__).'/../../lib/encryption.php');
|
||||
require_once 'caldav_sync.php';
|
||||
require_once 'encryption.php';
|
||||
|
||||
|
||||
class caldav_driver extends calendar_driver
|
||||
|
@ -47,13 +48,11 @@ class caldav_driver extends calendar_driver
|
|||
private $sensitivity_map = array('public' => 0, 'private' => 1, 'confidential' => 2);
|
||||
private $server_timezone;
|
||||
|
||||
private $db_events = 'caldav_events';
|
||||
private $db_sources = 'caldav_sources';
|
||||
private $db_calendars = 'caldav_calendars';
|
||||
private $db_events = 'caldav_events';
|
||||
private $db_attachments = 'caldav_attachments';
|
||||
|
||||
// Crypt key for CalDAV auth
|
||||
private $crypt_key;
|
||||
|
||||
// Holds CalDAV sync clients
|
||||
private $sync_clients = array();
|
||||
|
||||
|
@ -92,10 +91,10 @@ class caldav_driver extends calendar_driver
|
|||
|
||||
// read database config
|
||||
$db = $this->rc->get_dbh();
|
||||
$this->db_events = $this->rc->config->get('db_table_caldav_events', $db->table_name($this->db_events));
|
||||
$this->db_sources = $this->rc->config->get('db_table_caldav_sources', $db->table_name($this->db_sources));
|
||||
$this->db_calendars = $this->rc->config->get('db_table_caldav_calendars', $db->table_name($this->db_calendars));
|
||||
$this->db_events = $this->rc->config->get('db_table_caldav_events', $db->table_name($this->db_events));
|
||||
$this->db_attachments = $this->rc->config->get('db_table_caldav_attachments', $db->table_name($this->db_attachments));
|
||||
$this->crypt_key = $this->rc->config->get("calendar_crypt_key", "%E`c{2;<J2F^4_&._BxfQ<5Pf3qv!m{e");
|
||||
|
||||
// Set debug state
|
||||
if(self::$debug === null)
|
||||
|
@ -113,10 +112,22 @@ class caldav_driver extends calendar_driver
|
|||
|
||||
if (!empty($this->rc->user->ID)) {
|
||||
$calendar_ids = array();
|
||||
$result = $this->rc->db->query("SELECT *, calendar_id AS id
|
||||
FROM " . $this->db_calendars . "
|
||||
WHERE user_id=?
|
||||
ORDER BY name",
|
||||
$result = $this->rc->db->query('SELECT
|
||||
cal.calendar_id AS `calendar_id`,
|
||||
cal.source_id AS `source_id`,
|
||||
cal.name AS `name`,
|
||||
cal.color AS `color`,
|
||||
cal.showalarms AS `showalarms`,
|
||||
cal.caldav_tag AS `caldav_tag`,
|
||||
cal.caldav_url AS `caldav_url`,
|
||||
s.caldav_user AS `caldav_user`,
|
||||
s.caldav_pass AS `caldav_pass`,
|
||||
cal.caldav_last_change AS `caldav_last_change`,
|
||||
cal.calendar_id AS `id`
|
||||
FROM ' . $this->db_calendars . ' AS cal
|
||||
LEFT JOIN ' .$this->db_sources .' AS s ON (cal.source_id = s.source_id)
|
||||
WHERE cal.user_id=?
|
||||
ORDER BY name',
|
||||
$this->rc->user->ID
|
||||
);
|
||||
while ($result && ($arr = $this->rc->db->fetch_assoc($result))) {
|
||||
|
@ -132,6 +143,7 @@ class caldav_driver extends calendar_driver
|
|||
|
||||
// Init sync client
|
||||
$cal_id = $arr['calendar_id'];
|
||||
|
||||
$this->sync_clients[$cal_id] = new caldav_sync($arr);
|
||||
}
|
||||
$this->calendar_ids = join(',', $calendar_ids);
|
||||
|
@ -160,23 +172,73 @@ class caldav_driver extends calendar_driver
|
|||
|
||||
// 'personal' is unsupported in this driver
|
||||
|
||||
// append the virtual birthdays calendar
|
||||
if ($this->rc->config->get('calendar_contact_birthdays', false)) {
|
||||
$prefs = $this->rc->config->get('birthday_calendar', array('color' => '87CEFA'));
|
||||
$hidden = array_filter(explode(',', $this->rc->config->get('hidden_calendars', '')));
|
||||
$id = self::BIRTHDAY_CALENDAR_ID;
|
||||
|
||||
if (empty($active) || !in_array($id, $hidden)) {
|
||||
$calendars[$id] = array(
|
||||
'id' => $id,
|
||||
'name' => $this->cal->gettext('birthdays'),
|
||||
'listname' => $this->cal->gettext('birthdays'),
|
||||
'color' => $prefs['color'],
|
||||
'showalarms' => (bool)$this->rc->config->get('calendar_birthdays_alarm_type'),
|
||||
'active' => !in_array($id, $hidden),
|
||||
'group' => 'x-birthdays',
|
||||
'editable' => false,
|
||||
'default' => false,
|
||||
'children' => false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $calendars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts CalDAV calendar.
|
||||
* Creates CalDAV calendar or call create_source()
|
||||
* (we use the same function for both calendar and source to stay compatible with other drivers)
|
||||
*
|
||||
* @see database_driver::create_calendar()
|
||||
*/
|
||||
public function create_calendar($cal)
|
||||
{
|
||||
$result = false;
|
||||
$cal['caldav_url'] = self::_encode_url($cal["caldav_url"]);
|
||||
if(!isset($cal['color'])) $cal['color'] = 'cc0000';
|
||||
if(isset($cal['new-source'])) {
|
||||
return $this->create_source($cal);
|
||||
}
|
||||
else {
|
||||
$result = $this->rc->db->query('SELECT source_id, caldav_url, caldav_user, caldav_pass
|
||||
FROM ' . $this->db_sources . '
|
||||
WHERE user_id=? AND source_id=?',
|
||||
$this->rc->user->ID,
|
||||
$cal['source_id']
|
||||
);
|
||||
if(!$result || !($source = $this->rc->db->fetch_assoc($result))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$calendars = $this->_autodiscover_calendars($this->_expand_pass($cal));
|
||||
|
||||
|
||||
$server_url = self::_encode_url($source['caldav_url']);
|
||||
$server_path = parse_url($server_url, PHP_URL_PATH);
|
||||
$calId = $this->cal->generate_uid();
|
||||
$path = "/calendars/$source[caldav_user]/$calId";
|
||||
|
||||
self::debug_log("Creating new calendar \"$cal[name]\" with path $path at: " . $server_url);
|
||||
$client = new caldav_client($server_url, $source["caldav_user"], $source["caldav_pass"]);
|
||||
|
||||
if($client->create_calendar($server_path . $path, $cal['name'], isset($cal['color']) ? $cal['color'] : 'cc0000')) {
|
||||
$calendars = $this->_autodiscover_calendars($source);
|
||||
return $this->_add_calendars($calendars, $source);
|
||||
}
|
||||
}
|
||||
}
|
||||
private function _add_calendars($calendars, $source) {
|
||||
$cal_ids = array();
|
||||
|
||||
$result = false;
|
||||
if($calendars)
|
||||
{
|
||||
$result = true;
|
||||
|
@ -186,13 +248,13 @@ class caldav_driver extends calendar_driver
|
|||
$result = $this->rc->db->query("SELECT * FROM ".$this->db_calendars." WHERE user_id=? and caldav_url LIKE ?", $this->rc->user->ID, $calendar['href']);
|
||||
if($this->rc->db->affected_rows($result)) continue;
|
||||
|
||||
$cal['caldav_url'] = self::_encode_url($calendar['href']);
|
||||
$cal = array(
|
||||
'caldav_url' => self::_encode_url($calendar['href']),
|
||||
'name' => $calendar['name'],
|
||||
'color' => $calendar['color']
|
||||
);
|
||||
|
||||
// Respect $props['name'] if only a single calendar was found e.g. no auto-discovery.
|
||||
if(sizeof($calendars) > 1 || !isset($cal['name']) || $cal['name'] == "")
|
||||
$cal['name'] = $calendar['name'];
|
||||
|
||||
if (($obj_id = $this->_db_create_calendar($cal)) !== false) {
|
||||
if (($obj_id = $this->_db_create_calendar($cal, $source)) !== false) {
|
||||
array_push($cal_ids, $obj_id);
|
||||
} else $result = false;
|
||||
}
|
||||
|
@ -213,6 +275,48 @@ class caldav_driver extends calendar_driver
|
|||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds CalDAV source and loads adds all calendars from that source
|
||||
*
|
||||
* @see database_driver::create_calendar()
|
||||
*/
|
||||
public function create_source($source)
|
||||
{
|
||||
$source['caldav_url'] = self::_encode_url($source['caldav_url']);
|
||||
|
||||
try {
|
||||
$calendars = $this->_autodiscover_calendars($source);
|
||||
}
|
||||
catch(Exception $e) {
|
||||
self::debug_log("Could not add source: $source");
|
||||
}
|
||||
if($calendars) {
|
||||
$pass = isset($source['caldav_pass']) ? $this->_encrypt_pass($source['caldav_pass']) : null;
|
||||
$db_source_result = $this->rc->db->query(
|
||||
"INSERT INTO " . $this->db_sources . "
|
||||
(user_id, caldav_url, caldav_user, caldav_pass)
|
||||
VALUES (?, ?, ?, ?)",
|
||||
$this->rc->user->ID,
|
||||
$source['caldav_url'],
|
||||
isset($source['caldav_user']) ? $source['caldav_user'] : null,
|
||||
$pass
|
||||
);
|
||||
|
||||
if($db_source_result)
|
||||
$source['source_id'] = $this->rc->db->insert_id($this->db_sources);
|
||||
else {
|
||||
self::debug_log("Could not save source $source[caldav_url] to db");
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->_add_calendars($calendars, $source);
|
||||
}
|
||||
else {
|
||||
self::debug_log("Did not find any calendars at $source[caldav_url]. Aborting");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new calendar assigned to the current user
|
||||
*
|
||||
|
@ -226,20 +330,19 @@ class caldav_driver extends calendar_driver
|
|||
*
|
||||
* @return mixed ID of the calendar on success, False on error
|
||||
*/
|
||||
private function _db_create_calendar($prop)
|
||||
private function _db_create_calendar($prop, $source)
|
||||
{
|
||||
$result = $this->rc->db->query(
|
||||
"INSERT INTO " . $this->db_calendars . "
|
||||
(user_id, name, color, showalarms, caldav_url, caldav_tag, caldav_user, caldav_pass)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(user_id, source_id, name, color, showalarms, caldav_url, caldav_tag)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
$this->rc->user->ID,
|
||||
$source['source_id'],
|
||||
$prop['name'],
|
||||
$prop['color'],
|
||||
$prop['showalarms']?1:0,
|
||||
$prop['caldav_url'],
|
||||
isset($prop["caldav_tag"]) ? $prop["caldav_tag"] : null,
|
||||
isset($prop["caldav_user"]) ? $prop["caldav_user"] : null,
|
||||
isset($prop["caldav_pass"]) ? $this->_encrypt_pass($prop["caldav_pass"]) : null
|
||||
isset($prop["caldav_tag"]) ? $prop["caldav_tag"] : null
|
||||
);
|
||||
|
||||
if ($result)
|
||||
|
@ -256,31 +359,16 @@ class caldav_driver extends calendar_driver
|
|||
public function edit_calendar($cal)
|
||||
{
|
||||
$query = $this->rc->db->query("UPDATE " . $this->db_calendars . "
|
||||
SET name=?, color=?, showalarms=?, caldav_url=?, caldav_tag=?, caldav_user=?
|
||||
SET name=?, color=?, showalarms=?
|
||||
WHERE calendar_id=?
|
||||
AND user_id=?",
|
||||
$cal['name'],
|
||||
$cal['color'],
|
||||
$cal['showalarms']?1:0,
|
||||
$cal['caldav_url'],
|
||||
isset($cal["caldav_tag"]) ? $cal["caldav_tag"] : null,
|
||||
isset($cal["caldav_user"]) ? $cal["caldav_user"] : null,
|
||||
$cal['id'],
|
||||
$this->rc->user->ID
|
||||
);
|
||||
|
||||
// Change password if specified
|
||||
if (isset($cal["caldav_pass"])) {
|
||||
$query = $this->rc->db->query("UPDATE " . $this->db_calendars . "
|
||||
SET caldav_pass=?
|
||||
WHERE calendar_id=?
|
||||
AND user_id=?",
|
||||
$this->_encrypt_pass($cal['caldav_pass']),
|
||||
$cal['id'],
|
||||
$this->rc->user->ID
|
||||
);
|
||||
}
|
||||
|
||||
return $this->rc->db->affected_rows($query);
|
||||
}
|
||||
|
||||
|
@ -303,16 +391,32 @@ class caldav_driver extends calendar_driver
|
|||
}
|
||||
|
||||
/**
|
||||
* Delete the given calendar with all its contents
|
||||
* Delete the given calendar or source with all its contents
|
||||
* (we use the same function for both calendar and source to stay compatible with other drivers)
|
||||
*
|
||||
* @see calendar_driver::delete_calendar()
|
||||
*/
|
||||
public function delete_calendar($prop)
|
||||
{
|
||||
// events and attachments will be deleted by foreign key cascade
|
||||
|
||||
if(isset($prop['delete-source'])) {
|
||||
unset($prop['delete-source']);
|
||||
$count = 0;
|
||||
foreach($prop as $url) {
|
||||
self::debug_log("Deleting source id $url");
|
||||
$query = $this->rc->db->query("DELETE FROM " . $this->db_sources . " WHERE source_id=?", $url);
|
||||
$count += $this->rc->db->affected_rows($query);
|
||||
}
|
||||
self::debug_log("Deleted $count entries");
|
||||
return $count;
|
||||
}
|
||||
else {
|
||||
if(!$this->calendars[$prop['id']])
|
||||
return false;
|
||||
|
||||
// events and attachments will be deleted by foreign key cascade
|
||||
if(!$this->sync_clients[$prop['id']]->delete_calendar())
|
||||
return false;
|
||||
|
||||
$query = $this->rc->db->query(
|
||||
"DELETE FROM " . $this->db_calendars . " WHERE calendar_id=?",
|
||||
|
@ -320,6 +424,8 @@ class caldav_driver extends calendar_driver
|
|||
);
|
||||
|
||||
return $this->rc->db->affected_rows($query);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -349,8 +455,10 @@ class caldav_driver extends calendar_driver
|
|||
if (!empty($this->calendars)) {
|
||||
if ($event['calendar'] && !$this->calendars[$event['calendar']])
|
||||
return false;
|
||||
if (!$event['calendar'])
|
||||
$event['calendar'] = reset(array_keys($this->calendars));
|
||||
if (!$event['calendar']) {
|
||||
$keys = array_keys($this->calendars);
|
||||
$event['calendar'] = reset($keys);
|
||||
}
|
||||
|
||||
if($event = $this->_save_preprocess($event)) {
|
||||
|
||||
|
@ -605,7 +713,7 @@ class caldav_driver extends calendar_driver
|
|||
$recurrence_id_format = libcalendaring::recurrence_id_format($event);
|
||||
foreach ($exceptions as $exception) {
|
||||
$recurrence_id = rcube_utils::anytodatetime($exception['_instance'], $old['start']->getTimezone());
|
||||
if (is_a($recurrence_id, 'DateTime')) {
|
||||
if (is_a($recurrence_id, 'DateTime') || is_a($recurrence_id, 'DateTimeImmutable')) {
|
||||
$recurrence_id->add($date_shift);
|
||||
$exception['_instance'] = $recurrence_id->format($recurrence_id_format);
|
||||
$this->_update_event($exception, false);
|
||||
|
@ -696,7 +804,7 @@ class caldav_driver extends calendar_driver
|
|||
foreach (self::$scheduling_properties as $prop) {
|
||||
$a = $old[$prop];
|
||||
$b = $event[$prop];
|
||||
if ($event['allday'] && ($prop == 'start' || $prop == 'end') && $a instanceof DateTime && $b instanceof DateTime) {
|
||||
if ($event['allday'] && ($prop == 'start' || $prop == 'end') && ($a instanceof DateTime || $a instanceof DateTimeImmutable) && ($b instanceof DateTime || $b instanceof DateTimeImmutable)) {
|
||||
$a = $a->format('Y-m-d');
|
||||
$b = $b->format('Y-m-d');
|
||||
}
|
||||
|
@ -811,7 +919,7 @@ class caldav_driver extends calendar_driver
|
|||
$sql_set = array();
|
||||
$set_cols = array('start', 'end', 'all_day', 'recurrence_id', 'isexception', 'sequence', 'title', 'description', 'location', 'categories', 'url', 'free_busy', 'priority', 'sensitivity', 'status', 'attendees', 'alarms', 'notifyat', 'caldav_url', 'caldav_tag');
|
||||
foreach ($set_cols as $col) {
|
||||
if (is_object($event[$col]) && is_a($event[$col], 'DateTime'))
|
||||
if (is_object($event[$col]) && (is_a($event[$col], 'DateTime') || is_a($event[$col], 'DateTimeImmutable')))
|
||||
$sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote($event[$col]->format(self::DB_DATE_FORMAT));
|
||||
else if (is_array($event[$col]))
|
||||
$sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote(join(',', $event[$col]));
|
||||
|
@ -1209,12 +1317,25 @@ class caldav_driver extends calendar_driver
|
|||
$calendar_ids = array_intersect($calendars, array_keys($this->calendars));
|
||||
|
||||
// Make sure that the calendars are in sync.
|
||||
try {
|
||||
foreach($calendar_ids as $cal_id) {
|
||||
if(!$this->_is_synced($cal_id))
|
||||
$this->_sync_calendar($cal_id);
|
||||
}
|
||||
}
|
||||
catch(Exception $err) {
|
||||
self::debug_log("Could not sync calendars: $err");
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->_db_load_events($start, $end, $query, $calendars, $virtual, $modifiedsince);
|
||||
$events = $this->_db_load_events($start, $end, $query, $calendars, $virtual, $modifiedsince);
|
||||
|
||||
// add events from the address books birthday calendar
|
||||
if (in_array(self::BIRTHDAY_CALENDAR_ID, $calendars) && empty($query)) {
|
||||
$events = array_merge($events, $this->load_birthday_events($start, $end, null, $modifiedsince));
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1582,7 +1703,7 @@ class caldav_driver extends calendar_driver
|
|||
private function serialize_alarms($valarms)
|
||||
{
|
||||
foreach ((array)$valarms as $i => $alarm) {
|
||||
if ($alarm['trigger'] instanceof DateTime) {
|
||||
if ($alarm['trigger'] instanceof DateTime || $alarm['trigger'] instanceof DateTimeImmutable) {
|
||||
$valarms[$i]['trigger'] = '@' . $alarm['trigger']->format('c');
|
||||
}
|
||||
}
|
||||
|
@ -1632,7 +1753,7 @@ class caldav_driver extends calendar_driver
|
|||
$attendees = json_decode($s_attendees, true);
|
||||
} // decode the old serialization format
|
||||
else {
|
||||
foreach (explode("\n", $event['attendees']) as $line) {
|
||||
foreach (explode("\n", $s_attendees) as $line) {
|
||||
$att = array();
|
||||
foreach (rcube_utils::explode_quoted_string(';', $line) as $prop) {
|
||||
list($key, $value) = explode("=", $prop);
|
||||
|
@ -1670,13 +1791,15 @@ class caldav_driver extends calendar_driver
|
|||
}
|
||||
}
|
||||
|
||||
foreach (array($this->db_calendars, 'itipinvitations') as $table) {
|
||||
foreach (array($this->db_sources, $this->db_calendars, 'itipinvitations') as $table) {
|
||||
$db->query("DELETE FROM $table WHERE user_id=?", $user->ID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function to produce driver-specific calendar create/edit form
|
||||
* We are misusing calendar_form() for sources to stay compatible with the other drivers
|
||||
* and not having to change too much of the original code
|
||||
*
|
||||
* @param string Request action 'form-edit|form-new'
|
||||
* @param array Calendar properties (e.g. id, color)
|
||||
|
@ -1686,44 +1809,66 @@ class caldav_driver extends calendar_driver
|
|||
*/
|
||||
public function calendar_form($action, $calendar, $formfields)
|
||||
{
|
||||
// Make sure we have current attributes
|
||||
$calendar = $this->calendars[$calendar["id"]];
|
||||
switch($action) {
|
||||
case "form-source-new":
|
||||
array_splice($formfields, 0);
|
||||
|
||||
$input_caldav_url = new html_inputfield( array(
|
||||
"name" => "caldav_url",
|
||||
"id" => "caldav_url",
|
||||
"size" => 20
|
||||
));
|
||||
|
||||
$formfields["caldav_url"] = array(
|
||||
"label" => $this->cal->gettext("caldavurl"),
|
||||
"value" => $input_caldav_url->show($calendar["caldav_url"]),
|
||||
"label" => $this->cal->gettext("url"),
|
||||
"value" => $input_caldav_url->show(null),
|
||||
"id" => "caldav_url",
|
||||
);
|
||||
|
||||
|
||||
$input_caldav_user = new html_inputfield( array(
|
||||
"name" => "caldav_user",
|
||||
"id" => "caldav_user",
|
||||
"size" => 20
|
||||
));
|
||||
|
||||
$formfields["caldav_user"] = array(
|
||||
"label" => $this->cal->gettext("username"),
|
||||
"value" => $input_caldav_user->show($calendar["caldav_user"]),
|
||||
"value" => $input_caldav_user->show(null),
|
||||
"id" => "caldav_user",
|
||||
);
|
||||
|
||||
|
||||
$input_caldav_pass = new html_passwordfield( array(
|
||||
"name" => "caldav_pass",
|
||||
"id" => "caldav_pass",
|
||||
"size" => 20
|
||||
));
|
||||
|
||||
$formfields["caldav_pass"] = array(
|
||||
"label" => $this->cal->gettext("password"),
|
||||
"value" => $input_caldav_pass->show(null), // Don't send plain text password to GUI
|
||||
"value" => $input_caldav_pass->show(null),
|
||||
"id" => "caldav_pass",
|
||||
);
|
||||
break;
|
||||
case "form-source-delete":
|
||||
array_splice($formfields, 0);
|
||||
|
||||
$result = $this->rc->db->query("SELECT source_id, caldav_url FROM ".$this->db_sources);
|
||||
if($this->rc->db->num_rows($result)) {
|
||||
for($i=0; $source = $this->rc->db->fetch_assoc($result); ++$i) {
|
||||
$checkbox = new html_checkbox( array(
|
||||
"name" => "delete$i",
|
||||
"value" => $source['source_id']
|
||||
));
|
||||
|
||||
$formfields[$source['source_id']] = array(
|
||||
"label" => $source['caldav_url'],
|
||||
"value" => $checkbox->show(null),
|
||||
"id" => "caldav_url",
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return parent::calendar_form($action, $calendar, $formfields);
|
||||
}
|
||||
|
@ -1747,22 +1892,6 @@ class caldav_driver extends calendar_driver
|
|||
else return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand all "%p" occurrences in 'caldav_pass' element of calendar object
|
||||
* properties array with RC (imap) password.
|
||||
* Other elements are left untouched.
|
||||
*
|
||||
* @param array List of properties
|
||||
* @return array List of properties, with expanded 'caldav_pass' attribute
|
||||
*
|
||||
*/
|
||||
private function _expand_pass($props)
|
||||
{
|
||||
if (isset($props['caldav_pass']))
|
||||
$props['caldav_pass'] = str_replace('%p', $this->rc->get_user_password(), $props['caldav_pass']);
|
||||
|
||||
return $props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto discover calenders available to the user on the caldav server
|
||||
|
@ -1779,9 +1908,9 @@ class caldav_driver extends calendar_driver
|
|||
$calendars = array();
|
||||
$current_user_principal = array('{DAV:}current-user-principal');
|
||||
$calendar_home_set = array('{urn:ietf:params:xml:ns:caldav}calendar-home-set');
|
||||
$cal_attribs = array('{DAV:}resourcetype', '{DAV:}displayname');
|
||||
$cal_attribs = array('{DAV:}resourcetype', '{DAV:}displayname', '{http://apple.com/ns/ical/}calendar-color');
|
||||
|
||||
require_once ($this->cal->home.'/lib/caldav-client.php');
|
||||
require_once 'caldav_client.php';
|
||||
$caldav = new caldav_client($props["caldav_url"], $props["caldav_user"], $props["caldav_pass"]);
|
||||
|
||||
$tokens = parse_url($props["caldav_url"]);
|
||||
|
@ -1793,7 +1922,7 @@ class caldav_driver extends calendar_driver
|
|||
return false;
|
||||
}
|
||||
else if (array_key_exists ('{DAV:}resourcetype', $response) &&
|
||||
$response['{DAV:}resourcetype'] instanceof OldSabre\DAV\Property\ResourceType &&
|
||||
$response['{DAV:}resourcetype'] instanceof Sabre\DAV\Xml\Property\ResourceType &&
|
||||
in_array('{urn:ietf:params:xml:ns:caldav}calendar',
|
||||
$response['{DAV:}resourcetype']->getValue())) {
|
||||
|
||||
|
@ -1810,22 +1939,25 @@ class caldav_driver extends calendar_driver
|
|||
// directly return given url as it is a calendar
|
||||
}
|
||||
// probe further for principal url and user home set
|
||||
$caldav_url = $base_uri . $response[$current_user_principal[0]];
|
||||
// $caldav_url = $base_uri . $response[$current_user_principal[0]]; //### I guess this was the format that "OldSabre" in the original library used
|
||||
$caldav_url = $base_uri . $response[$current_user_principal[0]][0]['value'];
|
||||
$response = $caldav->prop_find($caldav_url, $calendar_home_set, 0);
|
||||
if (!$response) {
|
||||
$this->_raise_error("Resource \"$caldav_url\" contains no calendars.");
|
||||
return false;
|
||||
}
|
||||
$caldav_url = $base_uri . $response[$calendar_home_set[0]];
|
||||
// $caldav_url = $base_uri . $response[$calendar_home_set[0]]; //### I guess this was the format that "OldSabre" in the original library used
|
||||
$caldav_url = $base_uri . $response[$calendar_home_set[0]][0]['value'];
|
||||
$response = $caldav->prop_find($caldav_url, $cal_attribs, 1);
|
||||
foreach($response as $collection => $attribs)
|
||||
{
|
||||
$found = false;
|
||||
$name = '';
|
||||
$color = null;
|
||||
foreach($attribs as $key => $value)
|
||||
{
|
||||
if ($key == '{DAV:}resourcetype' && is_object($value)) {
|
||||
if ($value instanceof OldSabre\DAV\Property\ResourceType) {
|
||||
if ($value instanceof Sabre\DAV\Xml\Property\ResourceType) {
|
||||
$values = $value->getValue();
|
||||
if (in_array('{urn:ietf:params:xml:ns:caldav}calendar', $values))
|
||||
$found = true;
|
||||
|
@ -1834,11 +1966,28 @@ class caldav_driver extends calendar_driver
|
|||
else if ($key == '{DAV:}displayname') {
|
||||
$name = $value;
|
||||
}
|
||||
else if ($key == '{http://apple.com/ns/ical/}calendar-color') {
|
||||
//Thanks to https://github.com/agendav/agendav/blob/10397b6c04a52acd6cb9528683f9e167d516cdb5/web/src/CalDAV/Resource/Calendar.php#L264
|
||||
if (strlen($value) === 7) {
|
||||
return $value . 'ff';
|
||||
}
|
||||
|
||||
if (strlen($value) === 4) {
|
||||
preg_match('/#(.)(.)(.)/', $value, $matches);
|
||||
return '#' .
|
||||
$matches[1] .$matches[1] .
|
||||
$matches[2] .$matches[2] .
|
||||
$matches[3] .$matches[3] .
|
||||
'ff';
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if ($found) {
|
||||
array_push($calendars, array(
|
||||
'name' => $name,
|
||||
'href' => $base_uri.$collection,
|
||||
'color' => $color ?: 'cc0000'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -2024,13 +2173,13 @@ class caldav_driver extends calendar_driver
|
|||
|
||||
private function _decrypt_pass($pass) {
|
||||
$p = base64_decode($pass);
|
||||
$e = new Encryption(MCRYPT_BlOWFISH, MCRYPT_MODE_CBC);
|
||||
return $e->decrypt($p, $this->crypt_key);
|
||||
$e = new Encryption();
|
||||
return $e->decrypt($p);
|
||||
}
|
||||
|
||||
private function _encrypt_pass($pass) {
|
||||
$e = new Encryption(MCRYPT_BlOWFISH, MCRYPT_MODE_CBC);
|
||||
$p = $e->encrypt($pass, $this->crypt_key);
|
||||
$e = new Encryption();
|
||||
$p = $e->encrypt($pass);
|
||||
return base64_encode($p);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,14 +21,10 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
require_once (dirname(__FILE__).'/../../lib/caldav-client.php');
|
||||
require_once 'caldav_client.php';
|
||||
|
||||
class caldav_sync
|
||||
{
|
||||
const ACTION_NONE = 1;
|
||||
const ACTION_UPDATE = 2;
|
||||
const ACTION_CREATE = 4;
|
||||
|
||||
private $cal_id = null;
|
||||
private $ctag = null;
|
||||
private $username = null;
|
||||
|
|
73
drivers/caldav/encryption.php
Normal file
73
drivers/caldav/encryption.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
/**
|
||||
* Encryption class
|
||||
* (Copied by JodliDev from https://github.com/mstilkerich/rcmcarddav/blob/master/carddav.php)
|
||||
*
|
||||
* @author Jorge López Pérez <jorge@adobo.org> (original author)
|
||||
* @author JodliDev <jodlidev@gmail.com>
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
class Encryption {
|
||||
private function getDesKey()
|
||||
{
|
||||
$rcube = rcube::get_instance();
|
||||
$imap_password = $rcube->decrypt((string) $_SESSION['password']);
|
||||
|
||||
if ($imap_password === false || strlen($imap_password) == 0) {
|
||||
throw new \Exception('No password available to use for encryption');
|
||||
}
|
||||
|
||||
while (strlen($imap_password) < 24) {
|
||||
$imap_password .= $imap_password;
|
||||
}
|
||||
return substr($imap_password, 0, 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a password to storage format according to the password storage scheme setting.
|
||||
*
|
||||
* @param string $clear The password in clear text.
|
||||
* @return string The password in storage format (e.g. encrypted with user password as key)
|
||||
* @throws Exception
|
||||
*/
|
||||
public function encrypt($clear) {
|
||||
// encrypted with IMAP password
|
||||
$rcube = rcube::get_instance();
|
||||
|
||||
$imap_password = $this->getDesKey();
|
||||
$rcube->config->set('carddav_des_key', $imap_password);
|
||||
|
||||
$crypted = $rcube->encrypt($clear, 'carddav_des_key');
|
||||
|
||||
// there seems to be no way to unset a preference
|
||||
$rcube->config->set('carddav_des_key', '');
|
||||
|
||||
if ($crypted === false) {
|
||||
throw new \Exception('Password encryption with user password failed');
|
||||
}
|
||||
|
||||
return $crypted;
|
||||
}
|
||||
|
||||
public function decrypt($crypt) {
|
||||
try {
|
||||
$rcube = rcube::get_instance();
|
||||
|
||||
$imap_password = $this->getDesKey();
|
||||
$rcube->config->set('carddav_des_key', $imap_password);
|
||||
$clear = $rcube->decrypt($crypt, 'carddav_des_key');
|
||||
// there seems to be no way to unset a preference
|
||||
$rcube->config->set('carddav_des_key', '');
|
||||
if ($clear === false) {
|
||||
$clear = '';
|
||||
}
|
||||
|
||||
return $clear;
|
||||
} catch (\Exception $e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
|
@ -362,11 +362,11 @@ abstract class calendar_driver
|
|||
{
|
||||
$valid = true;
|
||||
|
||||
if (empty($event['start']) || !is_object($event['start']) || !is_a($event['start'], 'DateTime')) {
|
||||
if (empty($event['start']) || !is_object($event['start']) || (!is_a($event['start'], 'DateTime') && !is_a($event['start'], 'DateTimeImmutable'))) {
|
||||
$valid = false;
|
||||
}
|
||||
|
||||
if (empty($event['end']) || !is_object($event['end']) || !is_a($event['end'], 'DateTime')) {
|
||||
if (empty($event['end']) || !is_object($event['end']) || (!is_a($event['end'], 'DateTime') && !is_a($event['end'], 'DateTimeImmutable'))) {
|
||||
$valid = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*
|
||||
* For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
|
||||
*/
|
||||
$labels['addsources'] = 'Externe Quellen hinzufügen ';
|
||||
$labels['deletesources'] = 'Externe Quellen löschen ';
|
||||
$labels['default_view'] = 'Standardansicht';
|
||||
$labels['time_format'] = 'Zeitformatierung';
|
||||
$labels['timeslots'] = 'Zeitfenster pro Stunde';
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*
|
||||
* For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
|
||||
*/
|
||||
$labels['addsources'] = 'Externe Quellen hinzufügen ';
|
||||
$labels['deletesources'] = 'Externe Quellen löschen ';
|
||||
$labels['default_view'] = 'Standardansicht';
|
||||
$labels['time_format'] = 'Zeitformatierung';
|
||||
$labels['timeslots'] = 'Abschnitte pro Stunde';
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*
|
||||
* For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
|
||||
*/
|
||||
$labels['addsources'] = 'Externe Quellen hinzufügen ';
|
||||
$labels['deletesources'] = 'Externe Quellen löschen ';
|
||||
$labels['default_view'] = 'Standardansicht';
|
||||
$labels['time_format'] = 'Zeitformatierung';
|
||||
$labels['timeslots'] = 'Zeitfenster pro Stunde';
|
||||
|
|
|
@ -10,6 +10,10 @@
|
|||
|
||||
$labels = array();
|
||||
|
||||
//caldav driver
|
||||
$labels['addsources'] = 'Add external sources ';
|
||||
$labels['deletesources'] = 'Delete external sources ';
|
||||
|
||||
// preferences
|
||||
$labels['default_view'] = 'Default view';
|
||||
$labels['time_format'] = 'Time format';
|
||||
|
|
|
@ -141,6 +141,10 @@
|
|||
<div id="calendaractions-menu" class="popupmenu">
|
||||
<h3 id="aria-label-calendaroptions" class="voice"><roundcube:label name="calendar.calendaractions" /></h3>
|
||||
<ul class="menu listing" role="menu" aria-labelledby="aria-label-calendaroptions">
|
||||
<roundcube:if condition="env:calendar_driver == 'caldav'" />
|
||||
<roundcube:button type="link-menuitem" command="calendar-sources-new" label="calendar.addsources" class="create" />
|
||||
<roundcube:button type="link-menuitem" command="calendar-sources-delete" label="calendar.deletesources" class="delete" />
|
||||
<roundcube:endif />
|
||||
<roundcube:button type="link-menuitem" command="calendar-create" label="calendar.addcalendar" class="create disabled" classAct="create active" />
|
||||
<roundcube:button type="link-menuitem" command="calendar-edit" label="calendar.editcalendar" class="edit disabled" classAct="edit active" />
|
||||
<roundcube:button type="link-menuitem" command="calendar-delete" label="calendar.deletecalendar" class="delete disabled" classAct="delete active" />
|
||||
|
|
|
@ -68,6 +68,10 @@
|
|||
<div id="calendaroptionsmenu" class="popupmenu" aria-hidden="true">
|
||||
<h3 id="aria-label-calendaroptions" class="voice"><roundcube:label name="calendar.calendaractions" /></h3>
|
||||
<ul id="calendaroptionsmenu-menu" class="toolbarmenu" role="menu" aria-labelledby="aria-label-calendaroptions">
|
||||
<roundcube:if condition="env:calendar_driver == 'caldav'" />
|
||||
<li role="menuitem"><roundcube:button type="link" command="calendar-sources-new" label="calendar.addsources" class="active" /></li>
|
||||
<li role="menuitem"><roundcube:button type="link" command="calendar-sources-delete" label="calendar.deletesources" class="active" /></li>
|
||||
<roundcube:endif />
|
||||
<li role="menuitem"><roundcube:button type="link" command="calendar-edit" label="calendar.edit" classAct="active" /></li>
|
||||
<li role="menuitem"><roundcube:button type="link" command="calendar-delete" label="delete" classAct="active" /></li>
|
||||
<roundcube:if condition="env:calendar_driver == 'kolab'" />
|
||||
|
|
Loading…
Reference in a new issue