Compare commits
43 commits
mail_impor
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
f154953979 | ||
|
43c30995b0 | ||
|
61eb3a4a16 | ||
|
f0a90df1d3 | ||
|
3fa3b47e06 | ||
|
ad31194e03 | ||
|
9c6b8b4171 | ||
|
f679c616e8 | ||
|
847aae1321 | ||
|
89cdebc5ca | ||
|
b9ff57cf21 | ||
|
a6ec0c3b68 | ||
|
0e84caaa4e | ||
|
4e72a2b60f | ||
|
8347d65795 | ||
|
ee19875c97 | ||
|
2d72a2edcb | ||
|
64599f4cfe | ||
|
927bf88862 | ||
|
2a0c9f8c07 | ||
|
91a976b444 | ||
|
9caeea244a | ||
|
ca2409e081 | ||
|
c1f5524cec | ||
|
94a287afc6 | ||
|
72e7983d75 | ||
|
72dd4dffcd | ||
|
0fe9be1221 | ||
|
f9e2c2667f | ||
|
a782469f11 | ||
|
b68b5662a1 | ||
|
4100dadd69 | ||
|
71c5fc85a0 | ||
|
71c4d7aba9 | ||
|
3df123b0b8 | ||
|
766088b19a | ||
|
263e1cc1f3 | ||
|
cf57748f4c | ||
|
0cc7f1ebdd | ||
|
277e3094f7 | ||
|
5ee16ce9ef | ||
|
822b359185 | ||
|
21e3fff576 |
26 changed files with 3971 additions and 170 deletions
81
README
81
README
|
@ -1,81 +0,0 @@
|
|||
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/
|
68
README.md
Normal file
68
README.md
Normal file
|
@ -0,0 +1,68 @@
|
|||
## 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
|
||||
```
|
168
calendar.php
168
calendar.php
|
@ -928,6 +928,18 @@ $("#rcmfd_new_category").keypress(function(event) {
|
|||
}
|
||||
|
||||
switch ($action) {
|
||||
case "new-source": //we "misuse" create_calendar() to stay compatible with other drivers
|
||||
$cal['new-source'] = true;
|
||||
$success = $this->driver->create_calendar($cal);
|
||||
$reload = true;
|
||||
break;
|
||||
case "delete-source": //we "misuse" delete_calendar() to stay compatible with other drivers
|
||||
$cal['delete-source'] = true;
|
||||
$success = $this->driver->delete_calendar($cal);
|
||||
$reload = true;
|
||||
break;
|
||||
case "form-source-new": //we "misuse" calendar_editform() to stay compatible with other drivers
|
||||
case "form-source-delete": //we "misuse" calendar_editform() to stay compatible with other drivers
|
||||
case "form-new":
|
||||
case "form-edit":
|
||||
echo $this->ui->calendar_editform($action, $cal);
|
||||
|
@ -1239,23 +1251,31 @@ $("#rcmfd_new_category").keypress(function(event) {
|
|||
$noreply = intval($noreply) || $status == 'needs-action' || $itip_sending === 0;
|
||||
$reload = $event['calendar'] != $ev['calendar'] || !empty($event['recurrence']) ? 2 : 1;
|
||||
$emails = $this->get_user_emails();
|
||||
$ownedResourceEmails = $this->owned_resources_emails();
|
||||
$organizer = null;
|
||||
$resourceConfirmation = false;
|
||||
|
||||
foreach ($event['attendees'] as $i => $attendee) {
|
||||
if ($attendee['role'] == 'ORGANIZER') {
|
||||
$organizer = $attendee;
|
||||
}
|
||||
else if (!empty($attendee['email']) && in_array(strtolower($attendee['email']), $emails)) {
|
||||
else if (!empty($attendee['email']) && in_array_nocase($attendee['email'], $emails)) {
|
||||
$reply_sender = $attendee['email'];
|
||||
}
|
||||
else if (!empty($attendee['cutype']) && $attendee['cutype'] == 'RESOURCE' && !empty($attendee['email']) && in_array_nocase($attendee['email'], $ownedResourceEmails)) {
|
||||
$resourceConfirmation = true;
|
||||
// Note on behalf of which resource this update is going to be sent out
|
||||
$event['_resource'] = $attendee['email'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$noreply) {
|
||||
$itip = $this->load_itip();
|
||||
$itip->set_sender_email($reply_sender);
|
||||
$event['thisandfuture'] = $event['_savemode'] == 'future';
|
||||
$bodytextprefix = $resourceConfirmation ? 'itipmailbodyresource' : 'itipmailbody';
|
||||
|
||||
if ($organizer && $itip->send_itip_message($event, 'REPLY', $organizer, 'itipsubject' . $status, 'itipmailbody' . $status)) {
|
||||
if ($organizer && $itip->send_itip_message($event, 'REPLY', $organizer, 'itipsubject' . $status, $bodytextprefix . $status)) {
|
||||
$mailto = !empty($organizer['name']) ? $organizer['name'] : $organizer['email'];
|
||||
$msg = $this->gettext(['name' => 'sentresponseto', 'vars' => ['mailto' => $mailto]]);
|
||||
|
||||
|
@ -2006,10 +2026,12 @@ $("#rcmfd_new_category").keypress(function(event) {
|
|||
}
|
||||
|
||||
$identity['emails'][] = $this->rc->user->get_username();
|
||||
$identity['ownedResources'] = $this->owned_resources_emails();
|
||||
$settings['identity'] = [
|
||||
'name' => $identity['name'],
|
||||
'email' => strtolower($identity['email']),
|
||||
'emails' => ';' . strtolower(join(';', $identity['emails']))
|
||||
'emails' => ';' . strtolower(join(';', $identity['emails'])),
|
||||
'ownedResources' => ';' . strtolower(join(';', $identity['ownedResources']))
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -2390,7 +2412,7 @@ $("#rcmfd_new_category").keypress(function(event) {
|
|||
}
|
||||
|
||||
// set new organizer identity
|
||||
if ($organizer !== false && $identity) {
|
||||
if ($organizer !== false && !empty($identity)) {
|
||||
$event['attendees'][$organizer]['name'] = $identity['name'];
|
||||
$event['attendees'][$organizer]['email'] = $identity['email'];
|
||||
}
|
||||
|
@ -2400,7 +2422,7 @@ $("#rcmfd_new_category").keypress(function(event) {
|
|||
unset($event['attendees'][$owner]['rsvp']);
|
||||
}
|
||||
// fallback to the selected identity
|
||||
else if ($organizer === false && $identity) {
|
||||
else if ($organizer === false && !empty($identity)) {
|
||||
$event['attendees'][] = [
|
||||
'role' => 'ORGANIZER',
|
||||
'name' => $identity['name'],
|
||||
|
@ -2867,6 +2889,21 @@ $("#rcmfd_new_category").keypress(function(event) {
|
|||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* List email addressed of owned resources
|
||||
*/
|
||||
private function owned_resources_emails()
|
||||
{
|
||||
$results = [];
|
||||
if ($directory = $this->resources_directory()) {
|
||||
foreach ($directory->load_resources($_SESSION['kolab_dn'], 5000, 'owner') as $rec) {
|
||||
$results[] = $rec['email'];
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
|
||||
/**** Event invitation plugin hooks ****/
|
||||
|
||||
/**
|
||||
|
@ -3148,10 +3185,16 @@ $("#rcmfd_new_category").keypress(function(event) {
|
|||
*/
|
||||
private function mail_agenda_event_row($event, $class = '')
|
||||
{
|
||||
$time = !empty($event['allday']) ? $this->gettext('all-day') :
|
||||
$this->rc->format_date($event['start'], $this->rc->config->get('time_format'))
|
||||
. ' - ' .
|
||||
$this->rc->format_date($event['end'], $this->rc->config->get('time_format'));
|
||||
if (!empty($event['allday'])) {
|
||||
$time = $this->gettext('all-day');
|
||||
}
|
||||
else {
|
||||
$start = is_object($event['start']) ? clone $event['start'] : $event['start'];
|
||||
$end = is_object($event['end']) ? clone $event['end'] : $event['end'];
|
||||
|
||||
$time = $this->rc->format_date($start, $this->rc->config->get('time_format'))
|
||||
. ' - ' . $this->rc->format_date($end, $this->rc->config->get('time_format'));
|
||||
}
|
||||
|
||||
return html::div(rtrim('event-row ' . ($class ?: $event['className'])),
|
||||
html::span('event-date', $time)
|
||||
|
@ -3216,7 +3259,7 @@ $("#rcmfd_new_category").keypress(function(event) {
|
|||
// get prepared inline UI for this event object
|
||||
if ($ical_objects->method) {
|
||||
$append = '';
|
||||
$date_str = $this->rc->format_date($event['start'], $this->rc->config->get('date_format'), empty($event['start']->_dateonly));
|
||||
$date_str = $this->rc->format_date(clone $event['start'], $this->rc->config->get('date_format'), empty($event['start']->_dateonly));
|
||||
$date = new DateTime($event['start']->format('Y-m-d') . ' 12:00:00', new DateTimeZone('UTC'));
|
||||
|
||||
// prepare a small agenda preview to be filled with actual event data on async request
|
||||
|
@ -3254,19 +3297,27 @@ $("#rcmfd_new_category").keypress(function(event) {
|
|||
|
||||
// add "Save to calendar" button into attachment menu
|
||||
if ($has_events) {
|
||||
$this->add_button([
|
||||
'id' => 'attachmentsavecal',
|
||||
'name' => 'attachmentsavecal',
|
||||
'type' => 'link',
|
||||
'wrapper' => 'li',
|
||||
'command' => 'attachment-save-calendar',
|
||||
'class' => 'icon calendarlink disabled',
|
||||
'classact' => 'icon calendarlink active',
|
||||
$calendars = $this->driver->list_calendars(calendar_driver::FILTER_PERSONAL | calendar_driver::FILTER_WRITEABLE);
|
||||
|
||||
foreach($calendars as $calendar) {
|
||||
//TODO inline style tags are a bit ugly, but I cant think of a better way of coloring the icon
|
||||
$style = html::tag('style', ['type' => 'text/css'], ".icon.cal-$calendar[id]::before {color: #$calendar[color]}");
|
||||
$this->add_button([
|
||||
'id' => 'attachmentsavecal-' .$calendar['id'],
|
||||
'name' => 'attachmentsavecal',
|
||||
'type' => 'link',
|
||||
'wrapper' => 'li',
|
||||
'command' => 'attachment-save-calendar',
|
||||
'prop' => $calendar['id'],
|
||||
'class' => 'icon calendarlink disabled',
|
||||
'classact' => "icon calendarlink active cal-$calendar[id]",
|
||||
'innerclass' => 'icon calendar',
|
||||
'label' => 'calendar.savetocalendar',
|
||||
//maybe change the calendar.savetocalendar translation so a calendar can be added and use a label instead?
|
||||
'content' => $this->gettext('calendar.savetocalendar') ." ($calendar[name]) $style"
|
||||
],
|
||||
'attachmentmenu'
|
||||
);
|
||||
'attachmentmenu'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $p;
|
||||
|
@ -3405,39 +3456,38 @@ $("#rcmfd_new_category").keypress(function(event) {
|
|||
|
||||
// only update attendee status
|
||||
if ($event['_method'] == 'REPLY') {
|
||||
// try to identify the attendee using the email sender address
|
||||
$existing_attendee = -1;
|
||||
$existing_attendee_emails = [];
|
||||
|
||||
foreach ($existing['attendees'] as $i => $attendee) {
|
||||
$existing_attendee_emails[] = $attendee['email'];
|
||||
if ($this->itip->compare_email($attendee['email'], $event['_sender'], $event['_sender_utf'])) {
|
||||
$existing_attendee = $i;
|
||||
}
|
||||
}
|
||||
$existing_attendee_index = -1;
|
||||
|
||||
$event_attendee = null;
|
||||
$update_attendees = [];
|
||||
|
||||
foreach ($event['attendees'] as $attendee) {
|
||||
if ($this->itip->compare_email($attendee['email'], $event['_sender'], $event['_sender_utf'])) {
|
||||
$event_attendee = $attendee;
|
||||
$update_attendees[] = $attendee;
|
||||
$metadata['fallback'] = $attendee['status'];
|
||||
$metadata['attendee'] = $attendee['email'];
|
||||
$metadata['rsvp'] = !empty($attendee['rsvp']) || $attendee['role'] != 'NON-PARTICIPANT';
|
||||
if ($attendee = $this->itip->find_reply_attendee($event)) {
|
||||
$event_attendee = $attendee;
|
||||
$update_attendees[] = $attendee;
|
||||
$metadata['fallback'] = $attendee['status'];
|
||||
$metadata['attendee'] = $attendee['email'];
|
||||
$metadata['rsvp'] = !empty($attendee['rsvp']) || $attendee['role'] != 'NON-PARTICIPANT';
|
||||
|
||||
if ($attendee['status'] != 'DELEGATED') {
|
||||
break;
|
||||
$existing_attendee_emails = [];
|
||||
|
||||
// Find the attendee to update
|
||||
foreach ($existing['attendees'] as $i => $existing_attendee) {
|
||||
$existing_attendee_emails[] = $existing_attendee['email'];
|
||||
if ($this->itip->compare_email($existing_attendee['email'], $attendee['email'])) {
|
||||
$existing_attendee_index = $i;
|
||||
}
|
||||
}
|
||||
// also copy delegate attendee
|
||||
else if (!empty($attendee['delegated-from'])
|
||||
&& $this->itip->compare_email($attendee['delegated-from'], $event['_sender'], $event['_sender_utf'])
|
||||
) {
|
||||
$update_attendees[] = $attendee;
|
||||
if (!in_array_nocase($attendee['email'], $existing_attendee_emails)) {
|
||||
$existing['attendees'][] = $attendee;
|
||||
|
||||
if ($attendee['status'] == 'DELEGATED') {
|
||||
//Also find and copy the delegatee
|
||||
$delegatee_email = $attendee['email'];
|
||||
$delegatees = array_filter($event['attendees'], function($attendee) use ($delegatee_email){ return $attendee['role'] != 'ORGANIZER' && $this->itip->compare_email($attendee['delegated-from'], $delegatee_email); });
|
||||
|
||||
if ($delegatee = $this->itip->find_attendee_by_email($event['attendees'], 'delegated-from', $attendee['email'])) {
|
||||
$update_attendees[] = $delegatee;
|
||||
if (!in_array_nocase($delegatee['email'], $existing_attendee_emails)) {
|
||||
$existing['attendees'][] = $delegated_attendee;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3455,29 +3505,9 @@ $("#rcmfd_new_category").keypress(function(event) {
|
|||
}
|
||||
}
|
||||
|
||||
// Accept sender as a new participant (different email in From: and the iTip)
|
||||
// Use ATTENDEE entry from the iTip with replaced email address
|
||||
if (!$event_attendee) {
|
||||
// remove the organizer
|
||||
$itip_attendees = array_filter(
|
||||
$event['attendees'],
|
||||
function($item) { return $item['role'] != 'ORGANIZER'; }
|
||||
);
|
||||
|
||||
// there must be only one attendee
|
||||
if (is_array($itip_attendees) && count($itip_attendees) == 1) {
|
||||
$event_attendee = $itip_attendees[key($itip_attendees)];
|
||||
$event_attendee['email'] = $event['_sender'];
|
||||
$update_attendees[] = $event_attendee;
|
||||
$metadata['fallback'] = $event_attendee['status'];
|
||||
$metadata['attendee'] = $event_attendee['email'];
|
||||
$metadata['rsvp'] = !empty($event_attendee['rsvp']) || $event_attendee['role'] != 'NON-PARTICIPANT';
|
||||
}
|
||||
}
|
||||
|
||||
// found matching attendee entry in both existing and new events
|
||||
if ($existing_attendee >= 0 && $event_attendee) {
|
||||
$existing['attendees'][$existing_attendee] = $event_attendee;
|
||||
if ($existing_attendee_index >= 0 && $event_attendee) {
|
||||
$existing['attendees'][$existing_attendee_index] = $event_attendee;
|
||||
$success = $this->driver->update_attendees($existing, $update_attendees);
|
||||
}
|
||||
// update the entire attendees block
|
||||
|
|
|
@ -72,13 +72,12 @@ function rcube_calendar(settings)
|
|||
// handler for attachment-save-calendar commands
|
||||
this.save_to_calendar = function(p)
|
||||
{
|
||||
// TODO: show dialog to select the calendar for importing
|
||||
if (this.selected_attachment && window.rcube_libcalendaring) {
|
||||
rcmail.http_post('calendar/mailimportattach', {
|
||||
_uid: rcmail.env.uid,
|
||||
_mbox: rcmail.env.mailbox,
|
||||
_part: this.selected_attachment
|
||||
// _calendar: $('#calendar-attachment-saveto').val(),
|
||||
_part: this.selected_attachment,
|
||||
_calendar: p
|
||||
}, rcmail.set_busy(true, 'itip.savingdata'));
|
||||
}
|
||||
};
|
||||
|
@ -93,7 +92,7 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
|
|||
// register create-from-mail command to message_commands array
|
||||
if (rcmail.env.task == 'mail') {
|
||||
rcmail.register_command('calendar-create-from-mail', function() { cal.create_from_mail(); });
|
||||
rcmail.register_command('attachment-save-calendar', function() { cal.save_to_calendar(); });
|
||||
rcmail.register_command('attachment-save-calendar', function(p) { cal.save_to_calendar(p); });
|
||||
|
||||
if (rcmail.env.action != 'show') {
|
||||
rcmail.env.message_commands.push('calendar-create-from-mail');
|
||||
|
|
|
@ -459,7 +459,7 @@ function rcube_calendar_ui(settings)
|
|||
for (var j=0; j < num_attendees; j++) {
|
||||
data = event.attendees[j];
|
||||
if (data.email) {
|
||||
if (data.role != 'ORGANIZER' && settings.identity.emails.indexOf(';'+data.email) >= 0) {
|
||||
if (data.role != 'ORGANIZER' && is_this_me(data.email)) {
|
||||
mystatus = (data.status || 'UNKNOWN').toLowerCase();
|
||||
if (data.status == 'NEEDS-ACTION' || data.status == 'TENTATIVE' || data.rsvp)
|
||||
rsvp = mystatus;
|
||||
|
@ -2379,6 +2379,14 @@ function rcube_calendar_ui(settings)
|
|||
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
|
||||
var event_rsvp = function(response, delegate, replymode, event)
|
||||
{
|
||||
|
@ -2413,7 +2421,8 @@ function rcube_calendar_ui(settings)
|
|||
attendees = [];
|
||||
for (var data, i=0; i < me.selected_event.attendees.length; i++) {
|
||||
data = me.selected_event.attendees[i];
|
||||
if (settings.identity.emails.indexOf(';'+String(data.email).toLowerCase()) >= 0) {
|
||||
//FIXME this can only work if there is a single resource per invitation
|
||||
if (is_this_me(String(data.email).toLowerCase())) {
|
||||
data.status = response.toUpperCase();
|
||||
data.rsvp = 0; // unset RSVP flag
|
||||
|
||||
|
@ -2884,6 +2893,64 @@ function rcube_calendar_ui(settings)
|
|||
return update_event_confirm('remove', event, { id:event.id, calendar:event.calendar, attendees:event.attendees });
|
||||
};
|
||||
|
||||
//opens a dialog to add caldav sources
|
||||
this.calendar_new_source = function() {
|
||||
var title = rcmail.gettext('addsources', 'calendar'),
|
||||
params = {action: 'form-source-new', _framed: 1},
|
||||
$dialog = $('<iframe>').attr('src', rcmail.url('calendar', params)),
|
||||
save_func = function() {
|
||||
var data,
|
||||
form = $dialog.contents().find('#calendarpropform');
|
||||
|
||||
// form is not loaded
|
||||
if (!form || !form.length)
|
||||
return false;
|
||||
|
||||
// post data to server
|
||||
data = form.serializeJSON();
|
||||
if (data.color)
|
||||
data.color = data.color.replace(/^#/, '');
|
||||
if (calendar.id)
|
||||
data.id = calendar.id;
|
||||
|
||||
me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
|
||||
rcmail.http_post('calendar', { action:'new-source', c:data });
|
||||
$dialog.dialog("close");
|
||||
};
|
||||
|
||||
rcmail.simple_dialog($dialog, title, save_func, {
|
||||
width: 600,
|
||||
height: 400
|
||||
});
|
||||
};
|
||||
|
||||
//opens a dialog to delete caldav sources
|
||||
this.calendar_delete_sources = function() {
|
||||
var title = rcmail.gettext('deletesources', 'calendar'),
|
||||
params = {action: 'form-source-delete', _framed: 1},
|
||||
$dialog = $('<iframe>').attr('src', rcmail.url('calendar', params)),
|
||||
save_func = function() {
|
||||
var data,
|
||||
form = $dialog.contents().find('#calendarpropform');
|
||||
|
||||
// form is not loaded
|
||||
if (!form || !form.length)
|
||||
return false;
|
||||
|
||||
// post data to server
|
||||
data = form.serializeJSON();
|
||||
|
||||
me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
|
||||
rcmail.http_post('calendar', { action:'delete-source', c:data });
|
||||
$dialog.dialog("close");
|
||||
};
|
||||
|
||||
rcmail.simple_dialog($dialog, title, save_func, {
|
||||
width: 600,
|
||||
height: 400
|
||||
});
|
||||
};
|
||||
|
||||
// opens a jquery UI dialog with event properties (or empty for creating a new calendar)
|
||||
this.calendar_edit_dialog = function(calendar)
|
||||
{
|
||||
|
@ -2895,7 +2962,7 @@ function rcube_calendar_ui(settings)
|
|||
$dialog = $('<iframe>').attr('src', rcmail.url('calendar', params)).on('load', function() {
|
||||
var contents = $(this).contents();
|
||||
contents.find('#calendar-name')
|
||||
.prop('disabled', !calendar.editable)
|
||||
.prop('disabled', !calendar.editable && !calendar.editable_name)
|
||||
.val(calendar.editname || calendar.name)
|
||||
.select();
|
||||
contents.find('#calendar-color')
|
||||
|
@ -3549,7 +3616,7 @@ function rcube_calendar_ui(settings)
|
|||
if (node && node.id && me.calendars[node.id]) {
|
||||
me.select_calendar(node.id, true);
|
||||
rcmail.enable_command('calendar-edit', 'calendar-showurl', 'calendar-showfburl', true);
|
||||
rcmail.enable_command('calendar-delete', me.calendars[node.id].editable);
|
||||
rcmail.enable_command('calendar-delete', me.calendars[node.id].editable || me.calendars[node.id].deletable);
|
||||
rcmail.enable_command('calendar-remove', me.calendars[node.id] && me.calendars[node.id].removable);
|
||||
}
|
||||
});
|
||||
|
@ -4168,6 +4235,8 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
|
|||
rcmail.register_command('print', function(){ cal.print_calendars(); }, true);
|
||||
|
||||
// configure list operations
|
||||
rcmail.register_command('calendar-sources-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-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);
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"description": "Calendar plugin",
|
||||
"homepage": "https://git.kolab.org/diffusion/RPK/",
|
||||
"license": "AGPLv3",
|
||||
"version": "3.5.7",
|
||||
"version": "3.5.11",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Thomas Bruederli",
|
||||
|
@ -21,13 +21,18 @@
|
|||
{
|
||||
"type": "composer",
|
||||
"url": "https://plugins.roundcube.net"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/JodliDev/libcalendaring"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.4.0",
|
||||
"php": ">=5.5",
|
||||
"roundcube/plugin-installer": ">=0.1.3",
|
||||
"kolab/libcalendaring": ">=3.4.0",
|
||||
"kolab/libkolab": ">=3.4.0"
|
||||
"jodlidev/libcalendaring": "dev-master",
|
||||
"kolab/libkolab": ">=3.4.0",
|
||||
"sabre/dav": ">=4.1.5"
|
||||
},
|
||||
"extra": {
|
||||
"roundcube": {
|
||||
|
|
|
@ -25,14 +25,18 @@
|
|||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
// backend type (database, kolab)
|
||||
$config['calendar_driver'] = "database";
|
||||
// backend type (database, kolab, caldav)
|
||||
$config['calendar_driver'] = "caldav";
|
||||
|
||||
// Enable debugging output for iCAL/CalDAV drivers
|
||||
$config['calendar_caldav_debug'] = false;
|
||||
|
||||
// default calendar view (agendaDay, agendaWeek, month)
|
||||
$config['calendar_default_view'] = "agendaWeek";
|
||||
|
||||
// show a birthdays calendar from the user's address book(s)
|
||||
$config['calendar_contact_birthdays'] = false;
|
||||
$config['birthday_calendar'] = array('color' => 'fffb00');
|
||||
|
||||
// timeslots per hour (1, 2, 3, 4, 6)
|
||||
$config['calendar_timeslots'] = 2;
|
||||
|
@ -139,6 +143,20 @@ $config['kolab_invitation_calendars'] = false;
|
|||
// %i - Calendar UUID
|
||||
// $config['calendar_caldav_url'] = 'http://%h/iRony/calendars/%u/%i';
|
||||
|
||||
// List of CalDAV sources that should be allready installed.
|
||||
// They will be added when the calendar section is accessed for the first time by a user.
|
||||
// For 'caldav_user' and 'caldav_url' the following replacement variables are supported:
|
||||
// %u - Current webmail user name
|
||||
// For 'caldav_pass' %p is replaced by the current user's password.
|
||||
// $config['calendar_caldav_preinstalled_sources'] = array(
|
||||
// 'name' => array(
|
||||
// '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).
|
||||
// Leave empty or commented to disable resources support.
|
||||
// $config['calendar_resources_driver'] = 'ldap';
|
||||
|
|
75
drivers/caldav/Isync.php
Normal file
75
drivers/caldav/Isync.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?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);
|
||||
}
|
110
drivers/caldav/SQL/mysql.initial.sql
Normal file
110
drivers/caldav/SQL/mysql.initial.sql
Normal file
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* CalDAV Client
|
||||
*
|
||||
* @version @package_version@
|
||||
* @author Daniel Morlock <daniel.morlock@awesome-it.de>
|
||||
* @author JodliDev <jodlidev@gmail.com>
|
||||
*
|
||||
* Copyright (C) Awesome IT GbR <info@awesome-it.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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` (
|
||||
`calendar_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
|
||||
`source_id` int(10) UNSIGNED DEFAULT NULL,
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
|
||||
`color` varchar(8) NOT NULL,
|
||||
`showalarms` tinyint(1) NOT NULL DEFAULT '1',
|
||||
|
||||
`caldav_tag` varchar(255) DEFAULT NULL,
|
||||
`caldav_url` varchar(1024) NOT NULL,
|
||||
`caldav_last_change` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`is_ical` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`ical_user` varchar(255) DEFAULT NULL,
|
||||
`ical_pass` varchar(1024) DEFAULT NULL,
|
||||
|
||||
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,
|
||||
CONSTRAINT `fk_caldav_calendars_sources` FOREIGN KEY (`source_id`)
|
||||
REFERENCES `caldav_sources`(`source_id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_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 '',
|
||||
`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) CHARACTER SET utf8mb4 NOT NULL,
|
||||
`description` text CHARACTER SET utf8mb4 NOT NULL,
|
||||
`location` varchar(255) CHARACTER SET utf8mb4 NOT NULL DEFAULT '',
|
||||
`categories` varchar(255) CHARACTER SET utf8mb4 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,
|
||||
|
||||
`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 `caldav_calendars`(`calendar_id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_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 `caldav_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-caldav-version', '2021082400');
|
0
drivers/caldav/SQL/mysql/.keep_dir
Normal file
0
drivers/caldav/SQL/mysql/.keep_dir
Normal file
125
drivers/caldav/SQL/postgres.initial.sql
Normal file
125
drivers/caldav/SQL/postgres.initial.sql
Normal file
|
@ -0,0 +1,125 @@
|
|||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
-- SQLINES LICENSE FOR EVALUATION USE ONLY
|
||||
CREATE SEQUENCE IF NOT EXISTS caldav_sources_seq;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS caldav_sources (
|
||||
source_id int CHECK (source_id > 0) NOT NULL DEFAULT NEXTVAL ('caldav_sources_seq'),
|
||||
user_id int CHECK (user_id > 0) 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
|
||||
) /* SQLINES DEMO *** DB */ /* SQLINES DEMO *** ET utf8 COLLATE utf8_general_ci */;
|
||||
|
||||
-- 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;
|
101
drivers/caldav/SQL/sqlite.initial.sql
Normal file
101
drivers/caldav/SQL/sqlite.initial.sql
Normal file
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* 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);
|
404
drivers/caldav/caldav_client.php
Normal file
404
drivers/caldav/caldav_client.php
Normal file
|
@ -0,0 +1,404 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* CalDAV Client
|
||||
*
|
||||
* @version @package_version@
|
||||
* @author Daniel Morlock <daniel.morlock@awesome-it.de>
|
||||
* @author JodliDev <jodlidev@gmail.com>
|
||||
*
|
||||
* Copyright (C) Awesome IT GbR <info@awesome-it.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class caldav_client extends Sabre\DAV\Client
|
||||
{
|
||||
const CLARK_GETCTAG = '{http://calendarserver.org/ns/}getctag';
|
||||
const CLARK_GETETAG = '{DAV:}getetag';
|
||||
const CLARK_CALDATA = '{urn:ietf:params:xml:ns:caldav}calendar-data';
|
||||
|
||||
private $base_uri;
|
||||
private $path;
|
||||
private $libvcal;
|
||||
|
||||
/**
|
||||
* Default constructor for CalDAV client.
|
||||
*
|
||||
* @param string Caldav URI to appropriate calendar.
|
||||
* @param string Username for HTTP basic auth.
|
||||
* @param string Password for HTTP basic auth.
|
||||
*/
|
||||
public function __construct($uri, $user = null, $pass = null)
|
||||
{
|
||||
|
||||
// Include libvcalendar on demand ...
|
||||
if(!class_exists("libvcalendar"))
|
||||
require_once __DIR__ .'/../../../libcalendaring/libvcalendar.php';
|
||||
|
||||
|
||||
$this->libvcal = new libvcalendar();
|
||||
|
||||
$tokens = parse_url($uri);
|
||||
$this->base_uri = $tokens['scheme']."://".$tokens['host'].($tokens['port'] ? ":".$tokens['port'] : null);
|
||||
$this->path = $tokens['path'].($tokens['query'] ? "?".$tokens['query'] : null);
|
||||
|
||||
$settings = array(
|
||||
'baseUri' => $this->base_uri,
|
||||
'authType' => Sabre\DAV\Client::AUTH_BASIC
|
||||
);
|
||||
|
||||
$this->rc = rcmail::get_instance();
|
||||
|
||||
if ($user) $settings['userName'] = $user;
|
||||
if ($pass) $settings['password'] = $pass;
|
||||
|
||||
parent::__construct($settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches calendar ctag.
|
||||
*
|
||||
* @see http://code.google.com/p/sabredav/wiki/BuildingACalDAVClient#Retrieving_calendar_information
|
||||
* @return Calendar ctag or null on error.
|
||||
*/
|
||||
public function get_ctag()
|
||||
{
|
||||
try
|
||||
{
|
||||
$arr = $this->propFind($this->path, array(self::CLARK_GETCTAG));
|
||||
|
||||
if (isset($arr[self::CLARK_GETCTAG]))
|
||||
return $arr[self::CLARK_GETCTAG];
|
||||
}
|
||||
catch(Sabre\DAV\Exception $err)
|
||||
{
|
||||
rcube::raise_error(array(
|
||||
'code' => $err->getHTTPCode(),
|
||||
'type' => 'DAV',
|
||||
'file' => $err->getFile(),
|
||||
'line' => $err->getLine(),
|
||||
'message' => $err->getMessage()
|
||||
), true, false);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches event etags and urls.
|
||||
*
|
||||
* @see http://code.google.com/p/sabredav/wiki/BuildingACalDAVClient#Finding_out_if_anything_changed
|
||||
*
|
||||
* @param array Optional list of relative event URL's to retrieve specific etags. If not specified, all etags of the current calendar are returned.
|
||||
* @return array List of etag properties with keys:
|
||||
* url: Event ical path relative to the calendar URL.
|
||||
* etag: Current event etag.
|
||||
*/
|
||||
public function get_etags(array $event_urls = array())
|
||||
{
|
||||
$etags = array();
|
||||
|
||||
try
|
||||
{
|
||||
$arr = $this->prop_report($this->path, array(self::CLARK_GETETAG), $event_urls);
|
||||
foreach ($arr as $path => $data)
|
||||
{
|
||||
// Some caldav server return an empty calendar as event where etag is missing. Skip this!
|
||||
if($data[self::CLARK_GETETAG])
|
||||
{
|
||||
array_push($etags, array(
|
||||
"url" => $path,
|
||||
"etag" => str_replace('"', null, $data[self::CLARK_GETETAG])
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Sabre\DAV\Exception $err)
|
||||
{
|
||||
rcube::raise_error(array(
|
||||
'code' => $err->getHTTPCode(),
|
||||
'type' => 'DAV',
|
||||
'file' => $err->getFile(),
|
||||
'line' => $err->getLine(),
|
||||
'message' => $err->getMessage()
|
||||
), true, false);
|
||||
}
|
||||
|
||||
return $etags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches calendar events.
|
||||
*
|
||||
* @see http://code.google.com/p/sabredav/wiki/BuildingACalDAVClient#Downloading_objects
|
||||
* @param array $urls = array() Optional list of event URL's to fetch. If non is specified, all
|
||||
* events from the appropriate calendar will be fetched.
|
||||
* @return Array hash list that maps the events URL to the appropriate event properties.
|
||||
*/
|
||||
public function get_events($urls = array())
|
||||
{
|
||||
$events = array();
|
||||
|
||||
try
|
||||
{
|
||||
$vcals = $this->prop_report($this->path, array(
|
||||
self::CLARK_GETETAG,
|
||||
self::CLARK_CALDATA
|
||||
), $urls);
|
||||
|
||||
foreach ($vcals as $path => $response)
|
||||
{
|
||||
$vcal = $response[self::CLARK_CALDATA];
|
||||
if(!$vcal)
|
||||
continue;
|
||||
foreach ($this->libvcal->import($vcal) as $event) {
|
||||
$events[$path] = $event;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Sabre\DAV\Exception $err)
|
||||
{
|
||||
rcube::raise_error(array(
|
||||
'code' => $err->getHTTPCode(),
|
||||
'type' => 'DAV',
|
||||
'file' => $err->getFile(),
|
||||
'line' => $err->getLine(),
|
||||
'message' => $err->getMessage()
|
||||
), true, false);
|
||||
}
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a REPORT request
|
||||
*
|
||||
* @param string $url
|
||||
* @param array $properties List of requested properties must be specified as an array, in clark
|
||||
* notation.
|
||||
* @param array $event_urls If specified, a multiget report request will be initiated with the
|
||||
* specified event urls.
|
||||
* @param int $depth = 1 Depth should be either 0 or 1. A depth of 1 will cause a request to be
|
||||
* made to the server to also return all child resources.
|
||||
* @return array Hash with ics event path as key and a hash array with properties and appropriate values.
|
||||
*/
|
||||
public function prop_report($url, array $properties, array $event_urls = array(), $depth = 1)
|
||||
{
|
||||
$parent_tag = sizeof($event_urls) > 0 ? "c:calendar-multiget" : "d:propfind";
|
||||
$method = sizeof($event_urls) > 0 ? 'REPORT' : 'PROPFIND';
|
||||
|
||||
$body = '<?xml version="1.0"?>'."\n".'<'.$parent_tag.' xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">'."\n";
|
||||
|
||||
$body .= ' <d:prop>'."\n";
|
||||
foreach ($properties as $property)
|
||||
{
|
||||
|
||||
list($namespace, $elementName) = Sabre\Xml\Service::parseClarkNotation($property);
|
||||
|
||||
if ($namespace === 'DAV:')
|
||||
{
|
||||
$body .= ' <d:'.$elementName.' />'."\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
$body .= ' <x:'.$elementName.' xmlns:x="'.$namespace.'"/>'."\n";
|
||||
}
|
||||
}
|
||||
$body .= ' </d:prop>'."\n";
|
||||
|
||||
// http://tools.ietf.org/html/rfc4791#page-90
|
||||
// http://www.bedework.org/trac/bedework/wiki/Bedework/DevDocs/Filters
|
||||
/*
|
||||
if($start && $end)
|
||||
{
|
||||
$body.= ' <c:filter>'."\n".
|
||||
' <c:comp-filter name="VCALENDAR">'."\n".
|
||||
' <c:comp-filter name="VEVENT">'."\n".
|
||||
' <c:time-range start="'.$start.'" end="'.$end.'" />'."\n".
|
||||
' </c:comp-filter>'."\n".
|
||||
' </c:comp-filter>'."\n".
|
||||
' </c:filter>' . "\n";
|
||||
}
|
||||
*/
|
||||
|
||||
foreach ($event_urls as $event_url)
|
||||
{
|
||||
$body .= '<d:href>'.$event_url.'</d:href>'."\n";
|
||||
}
|
||||
|
||||
$body .= '</'.$parent_tag.'>';
|
||||
|
||||
$response = $this->request($method, $url, $body, array(
|
||||
'Depth' => $depth,
|
||||
'Content-Type' => 'application/xml'
|
||||
));
|
||||
|
||||
$result = $this->parseMultiStatus($response['body']);
|
||||
|
||||
// If depth was 0, we only return the top item
|
||||
if ($depth === 0)
|
||||
{
|
||||
reset($result);
|
||||
$result = current($result);
|
||||
return isset($result[200]) ? $result[200] : array();
|
||||
}
|
||||
|
||||
$new_result = array();
|
||||
foreach ($result as $href => $status_list)
|
||||
{
|
||||
$new_result[$href] = isset($status_list[200]) ? $status_list[200] : array();
|
||||
}
|
||||
|
||||
return $new_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates or creates a calendar event.
|
||||
*
|
||||
* @see http://code.google.com/p/sabredav/wiki/BuildingACalDAVClient#Updating_a_calendar_object
|
||||
* @param string Event ics path for the event.
|
||||
* @param array Hash array with event properties.
|
||||
* @param string Current event etag to match against server data. Pass null for new events.
|
||||
* @return True on success, -1 if precondition failed i.e. local etag is not up to date, false on error.
|
||||
*/
|
||||
public function put_event($path, $event, $etag = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
$headers = array("Content-Type" => "text/calendar; charset=utf-8");
|
||||
if ($etag) $headers["If-Match"] = '"'.$etag.'"';
|
||||
|
||||
// Temporarily disable error reporting since libvcal seems not checking array key properly.
|
||||
// TODO: Remove this todo if we could ensure that those errors come not from incomplete event properties.
|
||||
$err_rep = error_reporting(E_ERROR);
|
||||
$vcal = $this->libvcal->export(array($event));
|
||||
if (is_array($vcal))
|
||||
$vcal = array_shift($vcal);
|
||||
error_reporting($err_rep);
|
||||
|
||||
$response = $this->request('PUT', $path, $vcal, $headers);
|
||||
|
||||
// Following http://code.google.com/p/sabredav/wiki/BuildingACalDAVClient#Creating_a_calendar_object, the
|
||||
// caldav server must not always return the new etag.
|
||||
|
||||
return $response["statusCode"] == 201 || // 201 (created, successfully created)
|
||||
$response["statusCode"] == 204; // 204 (no content, successfully updated)
|
||||
}
|
||||
catch(Sabre\DAV\Exception\PreconditionFailed $err)
|
||||
{
|
||||
// Event tag not up to date, must be updated first ...
|
||||
return -1;
|
||||
}
|
||||
catch(Sabre\DAV\Exception $err)
|
||||
{
|
||||
rcube::raise_error(array(
|
||||
'code' => $err->getHTTPCode(),
|
||||
'type' => 'DAV',
|
||||
'file' => $err->getFile(),
|
||||
'line' => $err->getLine(),
|
||||
'message' => $err->getMessage()
|
||||
), true, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes event of given URL.
|
||||
*
|
||||
* @see http://code.google.com/p/sabredav/wiki/BuildingACalDAVClient#Deleting_a_calendar_object
|
||||
* @param string Event ics path for the event.
|
||||
* @param string Current event etag to match against server data. Pass null to force removing the event.
|
||||
* @return True on success, -1 if precondition failed i.e. local etag is not up to date, false on error.
|
||||
**/
|
||||
public function remove_event($path, $etag = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
$headers = array("Content-Type" => "text/calendar; charset=utf-8");
|
||||
if ($etag) $headers["If-Match"] = '"'.$etag.'"';
|
||||
|
||||
$response = $this->request('DELETE', $path, null, $headers);
|
||||
return $response["statusCode"] == 204 || // 204 (no content, successfully deleted)
|
||||
$response["statusCode"] == 200; // 200 (OK, successfully deleted)
|
||||
}
|
||||
catch(Sabre\DAV\Exception\PreconditionFailed $err)
|
||||
{
|
||||
// Event tag not up to date, must be updated first ...
|
||||
return -1;
|
||||
}
|
||||
catch(Sabre\DAV\Exception $err)
|
||||
{
|
||||
rcube::raise_error(array(
|
||||
'code' => $err->getHTTPCode(),
|
||||
'type' => 'DAV',
|
||||
'file' => $err->getFile(),
|
||||
'line' => $err->getLine(),
|
||||
'message' => $err->getMessage()
|
||||
), true, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a propFind query to caldav server
|
||||
* @param string $path absolute or relative URL to Resource
|
||||
* @param array $props list of properties to use for the query. Properties must have clark-notation.
|
||||
* @param int $depth 0 means no recurse while 1 means recurse
|
||||
* @return array
|
||||
*/
|
||||
public function prop_find($path, $props, $depth)
|
||||
{
|
||||
try {
|
||||
$response = $this->propFind($path, $props, $depth);
|
||||
}
|
||||
catch(Sabre\DAV\Exception $err)
|
||||
{
|
||||
rcube::raise_error(array(
|
||||
'code' => $err->getHTTPCode(),
|
||||
'type' => 'DAV',
|
||||
'file' => $err->getFile(),
|
||||
'line' => $err->getLine(),
|
||||
'message' => $err->getMessage()
|
||||
), true, false);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function create_calendar($path, $name, $color) {
|
||||
$headers = array("Content-Type" => "application/xml; charset=utf-8");
|
||||
$body = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>
|
||||
<C:mkcalendar xmlns:D=\"DAV:\" xmlns:C=\"urn:ietf:params:xml:ns:caldav\" xmlns:ical=\"http://apple.com/ns/ical/\">
|
||||
<D:set>
|
||||
<D:prop>
|
||||
<D:displayname>$name</D:displayname>
|
||||
<ical:calendar-color>#$color</ical:calendar-color>
|
||||
</D:prop>
|
||||
</D:set>
|
||||
</C:mkcalendar>";
|
||||
|
||||
$response = $this->request('MKCALENDAR', $path, $body, $headers);
|
||||
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)
|
||||
}
|
||||
};
|
||||
?>
|
2352
drivers/caldav/caldav_driver.php
Normal file
2352
drivers/caldav/caldav_driver.php
Normal file
File diff suppressed because it is too large
Load diff
254
drivers/caldav/caldav_sync.php
Normal file
254
drivers/caldav/caldav_sync.php
Normal file
|
@ -0,0 +1,254 @@
|
|||
<?php
|
||||
/**
|
||||
* CalDAV sync for the Calendar plugin
|
||||
*
|
||||
* @version @package_version@
|
||||
* @author Daniel Morlock <daniel.morlock@awesome-it.de>
|
||||
* @author JodliDev <jodlidev@gmail.com>
|
||||
*
|
||||
* Copyright (C) Awesome IT GbR <info@awesome-it.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
require_once 'caldav_client.php';
|
||||
require_once 'Isync.php';
|
||||
|
||||
class caldav_sync implements Isync
|
||||
{
|
||||
private $cal_id = null;
|
||||
private $ctag = null;
|
||||
private $username = null;
|
||||
private $pass = null;
|
||||
private $url = null;
|
||||
|
||||
public $caldav = null;
|
||||
|
||||
/**
|
||||
* Default constructor for calendar synchronization adapter.
|
||||
*
|
||||
* @param array Hash array with caldav properties at least the following:
|
||||
* id: Calendar ID
|
||||
* caldav_url: Caldav calendar URL.
|
||||
* caldav_user: Caldav http basic auth user.
|
||||
* caldav_pass: Password für caldav user.
|
||||
* caldav_tag: Caldav ctag for calendar.
|
||||
*/
|
||||
public function __construct($cal)
|
||||
{
|
||||
$this->cal_id = $cal["id"];
|
||||
$this->url = $cal["caldav_url"];
|
||||
$this->ctag = isset($cal["caldav_tag"]) ? $cal["caldav_tag"] : null;
|
||||
$this->username = isset($cal["caldav_user"]) ? $cal["caldav_user"] : null;
|
||||
$this->pass = isset($cal["caldav_pass"]) ? $cal["caldav_pass"] : null;
|
||||
|
||||
$this->caldav = new caldav_client($this->url, $this->username, $this->pass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for current calendar ctag.
|
||||
* @return string
|
||||
*/
|
||||
public function get_ctag()
|
||||
{
|
||||
return $this->ctag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether current calendar needs to be synced
|
||||
* regarding the CalDAV ctag.
|
||||
*
|
||||
* @return boolean True if the current calendar ctag differs from the CalDAV tag which
|
||||
* indicates that there are changes that must be synched. Returns false
|
||||
* if the calendar is up to date, no sync necesarry.
|
||||
*/
|
||||
public function is_synced()
|
||||
{
|
||||
$is_synced = $this->ctag == $this->caldav->get_ctag() && $this->ctag;
|
||||
caldav_driver::debug_log("Ctag indicates that calendar \"$this->cal_id\" ".($is_synced ? "is synced." : "needs update!"));
|
||||
|
||||
return $is_synced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes given events with caldav 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)
|
||||
{
|
||||
$ctag = $this->caldav->get_ctag();
|
||||
|
||||
if($ctag)
|
||||
{
|
||||
$this->ctag = $ctag;
|
||||
$etags = $this->caldav->get_etags();
|
||||
|
||||
list($updates, $synced_event_ids) = $this->_get_event_updates($events, $etags);
|
||||
return array($this->_get_event_data($updates), $synced_event_ids);
|
||||
}
|
||||
else
|
||||
{
|
||||
caldav_driver::debug_log("Unkown error while fetching calendar ctag for calendar \"$this->cal_id\"!");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines sync status and requried updates for the given events using given list of etags.
|
||||
*
|
||||
* @param array List of hash arrays with event properties, must include "caldav_url" and "caldav_tag".
|
||||
* @param array List of current remote etags.
|
||||
* @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.
|
||||
*
|
||||
* A list of event ids that are in sync.
|
||||
*/
|
||||
private function _get_event_updates($events, $etags)
|
||||
{
|
||||
$updates = array();
|
||||
$in_sync = array();
|
||||
|
||||
foreach ($etags as $etag)
|
||||
{
|
||||
$url = $etag["url"];
|
||||
$etag = $etag["etag"];
|
||||
$event_found = false;
|
||||
foreach($events as $event)
|
||||
{
|
||||
if ($event["caldav_url"] == $url)
|
||||
{
|
||||
$event_found = true;
|
||||
|
||||
if ($event["caldav_tag"] != $etag)
|
||||
{
|
||||
caldav_driver::debug_log("Event ".$event["uid"]." needs update.");
|
||||
|
||||
array_push($updates, array(
|
||||
"local_event" => $event,
|
||||
"etag" => $etag,
|
||||
"url" => $url
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
array_push($in_sync, $event["id"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$event_found)
|
||||
{
|
||||
caldav_driver::debug_log("Found new event ".$url);
|
||||
|
||||
array_push($updates, array(
|
||||
"url" => $url,
|
||||
"etag" => $etag
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return array($updates, $in_sync);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches event data and attaches it to the given update properties.
|
||||
*
|
||||
* @param $updates array of update properties.
|
||||
* @return array List of update properties with additional key "remote_event" containing the current caldav event.
|
||||
*/
|
||||
private function _get_event_data($updates)
|
||||
{
|
||||
$urls = array();
|
||||
|
||||
foreach ($updates as $update)
|
||||
{
|
||||
array_push($urls, $update["url"]);
|
||||
}
|
||||
|
||||
$events = $this->caldav->get_events($urls);
|
||||
foreach($updates as &$update)
|
||||
{
|
||||
// Attach remote events to the appropriate updates.
|
||||
// Note that this assumes unique event URL's!
|
||||
$url = $update["url"];
|
||||
if($events[$url]) {
|
||||
$update["remote_event"] = $events[$url];
|
||||
$update["remote_event"]["calendar"] = $this->cal_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $updates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the given event on the CalDAV server.
|
||||
*
|
||||
* @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)
|
||||
{
|
||||
$props = array(
|
||||
"caldav_url" => parse_url($this->url, PHP_URL_PATH)."/".$event["uid"].".ics",
|
||||
"caldav_tag" => null
|
||||
);
|
||||
|
||||
caldav_driver::debug_log("Push new event to url ".$props["caldav_url"]);
|
||||
$result = $this->caldav->put_event($props["caldav_url"], $event);
|
||||
|
||||
if($result == false || $result < 0)
|
||||
return null;
|
||||
return array_merge($event, $props);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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".
|
||||
* @return boolean True on success, false on error, -1 if the given event/etag is not up to date.
|
||||
*/
|
||||
public function update_event($event)
|
||||
{
|
||||
caldav_driver::debug_log("Updating event uid \"".$event["uid"]."\".");
|
||||
return $this->caldav->put_event($event["caldav_url"], $event, $event["caldav_tag"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given event from the caldav server.
|
||||
*
|
||||
* @param array Hash array with events properties, must include "caldav_url".
|
||||
* @return boolean True on success, false on error.
|
||||
*/
|
||||
public function remove_event($event)
|
||||
{
|
||||
caldav_driver::debug_log("Removing event uid \"".$event["uid"]."\".");
|
||||
return $this->caldav->remove_event($event["caldav_url"]);
|
||||
}
|
||||
};
|
||||
?>
|
73
drivers/caldav/encryption.php
Normal file
73
drivers/caldav/encryption.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
/**
|
||||
* Encryption class
|
||||
* (Copied by JodliDev from https://github.com/mstilkerich/rcmcarddav/blob/master/carddav.php)
|
||||
*
|
||||
* @author Jorge López Pérez <jorge@adobo.org> (original author)
|
||||
* @author JodliDev <jodlidev@gmail.com>
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
class Encryption {
|
||||
private function getDesKey()
|
||||
{
|
||||
$rcube = rcube::get_instance();
|
||||
$imap_password = $rcube->decrypt((string) $_SESSION['password']);
|
||||
|
||||
if ($imap_password === false || strlen($imap_password) == 0) {
|
||||
throw new \Exception('No password available to use for encryption');
|
||||
}
|
||||
|
||||
while (strlen($imap_password) < 24) {
|
||||
$imap_password .= $imap_password;
|
||||
}
|
||||
return substr($imap_password, 0, 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a password to storage format according to the password storage scheme setting.
|
||||
*
|
||||
* @param string $clear The password in clear text.
|
||||
* @return string The password in storage format (e.g. encrypted with user password as key)
|
||||
* @throws Exception
|
||||
*/
|
||||
public function encrypt($clear) {
|
||||
// encrypted with IMAP password
|
||||
$rcube = rcube::get_instance();
|
||||
|
||||
$imap_password = $this->getDesKey();
|
||||
$rcube->config->set('carddav_des_key', $imap_password);
|
||||
|
||||
$crypted = $rcube->encrypt($clear, 'carddav_des_key');
|
||||
|
||||
// there seems to be no way to unset a preference
|
||||
$rcube->config->set('carddav_des_key', '');
|
||||
|
||||
if ($crypted === false) {
|
||||
throw new \Exception('Password encryption with user password failed');
|
||||
}
|
||||
|
||||
return $crypted;
|
||||
}
|
||||
|
||||
public function decrypt($crypt) {
|
||||
try {
|
||||
$rcube = rcube::get_instance();
|
||||
|
||||
$imap_password = $this->getDesKey();
|
||||
$rcube->config->set('carddav_des_key', $imap_password);
|
||||
$clear = $rcube->decrypt($crypt, 'carddav_des_key');
|
||||
// there seems to be no way to unset a preference
|
||||
$rcube->config->set('carddav_des_key', '');
|
||||
if ($clear === false) {
|
||||
$clear = '';
|
||||
}
|
||||
|
||||
return $clear;
|
||||
} catch (\Exception $e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
163
drivers/caldav/ical_sync.php
Normal file
163
drivers/caldav/ical_sync.php
Normal file
|
@ -0,0 +1,163 @@
|
|||
<?php
|
||||
/**
|
||||
* iCalendar sync for the Calendar plugin
|
||||
*
|
||||
* @version @package_version@
|
||||
* @author Daniel Morlock <daniel.morlock@awesome-it.de>
|
||||
* @author JodliDev <jodlidev@gmail.com>
|
||||
*
|
||||
* Copyright (C) Awesome IT GbR <info@awesome-it.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class ical_sync implements Isync
|
||||
{
|
||||
const ACTION_NONE = 1;
|
||||
const ACTION_UPDATE = 2;
|
||||
const ACTION_CREATE = 4;
|
||||
|
||||
private $cal_id = null;
|
||||
private $url = null;
|
||||
private $user = null;
|
||||
private $pass = null;
|
||||
private $ical = null;
|
||||
|
||||
/**
|
||||
* Default constructor for calendar synchronization adapter.
|
||||
*
|
||||
* @param int Calendar id.
|
||||
* @param array Hash array with ical properties:
|
||||
* url: Absolute URL to iCAL resource.
|
||||
*/
|
||||
public function __construct($props)
|
||||
{
|
||||
$this->ical = libcalendaring::get_ical();
|
||||
$this->cal_id = $props["id"];
|
||||
|
||||
$this->url = $props["caldav_url"];
|
||||
$this->user = isset($props["ical_user"]) ? $props["ical_user"] : null;
|
||||
$this->pass = isset($props["ical_pass"]) ? $props["ical_pass"] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
// No change to check that so far.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches events from iCAL resource and returns updates.
|
||||
*
|
||||
* @param array List of local events.
|
||||
* @return array Tuple containing the following lists:
|
||||
*
|
||||
* Hash list for iCAL events to be created or to be updated with the keys:
|
||||
* 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)
|
||||
{
|
||||
$context = null;
|
||||
if($this->user != null && $this->pass != null)
|
||||
{
|
||||
$context = stream_context_create(array(
|
||||
'http' => array(
|
||||
'header' => "Authorization: Basic " . base64_encode("$this->user:$this->pass")
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
$vcal = file_get_contents($this->url, false, $context);
|
||||
$updates = array();
|
||||
$synced = array();
|
||||
if($vcal !== false) {
|
||||
|
||||
// Hash existing events by uid.
|
||||
$events_hash = array();
|
||||
foreach($events as $event) {
|
||||
$events_hash[$event['uid']] = $event;
|
||||
}
|
||||
|
||||
foreach ($this->ical->import($vcal) as $remote_event) {
|
||||
|
||||
// Attach remote event to current calendar
|
||||
$remote_event['calendar'] = $this->cal_id;
|
||||
|
||||
$local_event = null;
|
||||
if($events_hash[$remote_event['uid']])
|
||||
$local_event = $events_hash[$remote_event['uid']];
|
||||
|
||||
// Determine whether event don't need an update.
|
||||
if($local_event && $local_event['changed'] >= $remote_event['changed']) {
|
||||
array_push($synced, $local_event["id"]);
|
||||
}
|
||||
else if($local_event) {
|
||||
array_push($updates, array('local_event' => $local_event, 'remote_event' => $remote_event, 'url' => $this->url));
|
||||
}
|
||||
else {
|
||||
array_push($updates, array('remote_event' => $remote_event, 'url' => $this->url));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
?>
|
|
@ -362,11 +362,11 @@ abstract class calendar_driver
|
|||
{
|
||||
$valid = true;
|
||||
|
||||
if (empty($event['start']) || !is_object($event['start']) || !is_a($event['start'], 'DateTime')) {
|
||||
if (empty($event['start']) || !is_object($event['start']) || (!is_a($event['start'], 'DateTime') && !is_a($event['start'], 'DateTimeImmutable'))) {
|
||||
$valid = false;
|
||||
}
|
||||
|
||||
if (empty($event['end']) || !is_object($event['end']) || !is_a($event['end'], 'DateTime')) {
|
||||
if (empty($event['end']) || !is_object($event['end']) || (!is_a($event['end'], 'DateTime') && !is_a($event['end'], 'DateTimeImmutable'))) {
|
||||
$valid = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -715,6 +715,8 @@ class database_driver extends calendar_driver
|
|||
'title', 'description', 'location', 'categories', 'url', 'free_busy', 'priority',
|
||||
'sensitivity', 'status', 'attendees', 'alarms', 'notifyat'
|
||||
);
|
||||
if(array_key_exists('notifyat', $event) && empty($event['notifyat']))
|
||||
unset($event['notifyat']);
|
||||
|
||||
foreach ($set_cols as $col) {
|
||||
if (!empty($event[$col]) && is_a($event[$col], 'DateTime')) {
|
||||
|
|
|
@ -41,12 +41,13 @@ class resources_driver_ldap extends resources_driver
|
|||
/**
|
||||
* Fetch resource objects to be displayed for booking
|
||||
*
|
||||
* @param string $query Search query (optional)
|
||||
* @param int $num Max size of the result
|
||||
* @param string $query Search query (optional)
|
||||
* @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
|
||||
*/
|
||||
public function load_resources($query = null, $num = 5000)
|
||||
public function load_resources($query = null, $num = 5000, $searchField = '*')
|
||||
{
|
||||
if (!($ldap = $this->connect())) {
|
||||
return [];
|
||||
|
@ -56,7 +57,7 @@ class resources_driver_ldap extends resources_driver
|
|||
$ldap->set_pagesize($num);
|
||||
|
||||
if (isset($query)) {
|
||||
$results = $ldap->search('*', $query, 0, true, true);
|
||||
$results = $ldap->search($searchField, $query, 0, true, true);
|
||||
}
|
||||
else {
|
||||
$results = $ldap->list_records();
|
||||
|
|
|
@ -6,6 +6,11 @@
|
|||
*
|
||||
* 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['time_format'] = 'Zeitformatierung';
|
||||
$labels['timeslots'] = 'Zeitfenster pro Stunde';
|
||||
|
|
|
@ -6,6 +6,11 @@
|
|||
*
|
||||
* 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['time_format'] = 'Zeitformatierung';
|
||||
$labels['timeslots'] = 'Abschnitte pro Stunde';
|
||||
|
|
|
@ -6,6 +6,11 @@
|
|||
*
|
||||
* 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['time_format'] = 'Zeitformatierung';
|
||||
$labels['timeslots'] = 'Zeitfenster pro Stunde';
|
||||
|
|
|
@ -10,6 +10,12 @@
|
|||
|
||||
$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
|
||||
$labels['default_view'] = 'Default view';
|
||||
$labels['time_format'] = 'Time format';
|
||||
|
@ -207,6 +213,10 @@ $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['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['declinedeleteconfirm'] = 'Do you also want to delete this declined event from your calendar?';
|
||||
$labels['itipcomment'] = 'Invitation/notification comment';
|
||||
|
|
|
@ -141,6 +141,10 @@
|
|||
<div id="calendaractions-menu" class="popupmenu">
|
||||
<h3 id="aria-label-calendaroptions" class="voice"><roundcube:label name="calendar.calendaractions" /></h3>
|
||||
<ul class="menu listing" role="menu" aria-labelledby="aria-label-calendaroptions">
|
||||
<roundcube:if condition="env:calendar_driver == 'caldav'" />
|
||||
<roundcube:button type="link-menuitem" command="calendar-sources-new" label="calendar.addsources" class="create" />
|
||||
<roundcube:button type="link-menuitem" command="calendar-sources-delete" label="calendar.deletesources" class="delete" />
|
||||
<roundcube:endif />
|
||||
<roundcube:button type="link-menuitem" command="calendar-create" label="calendar.addcalendar" class="create disabled" classAct="create active" />
|
||||
<roundcube:button type="link-menuitem" command="calendar-edit" label="calendar.editcalendar" class="edit disabled" classAct="edit active" />
|
||||
<roundcube:button type="link-menuitem" command="calendar-delete" label="calendar.deletecalendar" class="delete disabled" classAct="delete active" />
|
||||
|
|
|
@ -68,6 +68,10 @@
|
|||
<div id="calendaroptionsmenu" class="popupmenu" aria-hidden="true">
|
||||
<h3 id="aria-label-calendaroptions" class="voice"><roundcube:label name="calendar.calendaractions" /></h3>
|
||||
<ul id="calendaroptionsmenu-menu" class="toolbarmenu" role="menu" aria-labelledby="aria-label-calendaroptions">
|
||||
<roundcube:if condition="env:calendar_driver == 'caldav'" />
|
||||
<li role="menuitem"><roundcube:button type="link" command="calendar-sources-new" label="calendar.addsources" class="active" /></li>
|
||||
<li role="menuitem"><roundcube:button type="link" command="calendar-sources-delete" label="calendar.deletesources" class="active" /></li>
|
||||
<roundcube:endif />
|
||||
<li role="menuitem"><roundcube:button type="link" command="calendar-edit" label="calendar.edit" classAct="active" /></li>
|
||||
<li role="menuitem"><roundcube:button type="link" command="calendar-delete" label="delete" classAct="active" /></li>
|
||||
<roundcube:if condition="env:calendar_driver == 'kolab'" />
|
||||
|
|
Loading…
Reference in a new issue