Compare commits

..

4 commits

Author SHA1 Message Date
JodliDev d99cddad2d Update calendar_ui.php & calendar.php 2021-08-17 18:30:24 +02:00
JodliDev 23191c9c8b Missed some mentions of "driver" that should be relevant for CalDav 2021-08-17 18:30:15 +02:00
JodliDev a90ebffdc1 Adapt composer.json
(cherry picked from commit 9a30a1e41e1240391c282ce0785ee680a908cab8)
2021-08-17 18:30:02 +02:00
JodliDev 49b078e3ff Incorporate https://github.com/fasterit/roundcube_calendar
(cherry picked from commit 2daa59c7ddbf367d010668f65a45278b8e16b869)
2021-08-17 18:29:47 +02:00
36 changed files with 3546 additions and 1525 deletions

81
README Normal file
View file

@ -0,0 +1,81 @@
A calendar module for Roundcube
-------------------------------
This plugin currently supports a local database as well as a Kolab groupware
server as backends for calendar and event storage. For both drivers, some
initialization of the local database is necessary. To do so, execute the
SQL commands in drivers/<yourchoice>/SQL/<yourdatabase>.initial.sql
For some general calendar-based operations such as alarms handling or iCal
parsing/exporting and UI widgets/style this plugins requires the `libcalendaring`
and `libkolab` plugins which are also part of the Kolab Roundcube Plugins repository.
Make sure these plugins are installed and configured correctly.
For recurring event computation, some utility classes from the Horde project
are used. They are packaged in a slightly modified version with this plugin.
REQUIREMENTS
------------
Some functions are shared with other plugins and therefore being moved to
library plugins. Thus in order to run the calendar plugin, you also need the
following plugins installed:
* kolab/libcalendaring [1]
* kolab/libkolab [1]
INSTALLATION
------------
For a manual installation of the calendar plugin (and its dependencies),
execute the following steps. This will set it up with the database backend
driver.
1. Get the source from git
$ cd /tmp
$ git clone https://git.kolab.org/diffusion/RPK/roundcubemail-plugins-kolab.git
$ cd /<path-to-roundcube>/plugins
$ cp -r /tmp/roundcubemail-plugins-kolab/plugins/calendar .
$ cp -r /tmp/roundcubemail-plugins-kolab/plugins/libcalendaring .
$ cp -r /tmp/roundcubemail-plugins-kolab/plugins/libkolab .
2. Create calendar plugin configuration
$ cd calendar/
$ cp config.inc.php.dist config.inc.php
$ edit config.inc.php
3. Initialize the calendar database tables
$ cd ../../
$ bin/initdb.sh --dir=plugins/calendar/drivers/database/SQL
4. Build css styles for the Elastic skin
$ lessc --relative-urls -x plugins/libkolab/skins/elastic/libkolab.less > plugins/libkolab/skins/elastic/libkolab.min.css
5. Enable the calendar plugin
$ edit config/config.inc.php
Add 'calendar' to the list of active plugins:
$config['plugins'] = array(
(...)
'calendar',
);
IMPORTANT
---------
This plugin doesn't work with the Classic skin of Roundcube because no
templates are available for that skin.
Use Roundcube `skins_allowed` option to limit skins available to the user
or remove incompatible skins from the skins folder.
[1] https://git.kolab.org/diffusion/RPK/

View file

@ -1,68 +0,0 @@
## TLDR
Contrary to other CalDAV forks, this one is based on the original calendar kolab-roundcube-plugins-mirror/calendar (which means the calendar itself is most up to date) and adds CalDAV capability on top of it. As far as I am aware, it is the most up to date version with the most bugfixes (April 2022).
(installation instructions are at the bottom)
## Why is this needed?
Unfortunately, the current situation about CalDAV support in roundcube is quite confusing. There are several plugins (/Forks) around that have CalDAV support but from what I found, all of them are slightly buggy or do not work anymore.
All of them are based on <https://gitlab.awesome-it.de/kolab/roundcube-plugins>, a very old Fork which (as far as I can tell) is based on a version of kolab-roundcube-plugins-mirror/calendar that is over 10 years old.
None of these forks incorporate updates from the original calendar, meaning it is only a matter of time until they are not compatible with roundcube anymore. I tried to change as little as possible in the original codebase and only added caldav support as a new driver - which means new updates from the roundcube team should be easy to incorporate.
## History of other Forks so far
### kolab-roundcube-plugins-mirror/calendar :
This is the original calendar that all other forks are based on. It is working very well and is actively maintained but unfortunately, it does not have caldav support
### [https://gitlab.awesome-it.de/kolab/roundcube-plugins](awesome-it) :
This is the "original fork" of the calendar. A lot of work was put into it and caldav is almost fully implemented. Unfortunately it has a few bugs / problems and most of them were not fixed in any other forks:
- The birthday calendar is not supported by the caldav driver.
- While the backend (mostly) supports adding all calendars from a dav-url, the front-end does not. That makes calendar handling a bit clunky and confusing.
- Calendar colors have to be set manually and can not be loaded from DAV.
- Adding and Removing calendars directly in the external source is not supported.
- It prepares the codebase so multiple drivers can be used. But as far as I can tell, this feature is not used in the code and also not really supported by the front-end. This means, that it still only uses one driver but as a result adds a lot of unnecessary changes to the original codebase.
### fasterit/roundcube_calendar :
A fork of awesome-it to make it work with blind-coder/rcmcardav (a CardDAV plugin) by packing the outdated version of sabre/DAV inside the plugin. But it hasn't been maintained and is still based on a very outdated version of kolab-roundcube-plugins-mirror/calendar.
### texxasrulez/calendar :
This is a fork of awesome-it with a few bugfixes to make it work with roundcube 1.3 but its maintainer does not seem to be active anymore.
It is the most current fork of the original CalDAV fork. But unfortunately, it is treated as its own project (which means that it doesn't have any updates from the original calendar) and is focused primarily on nextcloud (which I don't really understand since nextcloud is using CalDAV anyway).
Also, on top of still having the original bugs included, it is also still based on an ancient sabre/DAV version.
### texxasrulez/caldav_calendar :
That one confuses me. It is from texxasrulez as well and seems to be the basis of Texxas but was abandoned in favour of texxasrulez/calendar. But it seems to be only a few commits behind texxasrulez/calendar.
### What is this fork doing differently?
All CalDAV forks are based on faster-it which has a very different codebase to the original calendar because of its unfinished "multiple-driver" support. That makes it very difficult to get updates from the original calendar.
So I decided to ditch the "multiple driver" support (which isnt used anywway) and keep most changes in the CalDAV driver itself to stay compatible with the original calendar. I also added a ton of updates:
- Based on the most recent version of the calendar plugin.
- Uses the most recent version of sabre/dav (4.1.5)
- Only minor changes in the existing code base, meaning that future updates of the calendar plugin should be able to be merged quite easily.
- Added support for the birthday calendar.
- Changed the behaviour from "per calendar" to "per CalDAV source".
- All calendars from a source will be automatically added.
- Calendars can be created and deleted directly at the CalDAV source.
- ics support included.
### Why does this need a fork of libcalendaring?
The original libcalendaring still uses sabre/vobject 3.5.3
In order to be compatible with other plugins (and because version 3.5.3 is ancient), I updated it to version 4.1.5
The problem is, that sabre/vobject makes use of DateTimeImmutable which libcalendaring does not expect.
It only needs minor changes to account for that, but unfortunately the roundcube-project does not accept pull requests...
### Installation
I havent published this as a plugin yet, so you have to instruct composer to install directly from github. Run the following commands in the roundcubemail folder
(If you get an error that the "API rate limit" has been exceeded and you need an GitHub OAuth token, just follow the instructions in the console - you will need a GitHub account).
```
cd /pathTo/roundcubemail
composer config repositories.calendar vcs https://github.com/JodliDev/calendar
composer config repositories.libcalendaring vcs https://github.com/JodliDev/libcalendaring
composer config minimum-stability dev
composer require kolab/calendar
bin/initdb.sh --dir=plugins/calendar/drivers/caldav/SQL
```

File diff suppressed because it is too large Load diff

View file

@ -72,12 +72,13 @@ function rcube_calendar(settings)
// handler for attachment-save-calendar commands // handler for attachment-save-calendar commands
this.save_to_calendar = function(p) this.save_to_calendar = function(p)
{ {
// TODO: show dialog to select the calendar for importing
if (this.selected_attachment && window.rcube_libcalendaring) { if (this.selected_attachment && window.rcube_libcalendaring) {
rcmail.http_post('calendar/mailimportattach', { rcmail.http_post('calendar/mailimportattach', {
_uid: rcmail.env.uid, _uid: rcmail.env.uid,
_mbox: rcmail.env.mailbox, _mbox: rcmail.env.mailbox,
_part: this.selected_attachment, _part: this.selected_attachment
_calendar: p // _calendar: $('#calendar-attachment-saveto').val(),
}, rcmail.set_busy(true, 'itip.savingdata')); }, rcmail.set_busy(true, 'itip.savingdata'));
} }
}; };
@ -92,7 +93,7 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
// register create-from-mail command to message_commands array // register create-from-mail command to message_commands array
if (rcmail.env.task == 'mail') { if (rcmail.env.task == 'mail') {
rcmail.register_command('calendar-create-from-mail', function() { cal.create_from_mail(); }); rcmail.register_command('calendar-create-from-mail', function() { cal.create_from_mail(); });
rcmail.register_command('attachment-save-calendar', function(p) { cal.save_to_calendar(p); }); rcmail.register_command('attachment-save-calendar', function() { cal.save_to_calendar(); });
if (rcmail.env.action != 'show') { if (rcmail.env.action != 'show') {
rcmail.env.message_commands.push('calendar-create-from-mail'); rcmail.env.message_commands.push('calendar-create-from-mail');

View file

@ -459,7 +459,7 @@ function rcube_calendar_ui(settings)
for (var j=0; j < num_attendees; j++) { for (var j=0; j < num_attendees; j++) {
data = event.attendees[j]; data = event.attendees[j];
if (data.email) { if (data.email) {
if (data.role != 'ORGANIZER' && is_this_me(data.email)) { if (data.role != 'ORGANIZER' && settings.identity.emails.indexOf(';'+data.email) >= 0) {
mystatus = (data.status || 'UNKNOWN').toLowerCase(); mystatus = (data.status || 'UNKNOWN').toLowerCase();
if (data.status == 'NEEDS-ACTION' || data.status == 'TENTATIVE' || data.rsvp) if (data.status == 'NEEDS-ACTION' || data.status == 'TENTATIVE' || data.rsvp)
rsvp = mystatus; rsvp = mystatus;
@ -2379,14 +2379,6 @@ function rcube_calendar_ui(settings)
add_attendee($.extend({ role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'RESOURCE' }, resource)); add_attendee($.extend({ role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'RESOURCE' }, resource));
} }
var is_this_me = function(email)
{
if (settings.identity.emails.indexOf(';'+email) >= 0 || settings.identity.ownedResources.indexOf(';'+email) >= 0) {
return true;
}
return false;
};
// when the user accepts or declines an event invitation // when the user accepts or declines an event invitation
var event_rsvp = function(response, delegate, replymode, event) var event_rsvp = function(response, delegate, replymode, event)
{ {
@ -2421,8 +2413,7 @@ function rcube_calendar_ui(settings)
attendees = []; attendees = [];
for (var data, i=0; i < me.selected_event.attendees.length; i++) { for (var data, i=0; i < me.selected_event.attendees.length; i++) {
data = me.selected_event.attendees[i]; data = me.selected_event.attendees[i];
//FIXME this can only work if there is a single resource per invitation if (settings.identity.emails.indexOf(';'+String(data.email).toLowerCase()) >= 0) {
if (is_this_me(String(data.email).toLowerCase())) {
data.status = response.toUpperCase(); data.status = response.toUpperCase();
data.rsvp = 0; // unset RSVP flag data.rsvp = 0; // unset RSVP flag
@ -2893,64 +2884,6 @@ function rcube_calendar_ui(settings)
return update_event_confirm('remove', event, { id:event.id, calendar:event.calendar, attendees:event.attendees }); 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) // opens a jquery UI dialog with event properties (or empty for creating a new calendar)
this.calendar_edit_dialog = function(calendar) this.calendar_edit_dialog = function(calendar)
{ {
@ -2958,11 +2891,11 @@ function rcube_calendar_ui(settings)
calendar = { name:'', color:'cc0000', editable:true, showalarms:true }; calendar = { name:'', color:'cc0000', editable:true, showalarms:true };
var title = rcmail.gettext((calendar.id ? 'editcalendar' : 'createcalendar'), 'calendar'), var title = rcmail.gettext((calendar.id ? 'editcalendar' : 'createcalendar'), 'calendar'),
params = {action: calendar.id ? 'form-edit' : 'form-new', c: {id: calendar.id}, _framed: 1}, params = {action: calendar.id ? 'form-edit' : 'form-new', c: {id: calendar.id}, driver: calendar.driver, _framed: 1},
$dialog = $('<iframe>').attr('src', rcmail.url('calendar', params)).on('load', function() { $dialog = $('<iframe>').attr('src', rcmail.url('calendar', params)).on('load', function() {
var contents = $(this).contents(); var contents = $(this).contents();
contents.find('#calendar-name') contents.find('#calendar-name')
.prop('disabled', !calendar.editable && !calendar.editable_name) .prop('disabled', !calendar.editable)
.val(calendar.editname || calendar.name) .val(calendar.editname || calendar.name)
.select(); .select();
contents.find('#calendar-color') contents.find('#calendar-color')
@ -2996,7 +2929,7 @@ function rcube_calendar_ui(settings)
data.id = calendar.id; data.id = calendar.id;
me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata'); me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
rcmail.http_post('calendar', { action:(calendar.id ? 'edit' : 'new'), c:data }); rcmail.http_post('calendar', { action:(calendar.id ? 'edit' : 'new'), c:data, driver: calendar.driver });
$dialog.dialog("close"); $dialog.dialog("close");
}; };
@ -3017,7 +2950,7 @@ function rcube_calendar_ui(settings)
{ {
var label = calendar.children ? 'deletecalendarconfirmrecursive' : 'deletecalendarconfirm'; var label = calendar.children ? 'deletecalendarconfirmrecursive' : 'deletecalendarconfirm';
rcmail.confirm_dialog(rcmail.gettext(label, 'calendar'), 'delete', function() { rcmail.confirm_dialog(rcmail.gettext(label, 'calendar'), 'delete', function() {
rcmail.http_post('calendar', { action:'delete', c:{ id:calendar.id } }); rcmail.http_post('calendar', { action:'delete', c:{ id:calendar.id }, driver: calendar.driver });
return true; return true;
}); });
@ -3534,7 +3467,7 @@ function rcube_calendar_ui(settings)
var brightness, select, id = cal.id; var brightness, select, id = cal.id;
me.calendars[id] = $.extend({ me.calendars[id] = $.extend({
url: rcmail.url('calendar/load_events', { source: id }), url: rcmail.url('calendar/load_events', { source: id, driver: cal.driver }),
id: id id: id
}, cal); }, cal);
@ -3616,7 +3549,7 @@ function rcube_calendar_ui(settings)
if (node && node.id && me.calendars[node.id]) { if (node && node.id && me.calendars[node.id]) {
me.select_calendar(node.id, true); me.select_calendar(node.id, true);
rcmail.enable_command('calendar-edit', 'calendar-showurl', 'calendar-showfburl', true); rcmail.enable_command('calendar-edit', 'calendar-showurl', 'calendar-showfburl', true);
rcmail.enable_command('calendar-delete', me.calendars[node.id].editable || me.calendars[node.id].deletable); rcmail.enable_command('calendar-delete', me.calendars[node.id].editable);
rcmail.enable_command('calendar-remove', me.calendars[node.id] && me.calendars[node.id].removable); rcmail.enable_command('calendar-remove', me.calendars[node.id] && me.calendars[node.id].removable);
} }
}); });
@ -4235,8 +4168,6 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
rcmail.register_command('print', function(){ cal.print_calendars(); }, true); rcmail.register_command('print', function(){ cal.print_calendars(); }, true);
// configure list operations // configure list operations
rcmail.register_command('calendar-sources-new', 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-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-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); rcmail.register_command('calendar-remove', function(){ cal.calendar_remove(cal.calendars[cal.selected_calendar]); }, false);

View file

@ -1,10 +1,10 @@
{ {
"name": "kolab/calendar", "name": "jodlidev/calendar",
"type": "roundcube-plugin", "type": "roundcube-plugin",
"description": "Calendar plugin", "description": "Calendar plugin with CalDav",
"homepage": "https://git.kolab.org/diffusion/RPK/", "homepage": "https://git.kolab.org/diffusion/RPK/",
"license": "AGPLv3", "license": "AGPLv3",
"version": "3.5.11", "version": "3.5.7.1",
"authors": [ "authors": [
{ {
"name": "Thomas Bruederli", "name": "Thomas Bruederli",
@ -21,18 +21,13 @@
{ {
"type": "composer", "type": "composer",
"url": "https://plugins.roundcube.net" "url": "https://plugins.roundcube.net"
},
{
"type": "vcs",
"url": "https://github.com/JodliDev/libcalendaring"
} }
], ],
"require": { "require": {
"php": ">=5.5", "php": ">=5.4.0",
"roundcube/plugin-installer": ">=0.1.3", "roundcube/plugin-installer": ">=0.1.3",
"jodlidev/libcalendaring": "dev-master", "kolab/libcalendaring": ">=3.4.0",
"kolab/libkolab": ">=3.4.0", "kolab/libkolab": ">=3.4.0"
"sabre/dav": ">=4.1.5"
}, },
"extra": { "extra": {
"roundcube": { "roundcube": {

View file

@ -25,18 +25,15 @@
+-------------------------------------------------------------------------+ +-------------------------------------------------------------------------+
*/ */
// backend type (database, kolab, caldav) // backend type (database, kolab, caldav, ical)
$config['calendar_driver'] = "caldav"; $config['calendar_driver'] = array("database", "caldav");
$config['calendar_driver_default'] = "caldav";
// Enable debugging output for iCAL/CalDAV drivers
$config['calendar_caldav_debug'] = false;
// default calendar view (agendaDay, agendaWeek, month) // default calendar view (agendaDay, agendaWeek, month)
$config['calendar_default_view'] = "agendaWeek"; $config['calendar_default_view'] = "agendaWeek";
// show a birthdays calendar from the user's address book(s) // show a birthdays calendar from the user's address book(s)
$config['calendar_contact_birthdays'] = false; $config['calendar_contact_birthdays'] = false;
$config['birthday_calendar'] = array('color' => 'fffb00');
// timeslots per hour (1, 2, 3, 4, 6) // timeslots per hour (1, 2, 3, 4, 6)
$config['calendar_timeslots'] = 2; $config['calendar_timeslots'] = 2;
@ -143,19 +140,13 @@ $config['kolab_invitation_calendars'] = false;
// %i - Calendar UUID // %i - Calendar UUID
// $config['calendar_caldav_url'] = 'http://%h/iRony/calendars/%u/%i'; // $config['calendar_caldav_url'] = 'http://%h/iRony/calendars/%u/%i';
// List of CalDAV sources that should be allready installed. // Crypt key to encrypt passwords for added iCAL/CalDAV calendars
// They will be added when the calendar section is accessed for the first time by a user. $config['calendar_crypt_key'] = "put some random string here";
// For 'caldav_user' and 'caldav_url' the following replacement variables are supported:
// %u - Current webmail user name // Set to false to allow CURL to connect with SSL hosts that it can't verify the certificates from
// For 'caldav_pass' %p is replaced by the current user's password. // e.g. for self-signed certificates.
// $config['calendar_caldav_preinstalled_sources'] = array( // technical note: This sets CURLOPT_SSL_VERIFYPEER _and_ CURLOPT_SSL_VERIFYHOST.
// 'name' => array( $config['calendar_curl_secure_ssl'] = true;
// 'caldav_user' => '%u',
// 'caldav_pass' => '%p',
// 'caldav_url' => 'https://example.net/dav',
// 'showAlarms' => 1
// )
// );
// Driver to provide a resource directory ('ldap' is the only implementation yet). // Driver to provide a resource directory ('ldap' is the only implementation yet).
// Leave empty or commented to disable resources support. // Leave empty or commented to disable resources support.
@ -164,9 +155,34 @@ $config['kolab_invitation_calendars'] = false;
// LDAP directory configuration to find avilable resources for events // LDAP directory configuration to find avilable resources for events
// $config['calendar_resources_directory'] = array(/* ldap_public-like address book configuration */); // $config['calendar_resources_directory'] = array(/* ldap_public-like address book configuration */);
// Enable debugging output for iCAL/CalDAV drivers
$config['calendar_caldav_debug'] = false;
$config['calendar_ical_debug'] = false;
// Enables displaying of free-busy URL with token-based authentication // Enables displaying of free-busy URL with token-based authentication
// Set it to the prefix URL, e.g. 'https://hostname/freebusy' or just '/freebusy'. // Set it to the prefix URL, e.g. 'https://hostname/freebusy' or just '/freebusy'.
// See freebusy_session_auth in configuration of kolab_auth plugin. // See freebusy_session_auth in configuration of kolab_auth plugin.
$config['calendar_freebusy_session_auth_url'] = null; $config['calendar_freebusy_session_auth_url'] = null;
// Pre-installed calendars, added at first access to calendar section
// Caldav driver is supported only
// $config['calendar_preinstalled_calendars'] = array(
// 'Caldav' => array(
// 'driver' => 'caldav',
// 'caldav_user' => '%u',
// 'caldav_pass' => '%p',
// 'caldav_url' => 'http://example.caldav.org/%u/calendar/',
// 'color' => 'cccc00',
// 'showAlarms' => 1),
// 'Other' => array(
// 'driver' => 'other',
// 'other_user' => 'user@example.other.org',
// 'other_pass' => 'password',
// 'other_url' => 'http://example.other.org/user@example.other.org/other',
// 'color' => 'cc0000',
// 'other_property1' => 'value1',
// 'other_property2' => 'value2',
// 'showAlarms' => 1));
?> ?>

View file

@ -1,75 +0,0 @@
<?php
/**
* Interface for different sync drivers
*
* @version @package_version@
* @author JodliDev <jodlidev@gmail.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/>.
*/
interface Isync {
/**
* Getter for current calendar ctag (only for CalDAV).
* @return string
*/
public function get_ctag();
/**
* Determines whether current calendar needs to be synced
*
* @return boolean True if the current calendar needs to be synced, false otherwise.
*/
public function is_synced();
/**
* Synchronizes given events with server and returns updates.
*
* @param array List of hash arrays with event properties, must include "caldav_url" and "tag".
* @return array Tuple containing the following lists:
*
* Caldav properties for events to be created or to be updated with the keys:
* url: Event ical URL relative to calendar URL
* etag: Remote etag of the event
* local_event: The local event in case of an update.
* remote_event: The current event retrieved from caldav server.
*
* A list of event ids that are in sync.
*/
public function get_updates($events);
/**
* Creates the given event.
*
* @param array Hash array with event properties.
* @return array with updated "caldav_url" and "caldav_tag" attributes, null on error.
*/
public function create_event($event);
/**
* Updates the given event.
*
* @param array Hash array with event properties to update, must include "uid", "caldav_url" and "caldav_tag".
* @return boolean True on success, false on error, -1 if the given event/etag is not up to date.
*/
public function update_event($event);
/**
* Removes the given event.
*
* @param array Hash array with events properties, must include "caldav_url".
* @return boolean True on success, false on error.
*/
public function remove_event($event);
}

View file

@ -3,7 +3,6 @@
* *
* @version @package_version@ * @version @package_version@
* @author Daniel Morlock <daniel.morlock@awesome-it.de> * @author Daniel Morlock <daniel.morlock@awesome-it.de>
* @author JodliDev <jodlidev@gmail.com>
* *
* Copyright (C) Awesome IT GbR <info@awesome-it.de> * Copyright (C) Awesome IT GbR <info@awesome-it.de>
* *
@ -21,90 +20,73 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * 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(1024) 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` ( CREATE TABLE IF NOT EXISTS `caldav_calendars` (
`calendar_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, `calendar_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0', `user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
`source_id` int(10) UNSIGNED DEFAULT NULL, `name` varchar(255) NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 NOT NULL, `color` varchar(8) NOT NULL,
`color` varchar(8) NOT NULL, `showalarms` tinyint(1) NOT NULL DEFAULT '1',
`showalarms` tinyint(1) NOT NULL DEFAULT '1',
`caldav_tag` varchar(255) DEFAULT NULL, `caldav_url` varchar(255) NOT NULL,
`caldav_url` varchar(1024) NOT NULL, `caldav_tag` varchar(255) DEFAULT NULL,
`caldav_last_change` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `caldav_user` varchar(255) DEFAULT NULL,
`is_ical` tinyint(1) NOT NULL DEFAULT '0', `caldav_pass` varchar(1024) DEFAULT NULL,
`ical_user` varchar(255) DEFAULT NULL, `caldav_last_change` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`ical_pass` varchar(1024) DEFAULT NULL,
PRIMARY KEY(`calendar_id`), PRIMARY KEY(`calendar_id`),
INDEX `caldav_user_name_idx` (`user_id`, `name`), INDEX `caldav_user_name_idx` (`user_id`, `name`),
CONSTRAINT `fk_caldav_calendars_user_id` FOREIGN KEY (`user_id`) 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`) ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
REFERENCES `caldav_sources`(`source_id`) ON DELETE CASCADE ON UPDATE CASCADE
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */;
CREATE TABLE IF NOT EXISTS `caldav_events` ( CREATE TABLE IF NOT EXISTS `caldav_events` (
`event_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, `event_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`calendar_id` int(11) UNSIGNED NOT NULL DEFAULT '0', `calendar_id` int(11) UNSIGNED NOT NULL DEFAULT '0',
`recurrence_id` int(11) UNSIGNED NOT NULL DEFAULT '0', `recurrence_id` int(11) UNSIGNED NOT NULL DEFAULT '0',
`uid` varchar(255) NOT NULL DEFAULT '', `uid` varchar(255) NOT NULL DEFAULT '',
`instance` varchar(16) NOT NULL DEFAULT '', `instance` varchar(16) NOT NULL DEFAULT '',
`isexception` tinyint(1) NOT NULL DEFAULT '0', `isexception` tinyint(1) NOT NULL DEFAULT '0',
`created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00', `created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00', `changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`sequence` int(1) UNSIGNED NOT NULL DEFAULT '0', `sequence` int(1) UNSIGNED NOT NULL DEFAULT '0',
`start` datetime NOT NULL DEFAULT '1000-01-01 00:00:00', `start` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`end` datetime NOT NULL DEFAULT '1000-01-01 00:00:00', `end` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`recurrence` varchar(255) DEFAULT NULL, `recurrence` varchar(255) DEFAULT NULL,
`title` varchar(255) CHARACTER SET utf8mb4 NOT NULL, `title` varchar(255) NOT NULL,
`description` text CHARACTER SET utf8mb4 NOT NULL, `description` text NOT NULL,
`location` varchar(255) CHARACTER SET utf8mb4 NOT NULL DEFAULT '', `location` varchar(255) NOT NULL DEFAULT '',
`categories` varchar(255) CHARACTER SET utf8mb4 NOT NULL DEFAULT '', `categories` varchar(255) NOT NULL DEFAULT '',
`url` varchar(255) NOT NULL DEFAULT '', `url` varchar(255) NOT NULL DEFAULT '',
`all_day` tinyint(1) NOT NULL DEFAULT '0', `all_day` tinyint(1) NOT NULL DEFAULT '0',
`free_busy` tinyint(1) NOT NULL DEFAULT '0', `free_busy` tinyint(1) NOT NULL DEFAULT '0',
`priority` tinyint(1) NOT NULL DEFAULT '0', `priority` tinyint(1) NOT NULL DEFAULT '0',
`sensitivity` tinyint(1) NOT NULL DEFAULT '0', `sensitivity` tinyint(1) NOT NULL DEFAULT '0',
`status` varchar(32) NOT NULL DEFAULT '', `status` varchar(32) NOT NULL DEFAULT '',
`alarms` text NULL DEFAULT NULL, `alarms` text NULL DEFAULT NULL,
`attendees` text DEFAULT NULL, `attendees` text DEFAULT NULL,
`notifyat` datetime DEFAULT NULL, `notifyat` datetime DEFAULT NULL,
`caldav_url` varchar(255) NOT NULL, `caldav_url` varchar(255) NOT NULL,
`caldav_tag` varchar(255) DEFAULT NULL, `caldav_tag` varchar(255) DEFAULT NULL,
`caldav_last_change` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `caldav_last_change` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY(`event_id`), PRIMARY KEY(`event_id`),
INDEX `caldav_uid_idx` (`uid`), INDEX `caldav_uid_idx` (`uid`),
INDEX `caldav_recurrence_idx` (`recurrence_id`), INDEX `caldav_recurrence_idx` (`recurrence_id`),
INDEX `caldav_calendar_notify_idx` (`calendar_id`,`notifyat`), INDEX `caldav_calendar_notify_idx` (`calendar_id`,`notifyat`),
CONSTRAINT `fk_caldav_events_calendar_id` FOREIGN KEY (`calendar_id`) CONSTRAINT `fk_caldav_events_calendar_id` FOREIGN KEY (`calendar_id`)
REFERENCES `caldav_calendars`(`calendar_id`) ON DELETE CASCADE ON UPDATE CASCADE REFERENCES `caldav_calendars`(`calendar_id`) ON DELETE CASCADE ON UPDATE CASCADE
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */; ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
CREATE TABLE IF NOT EXISTS `caldav_attachments` ( CREATE TABLE IF NOT EXISTS `caldav_attachments` (
`attachment_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, `attachment_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`event_id` int(11) UNSIGNED NOT NULL DEFAULT '0', `event_id` int(11) UNSIGNED NOT NULL DEFAULT '0',
`filename` varchar(255) NOT NULL DEFAULT '', `filename` varchar(255) NOT NULL DEFAULT '',
`mimetype` varchar(255) NOT NULL DEFAULT '', `mimetype` varchar(255) NOT NULL DEFAULT '',
`size` int(11) NOT NULL DEFAULT '0', `size` int(11) NOT NULL DEFAULT '0',
`data` longtext NOT NULL, `data` longtext NOT NULL,
PRIMARY KEY(`attachment_id`), PRIMARY KEY(`attachment_id`),
CONSTRAINT `fk_caldav_attachments_event_id` FOREIGN KEY (`event_id`) CONSTRAINT `fk_caldav_attachments_event_id` FOREIGN KEY (`event_id`)
REFERENCES `caldav_events`(`event_id`) ON DELETE CASCADE ON UPDATE CASCADE REFERENCES `caldav_events`(`event_id`) ON DELETE CASCADE ON UPDATE CASCADE
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
REPLACE INTO `system` (`name`, `value`) VALUES ('calendar-caldav-version', '2021082400'); REPLACE INTO `system` (`name`, `value`) VALUES ('calendar-caldav-version', '2015022700');

View file

@ -0,0 +1,24 @@
/**
* CalDAV Client
*
* @version @package_version@
* @author Daniel Morlock <daniel.morlock@awesome-it.de>
*
* 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/>.
*/
ALTER TABLE `caldav_props` change `user` `username` varchar(255);
ALTER TABLE `events` ADD `status` VARCHAR(32) NOT NULL DEFAULT '' AFTER `sensitivity`;

View file

@ -0,0 +1,125 @@
/**
* CalDAV Client
*
* @version @package_version@
* @author Daniel Morlock <daniel.morlock@awesome-it.de>
*
* 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/>.
*/
/* Create new tables */
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',
`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_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
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
CREATE TABLE IF NOT EXISTS `caldav_events` (
`event_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`calendar_id` int(11) UNSIGNED NOT NULL DEFAULT '0',
`recurrence_id` int(11) UNSIGNED NOT NULL DEFAULT '0',
`uid` varchar(255) NOT NULL DEFAULT '',
`created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`sequence` int(1) UNSIGNED NOT NULL DEFAULT '0',
`start` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`end` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`recurrence` varchar(255) DEFAULT NULL,
`title` varchar(255) NOT NULL,
`description` text NOT NULL,
`location` varchar(255) NOT NULL DEFAULT '',
`categories` varchar(255) NOT NULL DEFAULT '',
`url` varchar(255) NOT NULL DEFAULT '',
`all_day` tinyint(1) NOT NULL DEFAULT '0',
`free_busy` tinyint(1) NOT NULL DEFAULT '0',
`priority` tinyint(1) NOT NULL DEFAULT '0',
`sensitivity` tinyint(1) NOT NULL DEFAULT '0',
`status` varchar(32) NOT NULL DEFAULT '',
`alarms` varchar(255) DEFAULT NULL,
`attendees` text DEFAULT NULL,
`notifyat` datetime DEFAULT NULL,
`caldav_url` varchar(255) NOT NULL,
`caldav_tag` varchar(255) DEFAULT NULL,
`caldav_last_change` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY(`event_id`),
INDEX `caldav_uid_idx` (`uid`),
INDEX `caldav_recurrence_idx` (`recurrence_id`),
INDEX `caldav_calendar_notify_idx` (`calendar_id`,`notifyat`),
CONSTRAINT `fk_caldav_events_calendar_id` FOREIGN KEY (`calendar_id`)
REFERENCES `calendars`(`calendar_id`) ON DELETE CASCADE ON UPDATE CASCADE
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
CREATE TABLE IF NOT EXISTS `caldav_attachments` (
`attachment_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`event_id` int(11) UNSIGNED NOT NULL DEFAULT '0',
`filename` varchar(255) NOT NULL DEFAULT '',
`mimetype` varchar(255) NOT NULL DEFAULT '',
`size` int(11) NOT NULL DEFAULT '0',
`data` longtext NOT NULL,
PRIMARY KEY(`attachment_id`),
CONSTRAINT `fk_caldav_attachments_event_id` FOREIGN KEY (`event_id`)
REFERENCES `events`(`event_id`) ON DELETE CASCADE ON UPDATE CASCADE
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
/* Migrate Data */
INSERT INTO caldav_calendars SELECT calendar_id, user_id, `name`, color, showalarms, url as caldav_url,
tag as caldav_tag, username as caldav_user, pass as caldav_pass,
last_change as caldav_last_change
FROM calendars cal, caldav_props dav
WHERE dav.obj_id = cal.calendar_id
AND dav.obj_type = 'vcal';
INSERT INTO caldav_events SELECT e.*, dav.url as caldav_url, dav.tag as caldav_tag, dav.last_change as caldav_last_change
FROM `events` e, caldav_props dav
WHERE dav.obj_id = e.event_id
AND dav.obj_type = 'vevent';
INSERT INTO caldav_attachments SELECT * FROM attachments a
WHERE a.event_id IN (
SELECT obj_id FROM caldav_props dav
WHERE dav.obj_type = 'vevent'
);
/* Drop deprecated data */
DELETE FROM `events` WHERE event_id IN (
SELECT obj_id FROM caldav_props dav
WHERE dav.obj_type = 'vevent'
);
DELETE FROM calendars WHERE calendar_id IN (
SELECT obj_id FROM caldav_props dav
WHERE dav.obj_type = 'vcal'
);
DELETE FROM attachments WHERE event_id IN (
SELECT obj_id FROM caldav_props dav
WHERE dav.obj_type = 'vevent'
);
DROP TABLE caldav_props;

View file

@ -0,0 +1,14 @@
-- add identifier for recurring instances and exceptions
ALTER TABLE `caldav_events` ADD `instance` varchar(16) NOT NULL DEFAULT '' AFTER `uid`;
ALTER TABLE `caldav_events` ADD `isexception` tinyint(1) NOT NULL DEFAULT '0' AFTER `instance`;
UPDATE `caldav_events` SET `instance` = DATE_FORMAT(`start`, '%Y%m%d')
WHERE `recurrence_id` != 0 AND `instance` = '' AND `all_day` = 1;
UPDATE `caldav_events` SET `instance` = DATE_FORMAT(`start`, '%Y%m%dT%k%i%s')
WHERE `recurrence_id` != 0 AND `instance` = '' AND `all_day` = 0;
-- extend alarms columns for multiple values
ALTER TABLE `caldav_events` CHANGE `alarms` `alarms` TEXT NULL DEFAULT NULL;

View file

@ -1,9 +1,10 @@
/** /**
* CalDAV Client * CalDAV Client
* (not tested & automatically generated from mysql)
* *
* @version @package_version@ * @version @package_version@
* @author JodliDev <jodlidev@gmail.com> * @author Hugo Slabbert <hugo@slabnet.com>
*
* Copyright (C) 2014, Hugo Slabbert <hugo@slabnet.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@ -19,107 +20,32 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
-- SQLINES LICENSE FOR EVALUATION USE ONLY CREATE TYPE caldav_type AS ENUM ('vcal','vevent','vtodo','');
CREATE SEQUENCE IF NOT EXISTS caldav_sources_seq;
CREATE TABLE IF NOT EXISTS caldav_sources ( CREATE TABLE IF NOT EXISTS caldav_props (
source_id int CHECK (source_id > 0) NOT NULL DEFAULT NEXTVAL ('caldav_sources_seq'), obj_id int NOT NULL,
user_id int CHECK (user_id > 0) NOT NULL DEFAULT '0', obj_type caldav_type NOT NULL,
url varchar(255) NOT NULL,
tag varchar(255) DEFAULT NULL,
username varchar(255) DEFAULT NULL,
pass varchar(1024) DEFAULT NULL,
last_change timestamp without time zone DEFAULT now() NOT NULL,
PRIMARY KEY (obj_id, obj_type)
);
caldav_url varchar(1024) NOT NULL, CREATE OR REPLACE FUNCTION upd_timestamp() RETURNS TRIGGER
caldav_user varchar(255) DEFAULT NULL, LANGUAGE plpgsql
caldav_pass varchar(1024) DEFAULT NULL, AS
$$
BEGIN
NEW.last_change = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$;
PRIMARY KEY(source_id), CREATE TRIGGER update_timestamp
CONSTRAINT fk_caldav_sources_user_id FOREIGN KEY (user_id) BEFORE INSERT OR UPDATE
REFERENCES users(user_id) ON DELETE CASCADE ON UPDATE CASCADE ON caldav_props
) /* SQLINES DEMO *** DB */ /* SQLINES DEMO *** ET utf8 COLLATE utf8_general_ci */; FOR EACH ROW
EXECUTE PROCEDURE upd_timestamp();
-- SQLINES LICENSE FOR EVALUATION USE ONLY
CREATE SEQUENCE IF NOT EXISTS caldav_calendars_seq;
CREATE TABLE IF NOT EXISTS caldav_calendars (
calendar_id int CHECK (calendar_id > 0) NOT NULL DEFAULT NEXTVAL ('caldav_calendars_seq'),
user_id int CHECK (user_id > 0) NOT NULL DEFAULT '0',
source_id int CHECK (source_id > 0) DEFAULT NULL,
name varchar(255) NOT NULL,
color varchar(8) NOT NULL,
showalarms smallint NOT NULL DEFAULT '1',
caldav_tag varchar(255) DEFAULT NULL,
caldav_url varchar(1024) NOT NULL,
caldav_last_change timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
is_ical smallint NOT NULL DEFAULT '0',
ical_user varchar(255) DEFAULT NULL,
ical_pass varchar(1024) DEFAULT NULL,
PRIMARY KEY(calendar_id)
,
CONSTRAINT fk_caldav_calendars_user_id FOREIGN KEY (user_id)
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
) /* SQLINES DEMO *** DB */ /* SQLINES DEMO *** ET utf8 COLLATE utf8_general_ci */;
CREATE INDEX IF NOT EXISTS caldav_user_name_idx ON caldav_calendars (user_id, name);
-- SQLINES LICENSE FOR EVALUATION USE ONLY
CREATE SEQUENCE IF NOT EXISTS caldav_events_seq;
CREATE TABLE IF NOT EXISTS caldav_events (
event_id int CHECK (event_id > 0) NOT NULL DEFAULT NEXTVAL ('caldav_events_seq'),
calendar_id int CHECK (calendar_id > 0) NOT NULL DEFAULT '0',
recurrence_id int NOT NULL DEFAULT '0',
uid varchar(255) NOT NULL DEFAULT '',
instance varchar(16) NOT NULL DEFAULT '',
isexception smallint NOT NULL DEFAULT '0',
created timestamp(0) NOT NULL DEFAULT '1000-01-01 00:00:00',
changed timestamp(0) NOT NULL DEFAULT '1000-01-01 00:00:00',
sequence int NOT NULL DEFAULT '0',
start timestamp(0) NOT NULL DEFAULT '1000-01-01 00:00:00',
"end" timestamp(0) NOT NULL DEFAULT '1000-01-01 00:00:00',
recurrence varchar(255) DEFAULT NULL,
title varchar(255) NOT NULL,
description text NOT NULL,
location varchar(255) NOT NULL DEFAULT '',
categories varchar(255) NOT NULL DEFAULT '',
url varchar(255) NOT NULL DEFAULT '',
all_day smallint NOT NULL DEFAULT '0',
free_busy smallint NOT NULL DEFAULT '0',
priority smallint NOT NULL DEFAULT '0',
sensitivity smallint NOT NULL DEFAULT '0',
status varchar(32) NOT NULL DEFAULT '',
alarms text NULL DEFAULT NULL,
attendees text DEFAULT NULL,
notifyat timestamp(0) DEFAULT NULL,
caldav_url varchar(255) NOT NULL,
caldav_tag varchar(255) DEFAULT NULL,
caldav_last_change timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY(event_id)
,
CONSTRAINT fk_caldav_events_calendar_id FOREIGN KEY (calendar_id)
REFERENCES caldav_calendars(calendar_id) ON DELETE CASCADE ON UPDATE CASCADE
) /* SQLINES DEMO *** DB */ /* SQLINES DEMO *** ET utf8 COLLATE utf8_general_ci */;
CREATE INDEX IF NOT EXISTS caldav_uid_idx ON caldav_events (uid);
CREATE INDEX IF NOT EXISTS caldav_recurrence_idx ON caldav_events (recurrence_id);
CREATE INDEX IF NOT EXISTS caldav_calendar_notify_idx ON caldav_events (calendar_id,notifyat);
-- SQLINES LICENSE FOR EVALUATION USE ONLY
CREATE SEQUENCE IF NOT EXISTS caldav_attachments_seq;
CREATE TABLE IF NOT EXISTS caldav_attachments (
attachment_id int CHECK (attachment_id > 0) NOT NULL DEFAULT NEXTVAL ('caldav_attachments_seq'),
event_id int CHECK (event_id > 0) NOT NULL DEFAULT '0',
filename varchar(255) NOT NULL DEFAULT '',
mimetype varchar(255) NOT NULL DEFAULT '',
size int NOT NULL DEFAULT '0',
data TEXT NOT NULL,
PRIMARY KEY(attachment_id),
CONSTRAINT fk_caldav_attachments_event_id FOREIGN KEY (event_id)
REFERENCES caldav_events(event_id) ON DELETE CASCADE ON UPDATE CASCADE
) /* SQLINES DEMO *** DB */ /* SQLINES DEMO *** ET utf8 COLLATE utf8_general_ci */;
INSERT INTO system (name, value) VALUES ('calendar-caldav-version', '2021082400') ON CONFLICT (name) DO UPDATE SET value = excluded.value;

View file

@ -1,101 +0,0 @@
/**
* CalDAV Client
* (not tested & automatically generated from mysql)
*
* @version @package_version@
* @author JodliDev <jodlidev@gmail.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/>.
*/
CREATE TABLE IF NOT EXISTS `caldav_sources` (
`source_id` INTEGER NOT NULL PRIMARY KEY,
`user_id` INTEGER NOT NULL DEFAULT '0',
`caldav_url` TEXT NOT NULL,
`caldav_user` TEXT DEFAULT NULL,
`caldav_pass` TEXT DEFAULT NULL,
CONSTRAINT fk_itipinvitations_user_id FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS `caldav_calendars` (
`calendar_id` INTEGER NOT NULL PRIMARY KEY,
`user_id` INTEGER NOT NULL DEFAULT '0',
`source_id` INTEGER DEFAULT NULL,
`name` TEXT NOT NULL,
`color` TEXT NOT NULL,
`showalarms` tinyINTEGER NOT NULL DEFAULT '1',
`caldav_tag` TEXT DEFAULT NULL,
`caldav_url` TEXT NOT NULL,
`caldav_last_change` timestamp NOT NULL ,
`is_ical` tinyINTEGER NOT NULL DEFAULT '0',
`ical_user` TEXT DEFAULT NULL,
`ical_pass` TEXT DEFAULT NULL,
CONSTRAINT `fk_caldav_calendars_user_id` FOREIGN KEY (`user_id`)
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
);
CREATE TABLE IF NOT EXISTS `caldav_events` (
`event_id` INTEGER NOT NULL PRIMARY KEY,
`calendar_id` INTEGER NOT NULL DEFAULT '0',
`recurrence_id` INTEGER NOT NULL DEFAULT '0',
`uid` TEXT NOT NULL DEFAULT '',
`instance` TEXT NOT NULL DEFAULT '',
`isexception` tinyINTEGER NOT NULL DEFAULT '0',
`created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`sequence` INTEGER NOT NULL DEFAULT '0',
`start` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`end` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`recurrence` TEXT DEFAULT NULL,
`title` TEXT NOT NULL,
`description` text NOT NULL,
`location` TEXT NOT NULL DEFAULT '',
`categories` TEXT NOT NULL DEFAULT '',
`url` TEXT NOT NULL DEFAULT '',
`all_day` tinyINTEGER NOT NULL DEFAULT '0',
`free_busy` tinyINTEGER NOT NULL DEFAULT '0',
`priority` tinyINTEGER NOT NULL DEFAULT '0',
`sensitivity` tinyINTEGER NOT NULL DEFAULT '0',
`status` TEXT NOT NULL DEFAULT '',
`alarms` text NULL DEFAULT NULL,
`attendees` text DEFAULT NULL,
`notifyat` datetime DEFAULT NULL,
`caldav_url` TEXT NOT NULL,
`caldav_tag` TEXT DEFAULT NULL,
`caldav_last_change` timestamp NOT NULL ,
FOREIGN KEY (`calendar_id`)
REFERENCES `caldav_calendars`(`calendar_id`) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS `caldav_attachments` (
`attachment_id` INTEGER NOT NULL PRIMARY KEY,
`event_id` INTEGER NOT NULL DEFAULT '0',
`filename` TEXT NOT NULL DEFAULT '',
`mimetype` TEXT NOT NULL DEFAULT '',
`size` INTEGER NOT NULL DEFAULT '0',
`data` TEXT NOT NULL,
FOREIGN KEY (`event_id`)
REFERENCES `caldav_events`(`event_id`) ON DELETE CASCADE ON UPDATE CASCADE
);
REPLACE INTO `system` (`name`, `value`) VALUES ('calendar-caldav-version', '2021082400');
CREATE INDEX caldav_user_name_idx ON caldav_calendars(user_id, name);
CREATE INDEX caldav_uid_idx ON caldav_events(uid);
CREATE INDEX caldav_recurrence_idx ON caldav_events(recurrence_id);
CREATE INDEX caldav_calendar_notify_idx ON caldav_events(calendar_id, notifyat);

View file

@ -4,7 +4,6 @@
* CalDAV driver for the Calendar plugin * CalDAV driver for the Calendar plugin
* *
* @author Daniel Morlock <daniel.morlock@awesome-it.de> * @author Daniel Morlock <daniel.morlock@awesome-it.de>
* @author JodliDev <jodlidev@gmail.com>
* *
* Copyright (C) Awesome IT GbR <info@awesome-it.de> * Copyright (C) Awesome IT GbR <info@awesome-it.de>
* *
@ -22,9 +21,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
require_once 'caldav_sync.php'; require_once (dirname(__FILE__).'/caldav_sync.php');
require_once 'ical_sync.php'; require_once (dirname(__FILE__).'/../../lib/encryption.php');
require_once 'encryption.php';
class caldav_driver extends calendar_driver class caldav_driver extends calendar_driver
@ -49,11 +47,13 @@ class caldav_driver extends calendar_driver
private $sensitivity_map = array('public' => 0, 'private' => 1, 'confidential' => 2); private $sensitivity_map = array('public' => 0, 'private' => 1, 'confidential' => 2);
private $server_timezone; private $server_timezone;
private $db_sources = 'caldav_sources';
private $db_calendars = 'caldav_calendars';
private $db_events = 'caldav_events'; private $db_events = 'caldav_events';
private $db_calendars = 'caldav_calendars';
private $db_attachments = 'caldav_attachments'; private $db_attachments = 'caldav_attachments';
// Crypt key for CalDAV auth
private $crypt_key;
// Holds CalDAV sync clients // Holds CalDAV sync clients
private $sync_clients = array(); private $sync_clients = array();
@ -92,50 +92,18 @@ class caldav_driver extends calendar_driver
// read database config // read database config
$db = $this->rc->get_dbh(); $db = $this->rc->get_dbh();
$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_events = $this->rc->config->get('db_table_caldav_events', $db->table_name($this->db_events));
$this->db_calendars = $this->rc->config->get('db_table_caldav_calendars', $db->table_name($this->db_calendars));
$this->db_attachments = $this->rc->config->get('db_table_caldav_attachments', $db->table_name($this->db_attachments)); $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 // Set debug state
if(self::$debug === null) if(self::$debug === null)
self::$debug = $this->rc->config->get('calendar_caldav_debug', False); self::$debug = $this->rc->config->get('calendar_caldav_debug', False);
$this->_setup_preinstalled_sources();
$this->_read_calendars(); $this->_read_calendars();
} }
/**
* Setup preinstalled sources defined in config file
*/
protected function _setup_preinstalled_sources()
{
$preinstalled_sources = $this->rc->config->get('calendar_caldav_preinstalled_sources', FALSE);
if ($preinstalled_sources && is_array($preinstalled_sources)) {
$username = $this->rc->get_user_name();
$password = $this->rc->get_user_password();
foreach ($preinstalled_sources as $cal){
$url = $cal['caldav_url'];
$user = $cal['caldav_user'];
$pass = $cal['caldav_pass'];
$url = str_replace('%u', $username, $url);
$user = str_replace('%u', $username, $user);
$pass = str_replace('%p', $password, $pass);
$cal['caldav_url'] = $url;
$cal['caldav_user'] = $user;
$cal['caldav_pass'] = $pass;
if (!$this->create_source($cal)) {
$error_msg = 'Unable to add default calendars' . ($this->last_error ? ': ' . $this->last_error :'');
$this->rc->output->show_message($error_msg, 'error');
}
}
}
}
/** /**
* Read available calendars for the current user and store them internally * Read available calendars for the current user and store them internally
*/ */
@ -145,22 +113,10 @@ class caldav_driver extends calendar_driver
if (!empty($this->rc->user->ID)) { if (!empty($this->rc->user->ID)) {
$calendar_ids = array(); $calendar_ids = array();
$result = $this->rc->db->query('SELECT $result = $this->rc->db->query("SELECT *, calendar_id AS id
cal.calendar_id AS `id`, FROM " . $this->db_calendars . "
cal.source_id AS `source_id`, WHERE user_id=?
cal.name AS `name`, ORDER BY 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.is_ical AS `is_ical`
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 $this->rc->user->ID
); );
while ($result && ($arr = $this->rc->db->fetch_assoc($result))) { while ($result && ($arr = $this->rc->db->fetch_assoc($result))) {
@ -169,21 +125,14 @@ class caldav_driver extends calendar_driver
$arr['name'] = html::quote($arr['name']); $arr['name'] = html::quote($arr['name']);
$arr['listname'] = html::quote($arr['name']); $arr['listname'] = html::quote($arr['name']);
$arr['rights'] = 'lrswikxteav'; $arr['rights'] = 'lrswikxteav';
$arr['editable'] = true;
$arr['caldav_pass'] = $this->_decrypt_pass($arr['caldav_pass']); $arr['caldav_pass'] = $this->_decrypt_pass($arr['caldav_pass']);
$this->calendars[$arr['calendar_id']] = $arr;
$calendar_ids[] = $this->rc->db->quote($arr['calendar_id']);
if($arr['is_ical']) { // Init sync client
$this->sync_clients[$arr['id']] = new ical_sync($arr); $cal_id = $arr['calendar_id'];
$arr["editable"] = false; $this->sync_clients[$cal_id] = new caldav_sync($arr);
$arr["deletable"] = true;
$arr["editable_name"] = true;
}
else {
$arr['editable'] = true;
$this->sync_clients[$arr['id']] = new caldav_sync($arr);
}
$this->calendars[$arr['id']] = $arr;
$calendar_ids[] = $this->rc->db->quote($arr['id']);
} }
$this->calendar_ids = join(',', $calendar_ids); $this->calendar_ids = join(',', $calendar_ids);
} }
@ -209,88 +158,25 @@ class caldav_driver extends calendar_driver
} }
} }
// append the virtual birthdays calendar // 'personal' is unsupported in this driver
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; return $calendars;
} }
/** /**
* Creates CalDAV calendar or call create_source() * Extracts CalDAV calendar.
* (we use the same function for both calendar and source to stay compatible with other drivers)
* *
* @see database_driver::create_calendar() * @see database_driver::create_calendar()
*/ */
public function create_calendar($cal) public function create_calendar($cal)
{ {
if(isset($cal['new-source'])) { //add new source $result = false;
return $this->create_source($cal); $cal['caldav_url'] = self::_encode_url($cal["caldav_url"]);
} if(!isset($cal['color'])) $cal['color'] = 'cc0000';
else if($cal['is_ical']) { //add an ics calendar
$cal['caldav_url'] = $cal['ical_url'];
$cal_id = $this->_db_create_calendar($cal);
if($cal_id) {
$this->_read_calendars();
$cal['id'] = $cal_id;
$this->sync_clients[$cal_id] = new ical_sync($cal);
$this->_sync_calendar($cal_id);
return true;
}
else
return false;
}
else { //add a calendar to a source
$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;
}
$source['caldav_pass'] = $this->_decrypt_pass($source['caldav_pass']); $calendars = $this->_autodiscover_calendars($this->_expand_pass($cal));
$server_url = self::_encode_url($source['caldav_url']);
$server_path = rtrim(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);
}
return false;
}
}
private function _add_calendars($calendars, $source) {
$cal_ids = array(); $cal_ids = array();
$result = false;
if($calendars) if($calendars)
{ {
$result = true; $result = true;
@ -300,13 +186,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']); $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; if($this->rc->db->affected_rows($result)) continue;
$cal = array( $cal['caldav_url'] = self::_encode_url($calendar['href']);
'caldav_url' => $calendar['href'],
'name' => $calendar['name'],
'color' => $calendar['color']
);
if (($obj_id = $this->_db_create_calendar($cal, $source)) !== false) { // 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) {
array_push($cal_ids, $obj_id); array_push($cal_ids, $obj_id);
} else $result = false; } else $result = false;
} }
@ -327,69 +213,6 @@ class caldav_driver extends calendar_driver
return $result; return $result;
} }
/**
* Adds CalDAV source and loads adds all calendars from that source
*/
public function create_source($source)
{
$source['caldav_url'] = self::_encode_url($source['caldav_url']);
// Re-discover all existing calendars systematically
try {
$calendars = $this->_autodiscover_calendars($source);
}
catch(Exception $e) {
self::debug_log($e);
$this->rc->output->show_message($this->cal->gettext('source_notadded_error'), 'error');
return false;
}
// Remove local data associated with deprecated calendars
$caldav_urls = array_column($calendars, 'href');
$query = $this->rc->db->query(
"DELETE FROM " . $this->db_calendars . " WHERE user_id=? AND caldav_url NOT IN ('" . implode("','", $caldav_urls) . "')",
$this->rc->user->ID);
$this->rc->db->affected_rows($query);
// Skip update if the set of available calendars matches
$result = $this->rc->db->query(
"SELECT calendar_id FROM " . $this->db_calendars . " WHERE user_id=? AND caldav_url LIKE ?",
$this->rc->user->ID, $source['caldav_url'] . '%');
$count_cur = $this->rc->db->num_rows($result);
$count_avail = count($calendars);
if ($count_cur == $count_avail)
{
self::debug_log("Skip source update.");
return true;
}
if(count($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 * Create a new calendar assigned to the current user
* *
@ -403,20 +226,20 @@ class caldav_driver extends calendar_driver
* *
* @return mixed ID of the calendar on success, False on error * @return mixed ID of the calendar on success, False on error
*/ */
private function _db_create_calendar($prop, $source=null) private function _db_create_calendar($prop)
{ {
$result = $this->rc->db->query( $result = $this->rc->db->query(
"INSERT INTO " . $this->db_calendars . " "INSERT INTO " . $this->db_calendars . "
(user_id, source_id, name, color, showalarms, caldav_url, caldav_tag, is_ical) (user_id, name, color, showalarms, caldav_url, caldav_tag, caldav_user, caldav_pass)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)", VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
$this->rc->user->ID, $this->rc->user->ID,
$source ? $source['source_id'] : null,
$prop['name'], $prop['name'],
isset($prop['color']) ? $prop['color'] : '#cc0000', $prop['color'],
$prop['showalarms']?1:0, $prop['showalarms']?1:0,
$prop['caldav_url'], $prop['caldav_url'],
isset($prop["caldav_tag"]) ? $prop["caldav_tag"] : null, isset($prop["caldav_tag"]) ? $prop["caldav_tag"] : null,
$prop['is_ical']?1:0 isset($prop["caldav_user"]) ? $prop["caldav_user"] : null,
isset($prop["caldav_pass"]) ? $this->_encrypt_pass($prop["caldav_pass"]) : null
); );
if ($result) if ($result)
@ -433,16 +256,31 @@ class caldav_driver extends calendar_driver
public function edit_calendar($cal) public function edit_calendar($cal)
{ {
$query = $this->rc->db->query("UPDATE " . $this->db_calendars . " $query = $this->rc->db->query("UPDATE " . $this->db_calendars . "
SET name=?, color=?, showalarms=? SET name=?, color=?, showalarms=?, caldav_url=?, caldav_tag=?, caldav_user=?
WHERE calendar_id=? WHERE calendar_id=?
AND user_id=?", AND user_id=?",
$cal['name'], $cal['name'],
$cal['color'], $cal['color'],
$cal['showalarms']?1:0, $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'], $cal['id'],
$this->rc->user->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); return $this->rc->db->affected_rows($query);
} }
@ -465,42 +303,23 @@ class caldav_driver extends calendar_driver
} }
/** /**
* Delete the given calendar or source with all its contents * Delete the given calendar 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() * @see calendar_driver::delete_calendar()
*/ */
public function delete_calendar($prop) public function delete_calendar($prop)
{ {
if (!$this->calendars[$prop['id']])
return false;
// events and attachments will be deleted by foreign key cascade // events and attachments will be deleted by foreign key cascade
if(isset($prop['delete-source'])) { $query = $this->rc->db->query(
unset($prop['delete-source']); "DELETE FROM " . $this->db_calendars . " WHERE calendar_id=?",
$count = 0; $prop['id']
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(!isset($this->calendars[$prop['id']]))
return false;
$calendar = $this->calendars[$prop['id']];
if(!$calendar['is_ical'] && !$this->sync_clients[$prop['id']]->caldav->delete_calendar()) return $this->rc->db->affected_rows($query);
return false;
$query = $this->rc->db->query(
"DELETE FROM " . $this->db_calendars . " WHERE calendar_id=?",
$prop['id']
);
return $this->rc->db->affected_rows($query);
}
} }
/** /**
@ -528,14 +347,18 @@ class caldav_driver extends calendar_driver
return false; return false;
if (!empty($this->calendars)) { if (!empty($this->calendars)) {
if (!$event['calendar'] || !$this->calendars[$event['calendar']]) if ($event['calendar'] && !$this->calendars[$event['calendar']])
return false; return false;
if (!$event['calendar'])
$event['calendar'] = reset(array_keys($this->calendars));
if($event = $this->_save_preprocess($event)) { if($event = $this->_save_preprocess($event)) {
$sync_client = $this->sync_clients[$event["calendar"]]; $sync_client = $this->sync_clients[$event["calendar"]];
// Only push event if caldav_tag is not set to avoid pushing it twice // Only push event if caldav_tag is not set to avoid pushing it twice
if (isset($event["caldav_tag"]) || ($event = $sync_client->create_event($event)) !== null) { if (isset($event["caldav_tag"]) || ($event = $sync_client->create_event($event)) !== false) {
if ($event_id = $this->_insert_event($event)) { if ($event_id = $this->_insert_event($event)) {
$this->_update_recurring($event); $this->_update_recurring($event);
} }
@ -555,11 +378,6 @@ class caldav_driver extends calendar_driver
{ {
//$event = $this->_save_preprocess($event); //$event = $this->_save_preprocess($event);
//TODO: proper 4 byte character (eg emoticons) handling
//utf8 in mysql only supports 3 byte characters, so this throws an error if there are emoticons in the description.
//For now we just remove them. But instead of removing, we should prepare the database for them (by using utf8mb4)
$desc = preg_replace('/[\xF0-\xF7].../s', '', strval($event['description']));
$this->rc->db->query(sprintf( $this->rc->db->query(sprintf(
"INSERT INTO " . $this->db_events . " "INSERT INTO " . $this->db_events . "
(calendar_id, created, changed, uid, recurrence_id, instance, isexception, %s, %s, all_day, recurrence, (calendar_id, created, changed, uid, recurrence_id, instance, isexception, %s, %s, all_day, recurrence,
@ -581,7 +399,7 @@ class caldav_driver extends calendar_driver
intval($event['all_day']), intval($event['all_day']),
$event['_recurrence'], $event['_recurrence'],
strval($event['title']), strval($event['title']),
$desc, strval($event['description']),
strval($event['location']), strval($event['location']),
join(',', (array)$event['categories']), join(',', (array)$event['categories']),
strval($event['url']), strval($event['url']),
@ -787,7 +605,7 @@ class caldav_driver extends calendar_driver
$recurrence_id_format = libcalendaring::recurrence_id_format($event); $recurrence_id_format = libcalendaring::recurrence_id_format($event);
foreach ($exceptions as $exception) { foreach ($exceptions as $exception) {
$recurrence_id = rcube_utils::anytodatetime($exception['_instance'], $old['start']->getTimezone()); $recurrence_id = rcube_utils::anytodatetime($exception['_instance'], $old['start']->getTimezone());
if (is_a($recurrence_id, 'DateTime') || is_a($recurrence_id, 'DateTimeImmutable')) { if (is_a($recurrence_id, 'DateTime')) {
$recurrence_id->add($date_shift); $recurrence_id->add($date_shift);
$exception['_instance'] = $recurrence_id->format($recurrence_id_format); $exception['_instance'] = $recurrence_id->format($recurrence_id_format);
$this->_update_event($exception, false); $this->_update_event($exception, false);
@ -878,7 +696,7 @@ class caldav_driver extends calendar_driver
foreach (self::$scheduling_properties as $prop) { foreach (self::$scheduling_properties as $prop) {
$a = $old[$prop]; $a = $old[$prop];
$b = $event[$prop]; $b = $event[$prop];
if ($event['allday'] && ($prop == 'start' || $prop == 'end') && ($a instanceof DateTime || $a instanceof DateTimeImmutable) && ($b instanceof DateTime || $b instanceof DateTimeImmutable)) { if ($event['allday'] && ($prop == 'start' || $prop == 'end') && $a instanceof DateTime && $b instanceof DateTime) {
$a = $a->format('Y-m-d'); $a = $a->format('Y-m-d');
$b = $b->format('Y-m-d'); $b = $b->format('Y-m-d');
} }
@ -937,20 +755,19 @@ class caldav_driver extends calendar_driver
} }
// compose vcalendar-style recurrencue rule from structured data // compose vcalendar-style recurrencue rule from structured data
$rrule = !empty($event['recurrence']) ? libcalendaring::to_rrule($event['recurrence']) : ''; $rrule = $event['recurrence'] ? libcalendaring::to_rrule($event['recurrence']) : '';
$sensitivity = strtolower($event['sensitivity']);
$free_busy = strtolower($event['free_busy']);
$event['_recurrence'] = rtrim($rrule, ';'); $event['_recurrence'] = rtrim($rrule, ';');
$event['free_busy'] = isset($this->free_busy_map[$free_busy]) ? $this->free_busy_map[$free_busy] : null; $event['free_busy'] = intval($this->free_busy_map[strtolower($event['free_busy'])]);
$event['sensitivity'] = isset($this->sensitivity_map[$sensitivity]) ? $this->sensitivity_map[$sensitivity] : null; $event['sensitivity'] = intval($this->sensitivity_map[strtolower($event['sensitivity'])]);
$event['all_day'] = !empty($event['allday']) ? 1 : 0;
if ($event['free_busy'] == 'tentative') { if ($event['free_busy'] == 'tentative') {
$event['status'] = 'TENTATIVE'; $event['status'] = 'TENTATIVE';
} }
if (isset($event['allday'])) {
$event['all_day'] = $event['allday'] ? 1 : 0;
}
// compute absolute time to notify the user // compute absolute time to notify the user
$event['notifyat'] = $this->_get_notification($event); $event['notifyat'] = $this->_get_notification($event);
@ -994,19 +811,12 @@ class caldav_driver extends calendar_driver
$sql_set = array(); $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'); $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) { foreach ($set_cols as $col) {
if (is_object($event[$col]) && (is_a($event[$col], 'DateTime') || is_a($event[$col], 'DateTimeImmutable'))) if (is_object($event[$col]) && is_a($event[$col], 'DateTime'))
$sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote($event[$col]->format(self::DB_DATE_FORMAT)); $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])) else if (is_array($event[$col]))
$sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote(join(',', $event[$col])); $sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote(join(',', $event[$col]));
else if (array_key_exists($col, $event) && is_null($event[$col])) else if (array_key_exists($col, $event))
$sql_set[] = $this->rc->db->quote_identifier($col) . '=NULL'; $sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote($event[$col]);
else if (array_key_exists($col, $event)) {
//TODO: proper 4 byte character (eg emoticons) handling
//utf8 in mysql only supports 3 byte characters, so this throws an error if there are emoticons in the description.
//For now we just remove them. But instead of removing, we should prepare the database for them (by using utf8mb4)
$text = preg_replace('/[\xF0-\xF7].../s', '', $event[$col]);
$sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote($text);
}
} }
if ($event['_recurrence']) if ($event['_recurrence'])
@ -1399,25 +1209,12 @@ class caldav_driver extends calendar_driver
$calendar_ids = array_intersect($calendars, array_keys($this->calendars)); $calendar_ids = array_intersect($calendars, array_keys($this->calendars));
// Make sure that the calendars are in sync. // Make sure that the calendars are in sync.
try { foreach ($calendar_ids as $cal_id) {
foreach($calendar_ids as $cal_id) { if (!$this->_is_synced($cal_id))
if(!$this->_is_synced($cal_id)) $this->_sync_calendar($cal_id);
$this->_sync_calendar($cal_id);
}
}
catch(Exception $err) {
self::debug_log("Could not sync calendars: $err");
return [];
} }
$events = $this->_db_load_events($start, $end, $query, $calendars, $virtual, $modifiedsince); return $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;
} }
/** /**
@ -1640,12 +1437,7 @@ class caldav_driver extends calendar_driver
*/ */
private function add_attachment($attachment, $event_id) private function add_attachment($attachment, $event_id)
{ {
if (isset($attachment['data'])) $data = $attachment['data'] ? $attachment['data'] : file_get_contents($attachment['path']);
$data = $attachment['data'];
elseif (isset($attachment['path']) && $attachment['path'] != '')
$data = file_get_contents($attachment['path']);
else
return 0;
$query = $this->rc->db->query( $query = $this->rc->db->query(
"INSERT INTO " . $this->db_attachments . "INSERT INTO " . $this->db_attachments .
@ -1790,7 +1582,7 @@ class caldav_driver extends calendar_driver
private function serialize_alarms($valarms) private function serialize_alarms($valarms)
{ {
foreach ((array)$valarms as $i => $alarm) { foreach ((array)$valarms as $i => $alarm) {
if ($alarm['trigger'] instanceof DateTime || $alarm['trigger'] instanceof DateTimeImmutable) { if ($alarm['trigger'] instanceof DateTime) {
$valarms[$i]['trigger'] = '@' . $alarm['trigger']->format('c'); $valarms[$i]['trigger'] = '@' . $alarm['trigger']->format('c');
} }
} }
@ -1840,7 +1632,7 @@ class caldav_driver extends calendar_driver
$attendees = json_decode($s_attendees, true); $attendees = json_decode($s_attendees, true);
} // decode the old serialization format } // decode the old serialization format
else { else {
foreach (explode("\n", $s_attendees) as $line) { foreach (explode("\n", $event['attendees']) as $line) {
$att = array(); $att = array();
foreach (rcube_utils::explode_quoted_string(';', $line) as $prop) { foreach (rcube_utils::explode_quoted_string(';', $line) as $prop) {
list($key, $value) = explode("=", $prop); list($key, $value) = explode("=", $prop);
@ -1878,15 +1670,13 @@ class caldav_driver extends calendar_driver
} }
} }
foreach (array($this->db_sources, $this->db_calendars, 'itipinvitations') as $table) { foreach (array($this->db_calendars, 'itipinvitations') as $table) {
$db->query("DELETE FROM $table WHERE user_id=?", $user->ID); $db->query("DELETE FROM $table WHERE user_id=?", $user->ID);
} }
} }
/** /**
* Callback function to produce driver-specific calendar create/edit form * 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 string Request action 'form-edit|form-new'
* @param array Calendar properties (e.g. id, color) * @param array Calendar properties (e.g. id, color)
@ -1896,128 +1686,44 @@ class caldav_driver extends calendar_driver
*/ */
public function calendar_form($action, $calendar, $formfields) public function calendar_form($action, $calendar, $formfields)
{ {
switch($action) { // Make sure we have current attributes
case 'form-source-new': $calendar = $this->calendars[$calendar["id"]];
array_splice($formfields, 0);
$input_caldav_url = new html_inputfield( array( $input_caldav_url = new html_inputfield( array(
'name' => 'caldav_url', "name" => "caldav_url",
'id' => 'caldav_url', "id" => "caldav_url",
'size' => 20 "size" => 20
)); ));
$formfields['caldav_url'] = array(
'label' => $this->cal->gettext('url'),
'value' => $input_caldav_url->show(null),
'id' => 'caldav_url',
);
$formfields["caldav_url"] = array(
"label" => $this->cal->gettext("caldavurl"),
"value" => $input_caldav_url->show($calendar["caldav_url"]),
"id" => "caldav_url",
);
$input_caldav_user = new html_inputfield( array( $input_caldav_user = new html_inputfield( array(
'name' => 'caldav_user', "name" => "caldav_user",
'id' => 'caldav_user', "id" => "caldav_user",
'size' => 20 "size" => 20
)); ));
$formfields['caldav_user'] = array(
'label' => $this->cal->gettext('username'),
'value' => $input_caldav_user->show(null),
'id' => 'caldav_user',
);
$formfields["caldav_user"] = array(
"label" => $this->cal->gettext("username"),
"value" => $input_caldav_user->show($calendar["caldav_user"]),
"id" => "caldav_user",
);
$input_caldav_pass = new html_passwordfield( array( $input_caldav_pass = new html_passwordfield( array(
"name" => "caldav_pass", "name" => "caldav_pass",
"id" => "caldav_pass", "id" => "caldav_pass",
"size" => 20 "size" => 20
)); ));
$formfields['caldav_pass'] = array(
'label' => $this->cal->gettext('password'),
'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 .' WHERE user_id = ?',
$this->rc->user->ID
);
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;
case 'form-new':
$result = $this->rc->db->query(
'SELECT source_id, caldav_url FROM '.$this->db_sources .' WHERE user_id = ?',
$this->rc->user->ID
);
$sources_exist = $this->rc->db->num_rows($result);
if($this->rc->db->num_rows($result)) {
$is_ical = new html_checkbox( array(
'name' => "is_ical",
'value' => 1,
'onclick' => '
if(this.checked) {
$("#ical_url").removeClass("hidden");
$("#caldav_url").addClass("hidden");
}
else {
$("#ical_url").addClass("hidden");
$("#caldav_url").removeClass("hidden");
}'
));
$formfields['is_ical'] = array(
'label' => $this->cal->gettext('calendar_ical_file'),
'value' => $is_ical->show(null),
'class' => 'hidden'
);
$ical_url = new html_inputfield( array(
'name' => 'ical_url',
'size' => 20,
'id' => 'ical_url',
'class' => 'hidden'
));
$caldav_url = new html_select([
'name' => 'source_id',
'id' => 'caldav_url'
]);
while($source = $this->rc->db->fetch_assoc($result)) {
$caldav_url->add($source['caldav_url'], $source['source_id']);
}
$formfields['url'] = array(
'label' => $this->cal->gettext('url'),
'value' => $caldav_url->show(null) .$ical_url->show(null),
);
}
else {
$ical_url = new html_inputfield( array(
'name' => 'ical_url',
'size' => 20,
));
$formfields['url'] = array(
'label' => $this->cal->gettext('calendar_ical_file'),
'value' => $ical_url->show(null),
);
$enable_ics = new html_hiddenfield(['name' => 'is_ical', 'value' => 1]);
$formfields['hidden'] = array(
'label' => ' ',
'value' =>$enable_ics->show(null),
);
}
}
$formfields["caldav_pass"] = array(
"label" => $this->cal->gettext("password"),
"value" => $input_caldav_pass->show(null), // Don't send plain text password to GUI
"id" => "caldav_pass",
);
return parent::calendar_form($action, $calendar, $formfields); return parent::calendar_form($action, $calendar, $formfields);
} }
@ -2041,6 +1747,22 @@ else {
else return $url; 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 * Auto discover calenders available to the user on the caldav server
@ -2048,7 +1770,7 @@ else {
* caldav_url: Absolute URL to CalDAV server * caldav_url: Absolute URL to CalDAV server
* caldav_user: Username * caldav_user: Username
* caldav_pass: Password * caldav_pass: Password
* @return array (empty on error) with the following calendar props: * @return False on error or an array with the following calendar props:
* name: Calendar display name * name: Calendar display name
* href: Absolute calendar URL * href: Absolute calendar URL
*/ */
@ -2057,9 +1779,9 @@ else {
$calendars = array(); $calendars = array();
$current_user_principal = array('{DAV:}current-user-principal'); $current_user_principal = array('{DAV:}current-user-principal');
$calendar_home_set = array('{urn:ietf:params:xml:ns:caldav}calendar-home-set'); $calendar_home_set = array('{urn:ietf:params:xml:ns:caldav}calendar-home-set');
$cal_attribs = array('{DAV:}resourcetype', '{DAV:}displayname', '{http://apple.com/ns/ical/}calendar-color'); $cal_attribs = array('{DAV:}resourcetype', '{DAV:}displayname');
require_once 'caldav_client.php'; require_once ($this->cal->home.'/lib/caldav-client.php');
$caldav = new caldav_client($props["caldav_url"], $props["caldav_user"], $props["caldav_pass"]); $caldav = new caldav_client($props["caldav_url"], $props["caldav_user"], $props["caldav_pass"]);
$tokens = parse_url($props["caldav_url"]); $tokens = parse_url($props["caldav_url"]);
@ -2068,10 +1790,10 @@ else {
$response = $caldav->prop_find($caldav_url, array_merge($current_user_principal,$cal_attribs), 0); $response = $caldav->prop_find($caldav_url, array_merge($current_user_principal,$cal_attribs), 0);
if (!$response) { if (!$response) {
$this->_raise_error("Resource \"$caldav_url\" has no collections"); $this->_raise_error("Resource \"$caldav_url\" has no collections");
return []; return false;
} }
else if (array_key_exists ('{DAV:}resourcetype', $response) && else if (array_key_exists ('{DAV:}resourcetype', $response) &&
$response['{DAV:}resourcetype'] instanceof Sabre\DAV\Xml\Property\ResourceType && $response['{DAV:}resourcetype'] instanceof OldSabre\DAV\Property\ResourceType &&
in_array('{urn:ietf:params:xml:ns:caldav}calendar', in_array('{urn:ietf:params:xml:ns:caldav}calendar',
$response['{DAV:}resourcetype']->getValue())) { $response['{DAV:}resourcetype']->getValue())) {
@ -2088,25 +1810,22 @@ else {
// directly return given url as it is a calendar // directly return given url as it is a calendar
} }
// probe further for principal url and user home set // probe further for principal url and user home set
// $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]];
$caldav_url = $base_uri . $response[$current_user_principal[0]][0]['value'];
$response = $caldav->prop_find($caldav_url, $calendar_home_set, 0); $response = $caldav->prop_find($caldav_url, $calendar_home_set, 0);
if (!$response) { if (!$response) {
$this->_raise_error("Resource \"$caldav_url\" contains no calendars."); $this->_raise_error("Resource \"$caldav_url\" contains no calendars.");
return []; return false;
} }
// $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]];
$caldav_url = $base_uri . $response[$calendar_home_set[0]][0]['value'];
$response = $caldav->prop_find($caldav_url, $cal_attribs, 1); $response = $caldav->prop_find($caldav_url, $cal_attribs, 1);
foreach($response as $collection => $attribs) foreach($response as $collection => $attribs)
{ {
$found = false; $found = false;
$name = ''; $name = '';
$color = null;
foreach($attribs as $key => $value) foreach($attribs as $key => $value)
{ {
if ($key == '{DAV:}resourcetype' && is_object($value)) { if ($key == '{DAV:}resourcetype' && is_object($value)) {
if ($value instanceof Sabre\DAV\Xml\Property\ResourceType) { if ($value instanceof OldSabre\DAV\Property\ResourceType) {
$values = $value->getValue(); $values = $value->getValue();
if (in_array('{urn:ietf:params:xml:ns:caldav}calendar', $values)) if (in_array('{urn:ietf:params:xml:ns:caldav}calendar', $values))
$found = true; $found = true;
@ -2115,29 +1834,11 @@ else {
else if ($key == '{DAV:}displayname') { else if ($key == '{DAV:}displayname') {
$name = $value; $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
switch(strlen($value)) {
case 4:
preg_match('/#(.)(.)(.)/', $value, $matches);
$color = $matches[1] .$matches[1] .
$matches[2] .$matches[2] .
$matches[3] .$matches[3];
break;
case 7:
$color = substr($value, 1);
break;
case 9:
$color = substr($value, 1, 6);
break;
}
}
} }
if ($found) { if ($found) {
array_push($calendars, array( array_push($calendars, array(
'name' => $name, 'name' => $name,
'href' => $base_uri.$collection, 'href' => $base_uri.$collection,
'color' => $color ?: 'cc0000'
)); ));
} }
} }
@ -2230,17 +1931,6 @@ else {
foreach($updates as $update) foreach($updates as $update)
{ {
if($update['remote_event']['allday'])
{
//caldav has exclusive end dates set to midnight of the next day.
//But we need it inclusive, so we just reduce it by an hour:
$old = $update['remote_event']['end'];
$new = new DateTime('now', $old->getTimezone());
$new->setTimestamp($old->getTimestamp() - 3600);
$update['remote_event']['end'] = $new;
}
// local event -> update event // local event -> update event
if(isset($update["local_event"])) if(isset($update["local_event"]))
{ {
@ -2269,13 +1959,7 @@ else {
"caldav_url" => $update["url"], "caldav_url" => $update["url"],
"caldav_tag" => $update["etag"])); "caldav_tag" => $update["etag"]));
try { $event_id = $this->new_event($event);
$event_id = $this->new_event($event);
}
catch(Exception $e) {
self::debug_log($e);
$event_id = 0;
}
if($event_id) if($event_id)
{ {
self::debug_log("Created event \"$event_id\"."); self::debug_log("Created event \"$event_id\".");
@ -2332,7 +2016,7 @@ else {
{ {
switch ($this->rc->db->db_provider) { switch ($this->rc->db->db_provider) {
case 'postgres': case 'postgres':
return "EXTRACT (EPOCH FROM $field::timestamp without time zone)"; return "EXTRACT (EPOCH FROM $field)";
default: default:
return "UNIX_TIMESTAMP($field)"; return "UNIX_TIMESTAMP($field)";
} }
@ -2340,13 +2024,13 @@ else {
private function _decrypt_pass($pass) { private function _decrypt_pass($pass) {
$p = base64_decode($pass); $p = base64_decode($pass);
$e = new Encryption(); $e = new Encryption(MCRYPT_BlOWFISH, MCRYPT_MODE_CBC);
return $e->decrypt($p); return $e->decrypt($p, $this->crypt_key);
} }
private function _encrypt_pass($pass) { private function _encrypt_pass($pass) {
$e = new Encryption(); $e = new Encryption(MCRYPT_BlOWFISH, MCRYPT_MODE_CBC);
$p = $e->encrypt($pass); $p = $e->encrypt($pass, $this->crypt_key);
return base64_encode($p); return base64_encode($p);
} }
} }

View file

@ -4,7 +4,6 @@
* *
* @version @package_version@ * @version @package_version@
* @author Daniel Morlock <daniel.morlock@awesome-it.de> * @author Daniel Morlock <daniel.morlock@awesome-it.de>
* @author JodliDev <jodlidev@gmail.com>
* *
* Copyright (C) Awesome IT GbR <info@awesome-it.de> * Copyright (C) Awesome IT GbR <info@awesome-it.de>
* *
@ -22,19 +21,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
require_once 'caldav_client.php'; require_once (dirname(__FILE__).'/../../lib/caldav-client.php');
require_once 'Isync.php';
class caldav_sync implements Isync class caldav_sync
{ {
const ACTION_NONE = 1;
const ACTION_UPDATE = 2;
const ACTION_CREATE = 4;
private $cal_id = null; private $cal_id = null;
private $ctag = null; private $ctag = null;
private $username = null; private $username = null;
private $pass = null; private $pass = null;
private $url = null; private $url = null;
public $caldav = null;
/** /**
* Default constructor for calendar synchronization adapter. * Default constructor for calendar synchronization adapter.
* *
@ -69,7 +69,7 @@ class caldav_sync implements Isync
* Determines whether current calendar needs to be synced * Determines whether current calendar needs to be synced
* regarding the CalDAV ctag. * regarding the CalDAV ctag.
* *
* @return boolean True if the current calendar ctag differs from the CalDAV tag which * @return True if the current calendar ctag differs from the CalDAV tag which
* indicates that there are changes that must be synched. Returns false * indicates that there are changes that must be synched. Returns false
* if the calendar is up to date, no sync necesarry. * if the calendar is up to date, no sync necesarry.
*/ */
@ -179,7 +179,7 @@ class caldav_sync implements Isync
/** /**
* Fetches event data and attaches it to the given update properties. * Fetches event data and attaches it to the given update properties.
* *
* @param $updates array of update properties. * @param $updates List of update properties.
* @return array List of update properties with additional key "remote_event" containing the current caldav event. * @return array List of update properties with additional key "remote_event" containing the current caldav event.
*/ */
private function _get_event_data($updates) private function _get_event_data($updates)
@ -210,7 +210,7 @@ class caldav_sync implements Isync
* Creates the given event on the CalDAV server. * Creates the given event on the CalDAV server.
* *
* @param array Hash array with event properties. * @param array Hash array with event properties.
* @return array with updated "caldav_url" and "caldav_tag" attributes, null on error. * @return Event with updated "caldav_url" and "caldav_tag" attributes, false on error.
*/ */
public function create_event($event) public function create_event($event)
{ {
@ -222,8 +222,7 @@ class caldav_sync implements Isync
caldav_driver::debug_log("Push new event to url ".$props["caldav_url"]); caldav_driver::debug_log("Push new event to url ".$props["caldav_url"]);
$result = $this->caldav->put_event($props["caldav_url"], $event); $result = $this->caldav->put_event($props["caldav_url"], $event);
if($result == false || $result < 0) if($result == false || $result < 0) return false;
return null;
return array_merge($event, $props); return array_merge($event, $props);
} }
@ -231,7 +230,7 @@ class caldav_sync implements Isync
* Updates the given event on the CalDAV server. * Updates the given event on the CalDAV server.
* *
* @param array Hash array with event properties to update, must include "uid", "caldav_url" and "caldav_tag". * @param array Hash array with event properties to update, must include "uid", "caldav_url" and "caldav_tag".
* @return boolean True on success, false on error, -1 if the given event/etag is not up to date. * @return True on success, false on error, -1 if the given event/etag is not up to date.
*/ */
public function update_event($event) public function update_event($event)
{ {
@ -243,7 +242,7 @@ class caldav_sync implements Isync
* Removes the given event from the caldav server. * Removes the given event from the caldav server.
* *
* @param array Hash array with events properties, must include "caldav_url". * @param array Hash array with events properties, must include "caldav_url".
* @return boolean True on success, false on error. * @return True on success, false on error.
*/ */
public function remove_event($event) public function remove_event($event)
{ {

View file

@ -1,73 +0,0 @@
<?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 "";
}
}
}
?>

View file

@ -362,11 +362,11 @@ abstract class calendar_driver
{ {
$valid = true; $valid = true;
if (empty($event['start']) || !is_object($event['start']) || (!is_a($event['start'], 'DateTime') && !is_a($event['start'], 'DateTimeImmutable'))) { if (empty($event['start']) || !is_object($event['start']) || !is_a($event['start'], 'DateTime')) {
$valid = false; $valid = false;
} }
if (empty($event['end']) || !is_object($event['end']) || (!is_a($event['end'], 'DateTime') && !is_a($event['end'], 'DateTimeImmutable'))) { if (empty($event['end']) || !is_object($event['end']) || !is_a($event['end'], 'DateTime')) {
$valid = false; $valid = false;
} }

View file

@ -715,8 +715,6 @@ class database_driver extends calendar_driver
'title', 'description', 'location', 'categories', 'url', 'free_busy', 'priority', 'title', 'description', 'location', 'categories', 'url', 'free_busy', 'priority',
'sensitivity', 'status', 'attendees', 'alarms', 'notifyat' 'sensitivity', 'status', 'attendees', 'alarms', 'notifyat'
); );
if(array_key_exists('notifyat', $event) && empty($event['notifyat']))
unset($event['notifyat']);
foreach ($set_cols as $col) { foreach ($set_cols as $col) {
if (!empty($event[$col]) && is_a($event[$col], 'DateTime')) { if (!empty($event[$col]) && is_a($event[$col], 'DateTime')) {

View file

@ -0,0 +1,91 @@
/**
* iCAL Client
*
* @version @package_version@
* @author Daniel Morlock <daniel.morlock@awesome-it.de>
*
* 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/>.
*/
CREATE TABLE IF NOT EXISTS `ical_calendars` (
`calendar_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_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',
`ical_url` varchar(255) NOT NULL,
`ical_user` varchar(255) DEFAULT NULL,
`ical_pass` varchar(1024) DEFAULT NULL,
`ical_last_change` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY(`calendar_id`),
INDEX `ical_user_name_idx` (`user_id`, `name`),
CONSTRAINT `fk_ical_calendars_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 `ical_events` (
`event_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`calendar_id` int(11) UNSIGNED NOT NULL DEFAULT '0',
`recurrence_id` int(11) UNSIGNED NOT NULL DEFAULT '0',
`uid` varchar(255) NOT NULL DEFAULT '',
`instance` varchar(16) NOT NULL DEFAULT '',
`isexception` tinyint(1) NOT NULL DEFAULT '0',
`created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`sequence` int(1) UNSIGNED NOT NULL DEFAULT '0',
`start` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`end` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`recurrence` varchar(255) DEFAULT NULL,
`title` varchar(255) NOT NULL,
`description` text NOT NULL,
`location` varchar(255) NOT NULL DEFAULT '',
`categories` varchar(255) NOT NULL DEFAULT '',
`url` varchar(255) NOT NULL DEFAULT '',
`all_day` tinyint(1) NOT NULL DEFAULT '0',
`free_busy` tinyint(1) NOT NULL DEFAULT '0',
`priority` tinyint(1) NOT NULL DEFAULT '0',
`sensitivity` tinyint(1) NOT NULL DEFAULT '0',
`status` varchar(32) NOT NULL DEFAULT '',
`alarms` text NULL DEFAULT NULL,
`attendees` text DEFAULT NULL,
`notifyat` datetime DEFAULT NULL,
`ical_url` varchar(255) NOT NULL,
`ical_last_change` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY(`event_id`),
INDEX `ical_uid_idx` (`uid`),
INDEX `ical_recurrence_idx` (`recurrence_id`),
INDEX `ical_calendar_notify_idx` (`calendar_id`,`notifyat`),
CONSTRAINT `fk_ical_events_calendar_id` FOREIGN KEY (`calendar_id`)
REFERENCES `ical_calendars`(`calendar_id`) ON DELETE CASCADE ON UPDATE CASCADE
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
CREATE TABLE IF NOT EXISTS `ical_attachments` (
`attachment_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`event_id` int(11) UNSIGNED NOT NULL DEFAULT '0',
`filename` varchar(255) NOT NULL DEFAULT '',
`mimetype` varchar(255) NOT NULL DEFAULT '',
`size` int(11) NOT NULL DEFAULT '0',
`data` longtext NOT NULL,
PRIMARY KEY(`attachment_id`),
CONSTRAINT `fk_ical_attachments_event_id` FOREIGN KEY (`event_id`)
REFERENCES `ical_events`(`event_id`) ON DELETE CASCADE ON UPDATE CASCADE
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
REPLACE INTO `system` (`name`, `value`) VALUES ('calendar-ical-version', '2015022700');

View file

View file

@ -0,0 +1,124 @@
/**
* iCAL Client
*
* @version @package_version@
* @author Daniel Morlock <daniel.morlock@awesome-it.de>
*
* 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/>.
*/
/* Create new tables */
CREATE TABLE IF NOT EXISTS `ical_calendars` (
`calendar_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_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',
`ical_url` varchar(255) NOT NULL,
`ical_user` varchar(255) DEFAULT NULL,
`ical_pass` varchar(1024) DEFAULT NULL,
`ical_last_change` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY(`calendar_id`),
INDEX `ical_user_name_idx` (`user_id`, `name`),
CONSTRAINT `fk_ical_calendars_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 `ical_events` (
`event_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`calendar_id` int(11) UNSIGNED NOT NULL DEFAULT '0',
`recurrence_id` int(11) UNSIGNED NOT NULL DEFAULT '0',
`uid` varchar(255) NOT NULL DEFAULT '',
`created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`sequence` int(1) UNSIGNED NOT NULL DEFAULT '0',
`start` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`end` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`recurrence` varchar(255) DEFAULT NULL,
`title` varchar(255) NOT NULL,
`description` text NOT NULL,
`location` varchar(255) NOT NULL DEFAULT '',
`categories` varchar(255) NOT NULL DEFAULT '',
`url` varchar(255) NOT NULL DEFAULT '',
`all_day` tinyint(1) NOT NULL DEFAULT '0',
`free_busy` tinyint(1) NOT NULL DEFAULT '0',
`priority` tinyint(1) NOT NULL DEFAULT '0',
`sensitivity` tinyint(1) NOT NULL DEFAULT '0',
`status` varchar(32) NOT NULL DEFAULT '',
`alarms` varchar(255) DEFAULT NULL,
`attendees` text DEFAULT NULL,
`notifyat` datetime DEFAULT NULL,
PRIMARY KEY(`event_id`),
INDEX `ical_uid_idx` (`uid`),
INDEX `ical_recurrence_idx` (`recurrence_id`),
INDEX `ical_calendar_notify_idx` (`calendar_id`,`notifyat`),
CONSTRAINT `fk_ical_events_calendar_id` FOREIGN KEY (`calendar_id`)
REFERENCES `calendars`(`calendar_id`) ON DELETE CASCADE ON UPDATE CASCADE
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
CREATE TABLE IF NOT EXISTS `ical_attachments` (
`attachment_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`event_id` int(11) UNSIGNED NOT NULL DEFAULT '0',
`filename` varchar(255) NOT NULL DEFAULT '',
`mimetype` varchar(255) NOT NULL DEFAULT '',
`size` int(11) NOT NULL DEFAULT '0',
`data` longtext NOT NULL,
PRIMARY KEY(`attachment_id`),
CONSTRAINT `fk_ical_attachments_event_id` FOREIGN KEY (`event_id`)
REFERENCES `events`(`event_id`) ON DELETE CASCADE ON UPDATE CASCADE
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
/* Migrate Data */
INSERT INTO ical_calendars
SELECT calendar_id, user_id, `name`, color, showalarms,
url as ical_url, NULL as ical_user, NULL as ical_pass, last_change as ical_last_change
FROM calendars cal, ical_props dav
WHERE dav.obj_id = cal.calendar_id
AND dav.obj_type = 'ical';
INSERT INTO ical_events SELECT e.* FROM `events` e
WHERE e.calendar_id IN (
SELECT obj_id FROM ical_props
WHERE obj_type = 'ical'
);
INSERT INTO ical_attachments SELECT * FROM attachments a
WHERE a.event_id IN (
SELECT e.event_id FROM `events` e
WHERE e.calendar_id IN (
SELECT obj_id FROM ical_props
WHERE obj_type = 'ical'
)
);
/* Drop deprecated data */
DELETE FROM `events` WHERE event_id IN (
SELECT obj_id FROM ical_props dav
WHERE dav.obj_type = 'vevent'
);
DELETE FROM calendars WHERE calendar_id IN (
SELECT obj_id FROM ical_props dav
WHERE dav.obj_type = 'ical'
);
DELETE FROM attachments WHERE event_id IN (
SELECT obj_id FROM ical_props dav
WHERE dav.obj_type = 'vevent'
);
DROP TABLE ical_props;

View file

@ -0,0 +1,14 @@
-- add identifier for recurring instances and exceptions
ALTER TABLE `ical_events` ADD `instance` varchar(16) NOT NULL DEFAULT '' AFTER `uid`;
ALTER TABLE `ical_events` ADD `isexception` tinyint(1) NOT NULL DEFAULT '0' AFTER `instance`;
UPDATE `ical_events` SET `instance` = DATE_FORMAT(`start`, '%Y%m%d')
WHERE `recurrence_id` != 0 AND `instance` = '' AND `all_day` = 1;
UPDATE `ical_events` SET `instance` = DATE_FORMAT(`start`, '%Y%m%dT%k%i%s')
WHERE `recurrence_id` != 0 AND `instance` = '' AND `all_day` = 0;
-- extend alarms columns for multiple values
ALTER TABLE `ical_events` CHANGE `alarms` `alarms` TEXT NULL DEFAULT NULL;

1821
drivers/ical/ical_driver.php Normal file

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,6 @@
* *
* @version @package_version@ * @version @package_version@
* @author Daniel Morlock <daniel.morlock@awesome-it.de> * @author Daniel Morlock <daniel.morlock@awesome-it.de>
* @author JodliDev <jodlidev@gmail.com>
* *
* Copyright (C) Awesome IT GbR <info@awesome-it.de> * Copyright (C) Awesome IT GbR <info@awesome-it.de>
* *
@ -22,7 +21,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
class ical_sync implements Isync class ical_sync
{ {
const ACTION_NONE = 1; const ACTION_NONE = 1;
const ACTION_UPDATE = 2; const ACTION_UPDATE = 2;
@ -41,12 +40,12 @@ class ical_sync implements Isync
* @param array Hash array with ical properties: * @param array Hash array with ical properties:
* url: Absolute URL to iCAL resource. * url: Absolute URL to iCAL resource.
*/ */
public function __construct($props) public function __construct($cal_id, $props)
{ {
$this->ical = libcalendaring::get_ical(); $this->ical = libcalendaring::get_ical();
$this->cal_id = $props["id"]; $this->cal_id = $cal_id;
$this->url = $props["caldav_url"]; $this->url = $props["ical_url"];
$this->user = isset($props["ical_user"]) ? $props["ical_user"] : null; $this->user = isset($props["ical_user"]) ? $props["ical_user"] : null;
$this->pass = isset($props["ical_pass"]) ? $props["ical_pass"] : null; $this->pass = isset($props["ical_pass"]) ? $props["ical_pass"] : null;
} }
@ -54,7 +53,7 @@ class ical_sync implements Isync
/** /**
* Determines whether current calendar needs to be synced. * Determines whether current calendar needs to be synced.
* *
* @return boolean True if the current calendar needs to be synced, false otherwise. * @return True if the current calendar needs to be synced, false otherwise.
*/ */
public function is_synced() public function is_synced()
{ {
@ -100,64 +99,27 @@ class ical_sync implements Isync
foreach ($this->ical->import($vcal) as $remote_event) { foreach ($this->ical->import($vcal) as $remote_event) {
// Attach remote event to current calendar // Attach remote event to current calendar
$remote_event['calendar'] = $this->cal_id; $remote_event["calendar"] = $this->cal_id;
$local_event = null; $local_event = null;
if($events_hash[$remote_event['uid']]) if($events_hash[$remote_event['uid']])
$local_event = $events_hash[$remote_event['uid']]; $local_event = $events_hash[$remote_event['uid']];
// Determine whether event don't need an update. // Determine whether event don't need an update.
if($local_event && $local_event['changed'] >= $remote_event['changed']) { if($local_event && $local_event["changed"] >= $remote_event["changed"])
{
array_push($synced, $local_event["id"]); array_push($synced, $local_event["id"]);
} }
else if($local_event) { else
array_push($updates, array('local_event' => $local_event, 'remote_event' => $remote_event, 'url' => $this->url)); {
} array_push($updates, array('local_event' => $local_event, 'remote_event' => $remote_event));
else {
array_push($updates, array('remote_event' => $remote_event, 'url' => $this->url));
} }
} }
} }
return array($updates, $synced); return array($updates, $synced);
} }
/**
* Getter for current calendar ctag (only for CalDAV).
* @return string
*/
public function get_ctag() {
return 'none';
}
/**
* Creates the given event.
*
* @param array Hash array with event properties.
* @return array with updated "caldav_url" and "caldav_tag" attributes, null on error.
*/
public function create_event($event) {
return $event;
}
/**
* Updates the given event.
*
* @param array Hash array with event properties to update, must include "uid", "caldav_url" and "caldav_tag".
* @return boolean True on success, false on error, -1 if the given event/etag is not up to date.
*/
public function update_event($event) {
return false;
}
/**
* Removes the given event.
*
* @param array Hash array with events properties, must include "caldav_url".
* @return boolean True on success, false on error.
*/
public function remove_event($event) {
return false;
}
} }
;
?> ?>

View file

@ -41,13 +41,12 @@ class resources_driver_ldap extends resources_driver
/** /**
* Fetch resource objects to be displayed for booking * Fetch resource objects to be displayed for booking
* *
* @param string $query Search query (optional) * @param string $query Search query (optional)
* @param int $num Max size of the result * @param int $num Max size of the result
* @param string $searchField Field to search with query
* *
* @return array List of resource records available for booking * @return array List of resource records available for booking
*/ */
public function load_resources($query = null, $num = 5000, $searchField = '*') public function load_resources($query = null, $num = 5000)
{ {
if (!($ldap = $this->connect())) { if (!($ldap = $this->connect())) {
return []; return [];
@ -57,7 +56,7 @@ class resources_driver_ldap extends resources_driver
$ldap->set_pagesize($num); $ldap->set_pagesize($num);
if (isset($query)) { if (isset($query)) {
$results = $ldap->search($searchField, $query, 0, true, true); $results = $ldap->search('*', $query, 0, true, true);
} }
else { else {
$results = $ldap->list_records(); $results = $ldap->list_records();

View file

@ -5,7 +5,6 @@
* *
* @version @package_version@ * @version @package_version@
* @author Daniel Morlock <daniel.morlock@awesome-it.de> * @author Daniel Morlock <daniel.morlock@awesome-it.de>
* @author JodliDev <jodlidev@gmail.com>
* *
* Copyright (C) Awesome IT GbR <info@awesome-it.de> * Copyright (C) Awesome IT GbR <info@awesome-it.de>
* *
@ -23,7 +22,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
class caldav_client extends Sabre\DAV\Client require_once (dirname(__FILE__).'/SabreDAV/vendor/autoload.php');
class caldav_client extends OldSabre\DAV\Client
{ {
const CLARK_GETCTAG = '{http://calendarserver.org/ns/}getctag'; const CLARK_GETCTAG = '{http://calendarserver.org/ns/}getctag';
const CLARK_GETETAG = '{DAV:}getetag'; const CLARK_GETETAG = '{DAV:}getetag';
@ -45,8 +47,7 @@ class caldav_client extends Sabre\DAV\Client
// Include libvcalendar on demand ... // Include libvcalendar on demand ...
if(!class_exists("libvcalendar")) if(!class_exists("libvcalendar"))
require_once __DIR__ .'/../../../libcalendaring/libvcalendar.php'; require_once (dirname(__FILE__).'/../../libcalendaring/libvcalendar.php');
$this->libvcal = new libvcalendar(); $this->libvcal = new libvcalendar();
@ -56,10 +57,12 @@ class caldav_client extends Sabre\DAV\Client
$settings = array( $settings = array(
'baseUri' => $this->base_uri, 'baseUri' => $this->base_uri,
'authType' => Sabre\DAV\Client::AUTH_BASIC 'authType' => OldSabre\DAV\Client::AUTH_BASIC
); );
// Forward SSL certificate setting to Sabre\DAV an onwards to CURL
$this->rc = rcmail::get_instance(); $this->rc = rcmail::get_instance();
parent::setVerifyPeer($this->rc->config->get('calendar_curl_secure_ssl', true));
if ($user) $settings['userName'] = $user; if ($user) $settings['userName'] = $user;
if ($pass) $settings['password'] = $pass; if ($pass) $settings['password'] = $pass;
@ -82,7 +85,7 @@ class caldav_client extends Sabre\DAV\Client
if (isset($arr[self::CLARK_GETCTAG])) if (isset($arr[self::CLARK_GETCTAG]))
return $arr[self::CLARK_GETCTAG]; return $arr[self::CLARK_GETCTAG];
} }
catch(Sabre\DAV\Exception $err) catch(OldSabre\DAV\Exception $err)
{ {
rcube::raise_error(array( rcube::raise_error(array(
'code' => $err->getHTTPCode(), 'code' => $err->getHTTPCode(),
@ -125,7 +128,7 @@ class caldav_client extends Sabre\DAV\Client
} }
} }
} }
catch(Sabre\DAV\Exception $err) catch(OldSabre\DAV\Exception $err)
{ {
rcube::raise_error(array( rcube::raise_error(array(
'code' => $err->getHTTPCode(), 'code' => $err->getHTTPCode(),
@ -161,14 +164,12 @@ class caldav_client extends Sabre\DAV\Client
foreach ($vcals as $path => $response) foreach ($vcals as $path => $response)
{ {
$vcal = $response[self::CLARK_CALDATA]; $vcal = $response[self::CLARK_CALDATA];
if(!$vcal)
continue;
foreach ($this->libvcal->import($vcal) as $event) { foreach ($this->libvcal->import($vcal) as $event) {
$events[$path] = $event; $events[$path] = $event;
} }
} }
} }
catch(Sabre\DAV\Exception $err) catch(OldSabre\DAV\Exception $err)
{ {
rcube::raise_error(array( rcube::raise_error(array(
'code' => $err->getHTTPCode(), 'code' => $err->getHTTPCode(),
@ -204,7 +205,7 @@ class caldav_client extends Sabre\DAV\Client
foreach ($properties as $property) foreach ($properties as $property)
{ {
list($namespace, $elementName) = Sabre\Xml\Service::parseClarkNotation($property); list($namespace, $elementName) = OldSabre\DAV\XMLUtil::parseClarkNotation($property);
if ($namespace === 'DAV:') if ($namespace === 'DAV:')
{ {
@ -295,12 +296,12 @@ class caldav_client extends Sabre\DAV\Client
return $response["statusCode"] == 201 || // 201 (created, successfully created) return $response["statusCode"] == 201 || // 201 (created, successfully created)
$response["statusCode"] == 204; // 204 (no content, successfully updated) $response["statusCode"] == 204; // 204 (no content, successfully updated)
} }
catch(Sabre\DAV\Exception\PreconditionFailed $err) catch(OldSabre\DAV\Exception\PreconditionFailed $err)
{ {
// Event tag not up to date, must be updated first ... // Event tag not up to date, must be updated first ...
return -1; return -1;
} }
catch(Sabre\DAV\Exception $err) catch(OldSabre\DAV\Exception $err)
{ {
rcube::raise_error(array( rcube::raise_error(array(
'code' => $err->getHTTPCode(), 'code' => $err->getHTTPCode(),
@ -332,12 +333,12 @@ class caldav_client extends Sabre\DAV\Client
return $response["statusCode"] == 204 || // 204 (no content, successfully deleted) return $response["statusCode"] == 204 || // 204 (no content, successfully deleted)
$response["statusCode"] == 200; // 200 (OK, successfully deleted) $response["statusCode"] == 200; // 200 (OK, successfully deleted)
} }
catch(Sabre\DAV\Exception\PreconditionFailed $err) catch(OldSabre\DAV\Exception\PreconditionFailed $err)
{ {
// Event tag not up to date, must be updated first ... // Event tag not up to date, must be updated first ...
return -1; return -1;
} }
catch(Sabre\DAV\Exception $err) catch(OldSabre\DAV\Exception $err)
{ {
rcube::raise_error(array( rcube::raise_error(array(
'code' => $err->getHTTPCode(), 'code' => $err->getHTTPCode(),
@ -362,7 +363,7 @@ class caldav_client extends Sabre\DAV\Client
try { try {
$response = $this->propFind($path, $props, $depth); $response = $this->propFind($path, $props, $depth);
} }
catch(Sabre\DAV\Exception $err) catch(OldSabre\DAV\Exception $err)
{ {
rcube::raise_error(array( rcube::raise_error(array(
'code' => $err->getHTTPCode(), 'code' => $err->getHTTPCode(),
@ -374,31 +375,5 @@ class caldav_client extends Sabre\DAV\Client
} }
return $response; 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);
if($response['statusCode'] !== 201) {
rcmail::console('Could not create calendar. Response:' .print_r($response, true));
return false;
}
return true;
}
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)
}
}; };
?> ?>

View file

@ -103,10 +103,31 @@ class calendar_ui
$this->cal->register_handler('plugin.events_export_form', [$this, 'events_export_form']); $this->cal->register_handler('plugin.events_export_form', [$this, 'events_export_form']);
$this->cal->register_handler('plugin.object_changelog_table', ['libkolab', 'object_changelog_table']); $this->cal->register_handler('plugin.object_changelog_table', ['libkolab', 'object_changelog_table']);
$this->cal->register_handler('plugin.searchform', [$this->rc->output, 'search_form']); $this->cal->register_handler('plugin.searchform', [$this->rc->output, 'search_form']);
$this->cal->register_handler('plugin.calendar_create_menu', array($this, 'calendar_create_menu'));
kolab_attachments_handler::ui(); kolab_attachments_handler::ui();
} }
/**
* Added for CalDav
* Handler for menu to choose the driver for calendar creation.
*/
function calendar_create_menu($attrib = array())
{
$content = "";
foreach($this->cal->get_drivers() as $name => $driver)
{
$content .= html::tag('li', null, $this->rc->output->button(
array('label' => 'calendar.calendar_'.$name,
'class' => 'active',
'prop' => json_encode(array('driver' => $name)),
'command' => 'calendar-create',
'title' => 'calendar.createcalendar')));
}
return $content;
}
/** /**
* Adds CSS stylesheets to the page header * Adds CSS stylesheets to the page header
*/ */
@ -153,28 +174,30 @@ class calendar_ui
*/ */
function calendar_css($attrib = []) function calendar_css($attrib = [])
{ {
$categories = $this->cal->driver->list_categories();
$calendars = $this->cal->driver->list_calendars();
$js_categories = [];
$mode = $this->rc->config->get('calendar_event_coloring', $this->cal->defaults['calendar_event_coloring']); $mode = $this->rc->config->get('calendar_event_coloring', $this->cal->defaults['calendar_event_coloring']);
$css = "\n"; $css = "\n";
foreach ((array) $categories as $class => $color) { foreach ($this->cal->get_drivers() as $driver) {
if (!empty($color)) { $categories = $driver->list_categories();
$js_categories[$class] = $color; $calendars = $driver->list_calendars();
$js_categories = [];
$color = ltrim($color, '#'); foreach((array)$categories as $class => $color) {
$class = 'cat-' . asciiwords(strtolower($class), true); if(!empty($color)) {
$css .= ".$class { color: #$color; }\n"; $js_categories[$class] = $color;
$color = ltrim($color, '#');
$class = 'cat-' . asciiwords(strtolower($class), true);
$css .= ".$class { color: #$color; }\n";
}
} }
}
$this->rc->output->set_env('calendar_categories', $js_categories); $this->rc->output->set_env('calendar_categories', $js_categories);
foreach ((array) $calendars as $id => $prop) { foreach((array)$calendars as $id => $prop) {
if (!empty($prop['color'])) { if(!empty($prop['color'])) {
$css .= $this->calendar_css_classes($id, $prop, $mode, $attrib); $css .= $this->calendar_css_classes($id, $prop, $mode, $attrib);
}
} }
} }
@ -211,7 +234,8 @@ class calendar_ui
$html = ''; $html = '';
$jsenv = []; $jsenv = [];
$tree = true; $tree = true;
$calendars = $this->cal->driver->list_calendars(0, $tree); // TODO: Check whether get_calendars() exists. Original $calendars = $this->cal->driver->list_calendars(0, $tree);
$calendars = $this->cal->get_calendars(false, false, $tree);
// walk folder tree // walk folder tree
if (is_object($tree)) { if (is_object($tree)) {
@ -296,12 +320,13 @@ class calendar_ui
// enrich calendar properties with settings from the driver // enrich calendar properties with settings from the driver
if (empty($prop['virtual'])) { if (empty($prop['virtual'])) {
unset($prop['user_id']); unset($prop['user_id']);
$driver = $this->cal->get_driver_by_cal($id);
$prop['alarms'] = $this->cal->driver->alarms; $prop['alarms'] = $driver->alarms;
$prop['attendees'] = $this->cal->driver->attendees; $prop['attendees'] = $driver->attendees;
$prop['freebusy'] = $this->cal->driver->freebusy; $prop['freebusy'] = $driver->freebusy;
$prop['attachments'] = $this->cal->driver->attachments; $prop['attachments'] = $driver->attachments;
$prop['undelete'] = $this->cal->driver->undelete; $prop['undelete'] = $driver->undelete;
$prop['feedurl'] = $this->cal->get_url([ $prop['feedurl'] = $this->cal->get_url([
'_cal' => $this->cal->ical_feed_hash($id) . '.ics', '_cal' => $this->cal->ical_feed_hash($id) . '.ics',
'action' => 'feed' 'action' => 'feed'
@ -434,7 +459,7 @@ class calendar_ui
$select = new html_select($attrib); $select = new html_select($attrib);
foreach ((array) $this->cal->driver->list_calendars() as $id => $prop) { foreach ((array) $this->cal->get_calendars() as $id => $prop) {
if ( if (
!empty($prop['editable']) !empty($prop['editable'])
|| (!empty($prop['rights']) && strpos($prop['rights'], 'i') !== false) || (!empty($prop['rights']) && strpos($prop['rights'], 'i') !== false)
@ -472,8 +497,10 @@ class calendar_ui
$select = new html_select($attrib); $select = new html_select($attrib);
$select->add('---', ''); $select->add('---', '');
foreach (array_keys((array) $this->cal->driver->list_categories()) as $cat) { foreach ($this->cal->get_drivers() as $driver) {
$select->add($cat, $cat); foreach(array_keys((array)$driver->list_categories()) as $cat) {
$select->add($cat, $cat);
}
} }
return $select->show(null); return $select->show(null);
@ -554,7 +581,14 @@ class calendar_ui
*/ */
function alarm_select($attrib = []) function alarm_select($attrib = [])
{ {
return $this->cal->lib->alarm_select($attrib, $this->cal->driver->alarm_types, $this->cal->driver->alarm_absolute); // Try GPC
$driver = $this->cal->get_driver_by_gpc(true /* quiet */);
// We assume that each calendar has equal alarm types, so fallback to default calendar is ok.
if(!$driver)
$driver = $this->cal->get_default_driver();
return $this->cal->lib->alarm_select($attrib, $driver->alarm_types, $driver->alarm_absolute);
} }
/** /**

166
lib/encryption.php Normal file
View file

@ -0,0 +1,166 @@
<?php
/**
* A class to handle secure encryption and decryption of arbitrary data
* Copyright http://stackoverflow.com/users/338665/ircmaxell
* http://stackoverflow.com/questions/5089841/php-2-way-encryption-i-need-to-store-passwords-that-can-be-retrieved
*
*
* Note that this is not just straight encryption. It also has a few other
* features in it to make the encrypted data far more secure. Note that any
* other implementations used to decrypt data will have to do the same exact
* operations.
*
* Security Benefits:
*
* - Uses Key stretching
* - Hides the Initialization Vector
* - Does HMAC verification of source data
*
*/
class Encryption {
/**
* @var string $cipher The mcrypt cipher to use for this instance
*/
protected $cipher = '';
/**
* @var int $mode The mcrypt cipher mode to use
*/
protected $mode = '';
/**
* @var int $rounds The number of rounds to feed into PBKDF2 for key generation
*/
protected $rounds = 100;
/**
* Constructor!
*
* @param string $cipher The MCRYPT_* cypher to use for this instance
* @param int $mode The MCRYPT_MODE_* mode to use for this instance
* @param int $rounds The number of PBKDF2 rounds to do on the key
*/
public function __construct($cipher, $mode, $rounds = 100) {
$this->cipher = $cipher;
$this->mode = $mode;
$this->rounds = (int) $rounds;
}
/**
* Decrypt the data with the provided key
*
* @param string $data The encrypted datat to decrypt
* @param string $key The key to use for decryption
*
* @returns string|false The returned string if decryption is successful
* false if it is not
*/
public function decrypt($data, $key) {
$salt = substr($data, 0, 128);
$enc = substr($data, 128, -64);
$mac = substr($data, -64);
list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);
if ($mac !== hash_hmac('sha512', $enc, $macKey, true)) {
return false;
}
$dec = mcrypt_decrypt($this->cipher, $cipherKey, $enc, $this->mode, $iv);
$data = $this->unpad($dec);
return $data;
}
/**
* Encrypt the supplied data using the supplied key
*
* @param string $data The data to encrypt
* @param string $key The key to encrypt with
*
* @returns string The encrypted data
*/
public function encrypt($data, $key) {
$salt = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);
$data = $this->pad($data);
$enc = mcrypt_encrypt($this->cipher, $cipherKey, $data, $this->mode, $iv);
$mac = hash_hmac('sha512', $enc, $macKey, true);
return $salt . $enc . $mac;
}
/**
* Generates a set of keys given a random salt and a master key
*
* @param string $salt A random string to change the keys each encryption
* @param string $key The supplied key to encrypt with
*
* @returns array An array of keys (a cipher key, a mac key, and a IV)
*/
protected function getKeys($salt, $key) {
$ivSize = mcrypt_get_iv_size($this->cipher, $this->mode);
$keySize = mcrypt_get_key_size($this->cipher, $this->mode);
$length = 2 * $keySize + $ivSize;
$key = $this->pbkdf2('sha512', $key, $salt, $this->rounds, $length);
$cipherKey = substr($key, 0, $keySize);
$macKey = substr($key, $keySize, $keySize);
$iv = substr($key, 2 * $keySize);
return array($cipherKey, $macKey, $iv);
}
/**
* Stretch the key using the PBKDF2 algorithm
*
* @see http://en.wikipedia.org/wiki/PBKDF2
*
* @param string $algo The algorithm to use
* @param string $key The key to stretch
* @param string $salt A random salt
* @param int $rounds The number of rounds to derive
* @param int $length The length of the output key
*
* @returns string The derived key.
*/
protected function pbkdf2($algo, $key, $salt, $rounds, $length) {
$size = strlen(hash($algo, '', true));
$len = ceil($length / $size);
$result = '';
for ($i = 1; $i <= $len; $i++) {
$tmp = hash_hmac($algo, $salt . pack('N', $i), $key, true);
$res = $tmp;
for ($j = 1; $j < $rounds; $j++) {
$tmp = hash_hmac($algo, $tmp, $key, true);
$res ^= $tmp;
}
$result .= $res;
}
return substr($result, 0, $length);
}
protected function pad($data) {
$length = mcrypt_get_block_size($this->cipher, $this->mode);
$padAmount = $length - strlen($data) % $length;
if ($padAmount == 0) {
$padAmount = $length;
}
return $data . str_repeat(chr($padAmount), $padAmount);
}
protected function unpad($data) {
$length = mcrypt_get_block_size($this->cipher, $this->mode);
$last = ord($data[strlen($data) - 1]);
if ($last > $length) return false;
if (substr($data, -1 * $last) !== str_repeat(chr($last), $last)) {
return false;
}
return substr($data, 0, -1 * $last);
}
}
?>

View file

@ -32,6 +32,7 @@ $labels['calendars'] = 'Календари';
$labels['category'] = 'Категория'; $labels['category'] = 'Категория';
$labels['categories'] = 'Категории'; $labels['categories'] = 'Категории';
$labels['createcalendar'] = 'Създаване на нов календар'; $labels['createcalendar'] = 'Създаване на нов календар';
$labels['editcalendar'] = 'Промяна на свойствата на календара';
$labels['name'] = 'Име'; $labels['name'] = 'Име';
$labels['color'] = 'Цвят'; $labels['color'] = 'Цвят';
$labels['day'] = 'Ден'; $labels['day'] = 'Ден';

View file

@ -6,11 +6,6 @@
* *
* For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
*/ */
$labels['addsources'] = 'CalDAV Quellen hinzufügen';
$labels['deletesources'] = 'CalDAV Quellen löschen';
$labels['source_notadded_error'] = 'CalDAV Quelle konnte nicht hinzugefügt werden.';
$labels['calendar_ical_file'] = 'ics-Datei';
$labels['default_view'] = 'Standardansicht'; $labels['default_view'] = 'Standardansicht';
$labels['time_format'] = 'Zeitformatierung'; $labels['time_format'] = 'Zeitformatierung';
$labels['timeslots'] = 'Zeitfenster pro Stunde'; $labels['timeslots'] = 'Zeitfenster pro Stunde';

View file

@ -6,11 +6,6 @@
* *
* For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
*/ */
$labels['addsources'] = 'CalDAV Quellen hinzufügen';
$labels['deletesources'] = 'CalDAV Quellen löschen';
$labels['source_notadded_error'] = 'CalDAV Quelle konnte nicht hinzugefügt werden.';
$labels['calendar_ical_file'] = 'ics-Datei';
$labels['default_view'] = 'Standardansicht'; $labels['default_view'] = 'Standardansicht';
$labels['time_format'] = 'Zeitformatierung'; $labels['time_format'] = 'Zeitformatierung';
$labels['timeslots'] = 'Abschnitte pro Stunde'; $labels['timeslots'] = 'Abschnitte pro Stunde';

View file

@ -6,11 +6,6 @@
* *
* For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
*/ */
$labels['addsources'] = 'CalDAV Quellen hinzufügen';
$labels['deletesources'] = 'CalDAV Quellen löschen';
$labels['source_notadded_error'] = 'CalDAV Quelle konnte nicht hinzugefügt werden.';
$labels['calendar_ical_file'] = 'ics-Datei';
$labels['default_view'] = 'Standardansicht'; $labels['default_view'] = 'Standardansicht';
$labels['time_format'] = 'Zeitformatierung'; $labels['time_format'] = 'Zeitformatierung';
$labels['timeslots'] = 'Zeitfenster pro Stunde'; $labels['timeslots'] = 'Zeitfenster pro Stunde';
@ -38,6 +33,7 @@ $labels['calendars'] = 'Kalender';
$labels['category'] = 'Kategorie'; $labels['category'] = 'Kategorie';
$labels['categories'] = 'Kategorien'; $labels['categories'] = 'Kategorien';
$labels['createcalendar'] = 'Neuen Kalender erstellen'; $labels['createcalendar'] = 'Neuen Kalender erstellen';
$labels['editcalendar'] = 'Kalendereigenschaften bearbeiten';
$labels['name'] = 'Name'; $labels['name'] = 'Name';
$labels['color'] = 'Farbe'; $labels['color'] = 'Farbe';
$labels['day'] = 'Tag'; $labels['day'] = 'Tag';
@ -158,6 +154,8 @@ $labels['availbusy'] = 'Gebucht';
$labels['availunknown'] = 'Unbekannt'; $labels['availunknown'] = 'Unbekannt';
$labels['availtentative'] = 'Mit Vorbehalt'; $labels['availtentative'] = 'Mit Vorbehalt';
$labels['availoutofoffice'] = 'Abwesend'; $labels['availoutofoffice'] = 'Abwesend';
$labels['delegatedto'] = 'Delegiert an:';
$labels['delegatedfrom'] = 'Delegiert von:';
$labels['scheduletime'] = 'Verfügbarkeit anzeigen'; $labels['scheduletime'] = 'Verfügbarkeit anzeigen';
$labels['sendinvitations'] = 'Einladungen versenden'; $labels['sendinvitations'] = 'Einladungen versenden';
$labels['sendnotifications'] = 'Teilnehmer über die Änderungen informieren'; $labels['sendnotifications'] = 'Teilnehmer über die Änderungen informieren';
@ -281,6 +279,15 @@ $labels['birthdayeventtitle'] = '$names Geburtstag';
$labels['birthdayage'] = 'Alter $age'; $labels['birthdayage'] = 'Alter $age';
$labels['objectchangelog'] = 'Änderungsverlauf'; $labels['objectchangelog'] = 'Änderungsverlauf';
$labels['objectdiff'] = 'Änderungen aus $rev1 nach $rev2'; $labels['objectdiff'] = 'Änderungen aus $rev1 nach $rev2';
$labels['revision'] = 'Version';
$labels['user'] = 'Benutzer';
$labels['operation'] = 'Aktion';
$labels['actionappend'] = 'Gespeichert';
$labels['actionmove'] = 'Verschoben';
$labels['actiondelete'] = 'Gelöscht';
$labels['compare'] = 'Vergleichen';
$labels['showrevision'] = 'Diese Version anzeigen';
$labels['restore'] = 'Diese Version wiederherstellen';
$labels['objectnotfound'] = 'Termindaten sind leider nicht vergübar'; $labels['objectnotfound'] = 'Termindaten sind leider nicht vergübar';
$labels['objectchangelognotavailable'] = 'Änderungshistorie ist nicht verfügbar für diesen Termin'; $labels['objectchangelognotavailable'] = 'Änderungshistorie ist nicht verfügbar für diesen Termin';
$labels['objectdiffnotavailable'] = 'Vergleich für die gewählten Versionen nicht möglich'; $labels['objectdiffnotavailable'] = 'Vergleich für die gewählten Versionen nicht möglich';
@ -297,4 +304,11 @@ $labels['arialabeleventattendees'] = 'Teilehmerliste';
$labels['arialabeleventresources'] = 'Liste der Terminressourcen'; $labels['arialabeleventresources'] = 'Liste der Terminressourcen';
$labels['arialabelresourcesearchform'] = 'Suchformular für Ressourcen'; $labels['arialabelresourcesearchform'] = 'Suchformular für Ressourcen';
$labels['arialabelresourceselection'] = 'Verfügbare Ressourcen'; $labels['arialabelresourceselection'] = 'Verfügbare Ressourcen';
$labels['calendar_database'] = 'Datenbank Kalender';
$labels['calendar_kolab'] = 'Kolab Kalender';
$labels['calendar_caldav'] = 'CalDAV Kalender';
$labels['calendar_ical'] = 'iCAL Kalender';
$labels['caldavurl'] = "CalDAV URL";
$labels['icalurl'] = "iCal URL";
?> ?>

View file

@ -10,12 +10,6 @@
$labels = array(); $labels = array();
//caldav driver
$labels['addsources'] = 'Add CalDAV sources';
$labels['deletesources'] = 'Delete CalDAV sources';
$labels['source_notadded_error'] = 'CalDAV source could not be added.';
$labels['calendar_ical_file'] = 'ics file';
// preferences // preferences
$labels['default_view'] = 'Default view'; $labels['default_view'] = 'Default view';
$labels['time_format'] = 'Time format'; $labels['time_format'] = 'Time format';
@ -184,6 +178,8 @@ $labels['availbusy'] = 'Busy';
$labels['availunknown'] = 'Unknown'; $labels['availunknown'] = 'Unknown';
$labels['availtentative'] = 'Tentative'; $labels['availtentative'] = 'Tentative';
$labels['availoutofoffice'] = 'Out of Office'; $labels['availoutofoffice'] = 'Out of Office';
$labels['delegatedto'] = 'Delegated to: ';
$labels['delegatedfrom'] = 'Delegated from: ';
$labels['scheduletime'] = 'Find availability'; $labels['scheduletime'] = 'Find availability';
$labels['sendinvitations'] = 'Send invitations'; $labels['sendinvitations'] = 'Send invitations';
$labels['sendnotifications'] = 'Notify participants about modifications'; $labels['sendnotifications'] = 'Notify participants about modifications';
@ -213,10 +209,6 @@ $labels['itipmailbodycancel'] = "\$sender has rejected your participation in the
$labels['itipmailbodydelegated'] = "\$sender has delegated the participation in the following event:\n\n*\$title*\n\nWhen: \$date"; $labels['itipmailbodydelegated'] = "\$sender has delegated the participation in the following event:\n\n*\$title*\n\nWhen: \$date";
$labels['itipmailbodydelegatedto'] = "\$sender has delegated the participation in the following event to you:\n\n*\$title*\n\nWhen: \$date"; $labels['itipmailbodydelegatedto'] = "\$sender has delegated the participation in the following event to you:\n\n*\$title*\n\nWhen: \$date";
$labels['itipmailbodyresourceaccepted'] = "\$sender has accepted the following resource booking:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
$labels['itipmailbodyresourcetentative'] = "\$sender has tentatively accepted the following resource booking:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
$labels['itipmailbodyresourcedeclined'] = "\$sender has declined the the following resource booking:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
$labels['itipdeclineevent'] = 'Do you want to decline your invitation to this event?'; $labels['itipdeclineevent'] = 'Do you want to decline your invitation to this event?';
$labels['declinedeleteconfirm'] = 'Do you also want to delete this declined event from your calendar?'; $labels['declinedeleteconfirm'] = 'Do you also want to delete this declined event from your calendar?';
$labels['itipcomment'] = 'Invitation/notification comment'; $labels['itipcomment'] = 'Invitation/notification comment';
@ -306,6 +298,15 @@ $labels['birthdayage'] = 'Age $age';
// history dialog // history dialog
$labels['objectchangelog'] = 'Change History'; $labels['objectchangelog'] = 'Change History';
$labels['objectdiff'] = 'Changes from $rev1 to $rev2'; $labels['objectdiff'] = 'Changes from $rev1 to $rev2';
$labels['revision'] = 'Revision';
$labels['user'] = 'User';
$labels['operation'] = 'Action';
$labels['actionappend'] = 'Saved';
$labels['actionmove'] = 'Moved';
$labels['actiondelete'] = 'Deleted';
$labels['compare'] = 'Compare';
$labels['showrevision'] = 'Show this version';
$labels['restore'] = 'Restore this version';
$labels['objectnotfound'] = 'Failed to load event data'; $labels['objectnotfound'] = 'Failed to load event data';
$labels['objectchangelognotavailable'] = 'Change history is not available for this event'; $labels['objectchangelognotavailable'] = 'Change history is not available for this event';
$labels['objectdiffnotavailable'] = 'No comparison possible for the selected revisions'; $labels['objectdiffnotavailable'] = 'No comparison possible for the selected revisions';
@ -327,4 +328,9 @@ $labels['arialabelresourcesearchform'] = 'Resources search form';
$labels['arialabelresourceselection'] = 'Available resources'; $labels['arialabelresourceselection'] = 'Available resources';
$labels['arialabeleventform'] = 'Event editing form'; $labels['arialabeleventform'] = 'Event editing form';
$labels['calendar_kolab'] = 'Kolab Calender';
$labels['calendar_caldav'] = 'CalDAV Calender';
$labels['calendar_ical'] = 'iCAL Calender';
$labels['caldavurl'] = "CalDAV URL";
$labels['icalurl'] = "iCal URL";
?> ?>

View file

@ -141,10 +141,6 @@
<div id="calendaractions-menu" class="popupmenu"> <div id="calendaractions-menu" class="popupmenu">
<h3 id="aria-label-calendaroptions" class="voice"><roundcube:label name="calendar.calendaractions" /></h3> <h3 id="aria-label-calendaroptions" class="voice"><roundcube:label name="calendar.calendaractions" /></h3>
<ul class="menu listing" role="menu" aria-labelledby="aria-label-calendaroptions"> <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-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-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" /> <roundcube:button type="link-menuitem" command="calendar-delete" label="calendar.deletecalendar" class="delete disabled" classAct="delete active" />

View file

@ -68,10 +68,6 @@
<div id="calendaroptionsmenu" class="popupmenu" aria-hidden="true"> <div id="calendaroptionsmenu" class="popupmenu" aria-hidden="true">
<h3 id="aria-label-calendaroptions" class="voice"><roundcube:label name="calendar.calendaractions" /></h3> <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"> <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-edit" label="calendar.edit" classAct="active" /></li>
<li role="menuitem"><roundcube:button type="link" command="calendar-delete" label="delete" 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'" /> <roundcube:if condition="env:calendar_driver == 'kolab'" />