Added (modified) copy of Roundcube calendar from https://github.com/rc-calendar/calendar
7
plugins/calendar/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
*.swp
|
||||||
|
*.bak
|
||||||
|
*.old
|
||||||
|
*~
|
||||||
|
config.inc.php
|
||||||
|
skins/*
|
||||||
|
!skins/default
|
37
plugins/calendar/TODO
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
+ Edit: 3.12: Subject
|
||||||
|
+ Edit: 3.13: Location
|
||||||
|
+ Edit: 3.14: Start / End / All Day
|
||||||
|
+ Edit: 3.15: Show time as: Busy, Free, Out of office
|
||||||
|
- Edit: 3.16: Reminder set
|
||||||
|
+ Edit: 3.17: Priority: High/Low
|
||||||
|
- Edit: 3.19: Attachment Upload
|
||||||
|
- Edit: 3.20: Print
|
||||||
|
- Recurring events
|
||||||
|
- Add/Manage Attendees
|
||||||
|
- Edit: 3.21: Required / Optional / Resource specification
|
||||||
|
- Edit: 3.22: Conflict Handling (Free/Busy Check for attendees)
|
||||||
|
- Edit: 3.23: Specify folder for new event (prefs)
|
||||||
|
- View: 3.1: Folder list
|
||||||
|
- View: 3.3: Display modes (agenda / day / week / month)
|
||||||
|
+ Day / Week / Month
|
||||||
|
- List (Agenda) view
|
||||||
|
- Individual days selection
|
||||||
|
- View: 3.4: Fish-Eye View For Busy Days
|
||||||
|
- View: 3.5: Search
|
||||||
|
+ Show list of calendars in a (hideable) drawer
|
||||||
|
+ View: 3.6: Combined calendar view (Turn calendars on/off)
|
||||||
|
+ View: 3.7: Small month overview calendar
|
||||||
|
- View: 3.8: Color according to calendar and category (similar to Kontact)
|
||||||
|
+ View: 3.9: Alter event with drag/drop
|
||||||
|
- Option: 4.12: Set default reminder time
|
||||||
|
+ Support for multiple calendars (replace categories)
|
||||||
|
- Remember last visited view
|
||||||
|
- Allow user to create/edit/delete calendars
|
||||||
|
- Colors for calendars should be user-configurable
|
||||||
|
- ICS parser/generator ((http://code.google.com/p/qcal/)
|
||||||
|
- Importing ICS files
|
||||||
|
- Create/manage invdividual views
|
||||||
|
|
||||||
|
- Support for tasks/todos with task list view (ordered by date/time)
|
||||||
|
? Show and edit two different views
|
||||||
|
? Use ICS format for storage in DB
|
768
plugins/calendar/calendar.js
Normal file
|
@ -0,0 +1,768 @@
|
||||||
|
/*
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Javascript for the Calendar Plugin |
|
||||||
|
| Version 0.3 beta |
|
||||||
|
| |
|
||||||
|
| This program is free software; you can redistribute it and/or modify |
|
||||||
|
| it under the terms of the GNU General Public License version 2 |
|
||||||
|
| as published by the Free Software Foundation. |
|
||||||
|
| |
|
||||||
|
| 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 General Public License for more details. |
|
||||||
|
| |
|
||||||
|
| You should have received a copy of the GNU General Public License along |
|
||||||
|
| with this program; if not, write to the Free Software Foundation, Inc., |
|
||||||
|
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
|
||||||
|
| |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Author: Lazlo Westerhof <hello@lazlo.me> |
|
||||||
|
| Thomas Bruederli <roundcube@gmail.com> |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* calendar initialization */
|
||||||
|
window.rcmail && rcmail.addEventListener('init', function(evt) {
|
||||||
|
|
||||||
|
// quote html entities
|
||||||
|
function Q(str)
|
||||||
|
{
|
||||||
|
return String(str).replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||||
|
}
|
||||||
|
// php equivalent
|
||||||
|
function nl2br(str)
|
||||||
|
{
|
||||||
|
return String(str).replace(/\n/g, "<br/>");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Roundcube calendar client class
|
||||||
|
function rcube_calendar(settings)
|
||||||
|
{
|
||||||
|
this.settings = settings;
|
||||||
|
var me = this;
|
||||||
|
|
||||||
|
// private vars
|
||||||
|
var day_clicked = 0;
|
||||||
|
var ignore_click = false;
|
||||||
|
|
||||||
|
// data loader
|
||||||
|
var load_events = function(calendar, start, end, callback) {
|
||||||
|
$.ajax({
|
||||||
|
url: "./?_task=calendar&_action=plugin.load_events",
|
||||||
|
dataType: 'json',
|
||||||
|
data: {
|
||||||
|
source: calendar,
|
||||||
|
start: Math.round(start.getTime() / 1000),
|
||||||
|
end: Math.round(end.getTime() / 1000)
|
||||||
|
},
|
||||||
|
success: callback
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// event details dialog (show only)
|
||||||
|
var event_show_dialog = function(event) {
|
||||||
|
var $dialog = $("#eventshow");
|
||||||
|
var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { editable:false };
|
||||||
|
|
||||||
|
$dialog.find('div.event-section, div.event-line').hide();
|
||||||
|
$('#event-title').html(Q(event.title)).show();
|
||||||
|
|
||||||
|
if (event.location)
|
||||||
|
$('#event-location').html('@ ' + Q(event.location)).show();
|
||||||
|
if (event.description)
|
||||||
|
$('#event-description').show().children('.event-text').html(nl2br(Q(event.description))); // TODO: format HTML with clickable links and stuff
|
||||||
|
|
||||||
|
// TODO: create a nice human-readable string for the date/time range
|
||||||
|
var fromto, duration = event.end.getTime() / 1000 - event.start.getTime() / 1000;
|
||||||
|
if (event.allDay)
|
||||||
|
fromto = $.fullCalendar.formatDate(event.start, settings['date_format']) + ' — ' + $.fullCalendar.formatDate(event.end, settings['date_format']);
|
||||||
|
else if (duration < 86400 && event.start.getDay() == event.end.getDay())
|
||||||
|
fromto = $.fullCalendar.formatDate(event.start, settings['date_format']) + ' ' + $.fullCalendar.formatDate(event.start, settings['time_format']) + ' — '
|
||||||
|
+ $.fullCalendar.formatDate(event.end, settings['time_format']);
|
||||||
|
else
|
||||||
|
fromto = $.fullCalendar.formatDate(event.start, settings['date_format']) + ' ' + $.fullCalendar.formatDate(event.start, settings['time_format']) + ' — '
|
||||||
|
+ $.fullCalendar.formatDate(event.end, settings['date_format']) + ' ' + $.fullCalendar.formatDate(event.end, settings['time_format']);
|
||||||
|
$('#event-date').html(Q(fromto)).show();
|
||||||
|
|
||||||
|
if (event.recurrence && event.recurrence_text)
|
||||||
|
$('#event-repeat').show().children('.event-text').html(Q(event.recurrence_text));
|
||||||
|
|
||||||
|
if (event.reminders && event.reminders_text)
|
||||||
|
$('#event-alarm').show().children('.event-text').html(Q(event.reminders_text));
|
||||||
|
|
||||||
|
if (calendar.name)
|
||||||
|
$('#event-calendar').show().children('.event-text').html(Q(calendar.name)).removeClass().addClass('event-text').addClass('cal-'+calendar.id);
|
||||||
|
if (event.categories)
|
||||||
|
$('#event-category').show().children('.event-text').html(Q(event.categories)).removeClass().addClass('event-text '+event.className);
|
||||||
|
if (event.free_busy)
|
||||||
|
$('#event-free-busy').show().children('.event-text').html(Q(rcmail.gettext(event.free_busy, 'calendar')));
|
||||||
|
if (event.priority != 1) {
|
||||||
|
var priolabels = { 0:rcmail.gettext('low'), 1:rcmail.gettext('normal'), 2:rcmail.gettext('high') };
|
||||||
|
$('#event-priority').show().children('.event-text').html(Q(priolabels[event.priority]));
|
||||||
|
}
|
||||||
|
|
||||||
|
var buttons = {};
|
||||||
|
if (calendar.editable) {
|
||||||
|
buttons[rcmail.gettext('edit', 'calendar')] = function() {
|
||||||
|
event_edit_dialog('edit', event);
|
||||||
|
};
|
||||||
|
buttons[rcmail.gettext('remove', 'calendar')] = function() {
|
||||||
|
me.delete_event(event);
|
||||||
|
$dialog.dialog('close');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buttons[rcmail.gettext('close', 'calendar')] = function(){
|
||||||
|
$dialog.dialog('close');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// open jquery UI dialog
|
||||||
|
$dialog.dialog({
|
||||||
|
modal: false,
|
||||||
|
resizable: true,
|
||||||
|
title: null,
|
||||||
|
close: function() {
|
||||||
|
$dialog.dialog('destroy');
|
||||||
|
$dialog.hide();
|
||||||
|
},
|
||||||
|
buttons: buttons,
|
||||||
|
minWidth: 320,
|
||||||
|
width: 420
|
||||||
|
}).show();
|
||||||
|
|
||||||
|
$('<a>')
|
||||||
|
.attr('href', '#')
|
||||||
|
.html('More Options')
|
||||||
|
.addClass('dropdown-link')
|
||||||
|
.click(function(){ return false; })
|
||||||
|
.insertBefore($dialog.parent().find('.ui-dialog-buttonset').children().first());
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// bring up the event dialog (jquery-ui popup)
|
||||||
|
var event_edit_dialog = function(action, event) {
|
||||||
|
// close show dialog first
|
||||||
|
$("#eventshow").dialog('close');
|
||||||
|
|
||||||
|
var $dialog = $("#eventedit");
|
||||||
|
var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { editable:action=='new' };
|
||||||
|
|
||||||
|
// reset dialog first, enable/disable fields according to editable state
|
||||||
|
$('#eventtabs').get(0).reset();
|
||||||
|
$('#calendar-select')[(action == 'new' ? 'show' : 'hide')]();
|
||||||
|
|
||||||
|
// event details
|
||||||
|
var title = $('#edit-title').val(event.title);
|
||||||
|
var location = $('#edit-location').val(event.location);
|
||||||
|
var description = $('#edit-description').val(event.description);
|
||||||
|
var categories = $('#edit-categories').val(event.categories);
|
||||||
|
var calendars = $('#edit-calendar').val(event.calendar);
|
||||||
|
var freebusy = $('#edit-free-busy').val(event.free_busy);
|
||||||
|
var priority = $('#edit-priority').val(event.priority);
|
||||||
|
|
||||||
|
var duration = Math.round((event.end.getTime() - event.start.getTime()) / 1000);
|
||||||
|
var startdate = $('#edit-startdate').val($.fullCalendar.formatDate(event.start, settings['date_format'])).data('duration', duration);
|
||||||
|
var starttime = $('#edit-starttime').val($.fullCalendar.formatDate(event.start, settings['time_format'])).show();
|
||||||
|
var enddate = $('#edit-enddate').val($.fullCalendar.formatDate(event.end, settings['date_format']));
|
||||||
|
var endtime = $('#edit-endtime').val($.fullCalendar.formatDate(event.end, settings['time_format'])).show();
|
||||||
|
var allday = $('#edit-allday').get(0);
|
||||||
|
|
||||||
|
if (event.allDay) {
|
||||||
|
starttime.val("00:00").hide();
|
||||||
|
endtime.val("23:59").hide();
|
||||||
|
allday.checked = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
allday.checked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set alarm(s)
|
||||||
|
// TODO: support multiple alarm entries
|
||||||
|
if (event.alarms) {
|
||||||
|
if (typeof event.alarms == 'string')
|
||||||
|
event.alarms = event.alarms.split(';');
|
||||||
|
|
||||||
|
for (var alarm, i=0; i < event.alarms.length; i++) {
|
||||||
|
alarm = String(event.alarms[i]).split(':');
|
||||||
|
$('select.edit-alarm-type').val(alarm[0]);
|
||||||
|
|
||||||
|
if (alarm[1].match(/@(\d+)/)) {
|
||||||
|
var ondate = new Date(parseInt(RegExp.$1));
|
||||||
|
$('select.edit-alarm-offset').val('@');
|
||||||
|
$('input.edit-alarm-date').val($.fullCalendar.formatDate(ondate, settings['date_format']));
|
||||||
|
$('input.edit-alarmtime').val($.fullCalendar.formatDate(ondate, settings['time_format']));
|
||||||
|
}
|
||||||
|
else if (alarm[1].match(/([-+])(\d+)([mhd])/)) {
|
||||||
|
$('input.edit-alarm-value').val(RegExp.$2);
|
||||||
|
$('select.edit-alarm-offset').val(''+RegExp.$1+RegExp.$3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// set correct visibility by triggering onchange handlers
|
||||||
|
$('select.edit-alarm-type, select.edit-alarm-offset').change();
|
||||||
|
}
|
||||||
|
|
||||||
|
// set recurrence form
|
||||||
|
var recurrence = $('#edit-recurrence-frequency').val(event.recurrence ? event.recurrence.FREQ : '').change();
|
||||||
|
var interval = $('select.edit-recurrence-interval').val(event.recurrence ? event.recurrence.INTERVAL : 1);
|
||||||
|
var rrtimes = $('#edit-recurrence-repeat-times').val(event.recurrence ? event.recurrence.COUNT : 1);
|
||||||
|
var rrenddate = $('#edit-recurrence-enddate').val(event.recurrence && event.recurrence.UNTIL ? $.fullCalendar.formatDate(new Date(event.recurrence.UNTIL*1000), settings['date_format']) : '');
|
||||||
|
$('input.edit-recurrence-until:checked').prop('checked', false);
|
||||||
|
|
||||||
|
var weekdays = ['SU','MO','TU','WE','TH','FR','SA'];
|
||||||
|
var rrepeat_id = '#edit-recurrence-repeat-forever';
|
||||||
|
if (event.recurrence && event.recurrence.COUNT) rrepeat_id = '#edit-recurrence-repeat-count';
|
||||||
|
else if (event.recurrence && event.recurrence.UNTIL) rrepeat_id = '#edit-recurrence-repeat-until';
|
||||||
|
$(rrepeat_id).prop('checked', true);
|
||||||
|
|
||||||
|
if (event.recurrence && event.recurrence.BYDAY && event.recurrence.FREQ == 'WEEKLY') {
|
||||||
|
var wdays = event.recurrence.BYDAY.split(',');
|
||||||
|
$('input.edit-recurrence-weekly-byday').val(wdays);
|
||||||
|
}
|
||||||
|
else if (event.start) {
|
||||||
|
$('input.edit-recurrence-weekly-byday').val([weekdays[event.start.getDay()]]);
|
||||||
|
}
|
||||||
|
if (event.recurrence && event.recurrence.BYMONTHDAY) {
|
||||||
|
$('input.edit-recurrence-monthly-bymonthday').val(String(event.recurrence.BYMONTHDAY).split(','));
|
||||||
|
$('input.edit-recurrence-monthly-mode').val(['BYMONTHDAY']);
|
||||||
|
}
|
||||||
|
else if (event.start) {
|
||||||
|
$('input.edit-recurrence-monthly-bymonthday').val([event.start.getDate()]);
|
||||||
|
}
|
||||||
|
if (event.recurrence && event.recurrence.BYDAY && (event.recurrence.FREQ == 'MONTHLY' || event.recurrence.FREQ == 'YEARLY')) {
|
||||||
|
var byday, section = event.recurrence.FREQ.toLowerCase();
|
||||||
|
if ((byday = String(event.recurrence.BYDAY).match(/(-?[1-4])([A-Z]+)/))) {
|
||||||
|
$('#edit-recurrence-'+section+'-prefix').val(byday[1]);
|
||||||
|
$('#edit-recurrence-'+section+'-byday').val(byday[2]);
|
||||||
|
}
|
||||||
|
$('input.edit-recurrence-'+section+'-mode').val(['BYDAY']);
|
||||||
|
}
|
||||||
|
else if (event.start) {
|
||||||
|
$('#edit-recurrence-monthly-byday').val(weekdays[event.start.getDay()]);
|
||||||
|
}
|
||||||
|
if (event.recurrence && event.recurrence.BYMONTH) {
|
||||||
|
$('input.edit-recurrence-yearly-bymonth').val(String(event.recurrence.BYMONTH).split(','));
|
||||||
|
}
|
||||||
|
else if (event.start) {
|
||||||
|
$('input.edit-recurrence-yearly-bymonth').val([String(event.start.getMonth()+1)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// buttons
|
||||||
|
var buttons = {};
|
||||||
|
|
||||||
|
buttons[rcmail.gettext('save', 'calendar')] = function() {
|
||||||
|
var start = me.parse_datetime(starttime.val(), startdate.val());
|
||||||
|
var end = me.parse_datetime(endtime.val(), enddate.val());
|
||||||
|
|
||||||
|
// post data to server
|
||||||
|
var data = {
|
||||||
|
action: action,
|
||||||
|
start: start.getTime()/1000,
|
||||||
|
end: end.getTime()/1000,
|
||||||
|
allday: allday.checked?1:0,
|
||||||
|
title: title.val(),
|
||||||
|
description: description.val(),
|
||||||
|
location: location.val(),
|
||||||
|
categories: categories.val(),
|
||||||
|
free_busy: freebusy.val(),
|
||||||
|
priority: priority.val(),
|
||||||
|
recurrence: '',
|
||||||
|
alarms:'',
|
||||||
|
};
|
||||||
|
|
||||||
|
// serialize alarm settings
|
||||||
|
// TODO: support multiple alarm entries
|
||||||
|
var alarm = $('select.edit-alarm-type').val();
|
||||||
|
if (alarm) {
|
||||||
|
var val, offset = $('select.edit-alarm-offset').val();
|
||||||
|
if (offset == '@')
|
||||||
|
data.alarms = alarm + ':@' + (me.parse_datetime($('input.edit-alarm-time').val(), $('input.edit-alarm-date').val()).getTime()/1000);
|
||||||
|
else if ((val = parseInt($('input.edit-alarm-value').val())) && !isNaN(val) && val >= 0)
|
||||||
|
data.alarms = alarm + ':' + offset[0] + val + offset[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// gather recurrence settings
|
||||||
|
var freq;
|
||||||
|
if ((freq = recurrence.val()) != '') {
|
||||||
|
data.recurrence = {
|
||||||
|
FREQ: freq,
|
||||||
|
INTERVAL: $('#edit-recurrence-interval-'+freq.toLowerCase()).val()
|
||||||
|
};
|
||||||
|
|
||||||
|
var until = $('input.edit-recurrence-until:checked').val();
|
||||||
|
if (until == 'count')
|
||||||
|
data.recurrence.COUNT = rrtimes.val();
|
||||||
|
else if (until == 'until')
|
||||||
|
data.recurrence.UNTIL = me.parse_datetime(endtime.val(), rrenddate.val()).getTime()/1000;
|
||||||
|
|
||||||
|
if (freq == 'WEEKLY') {
|
||||||
|
var byday = [];
|
||||||
|
$('input.edit-recurrence-weekly-byday:checked').each(function(){ byday.push(this.value); });
|
||||||
|
data.recurrence.BYDAY = byday.join(',');
|
||||||
|
}
|
||||||
|
else if (freq == 'MONTHLY') {
|
||||||
|
var mode = $('input.edit-recurrence-monthly-mode:checked').val(), bymonday = [];
|
||||||
|
if (mode == 'BYMONTHDAY') {
|
||||||
|
$('input.edit-recurrence-monthly-bymonthday:checked').each(function(){ bymonday.push(this.value); });
|
||||||
|
data.recurrence.BYMONTHDAY = bymonday.join(',');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
data.recurrence.BYDAY = $('#edit-recurrence-monthly-prefix').val() + $('#edit-recurrence-monthly-byday').val();
|
||||||
|
}
|
||||||
|
else if (freq == 'YEARLY') {
|
||||||
|
var byday, bymonth = [];
|
||||||
|
$('input.edit-recurrence-yearly-bymonth:checked').each(function(){ bymonth.push(this.value); });
|
||||||
|
data.recurrence.BYMONTH = bymonth.join(',');
|
||||||
|
if ((byday = $('#edit-recurrence-yearly-byday').val()))
|
||||||
|
data.recurrence.BYDAY = $('#edit-recurrence-yearly-prefix').val() + byday;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.id)
|
||||||
|
data.id = event.id;
|
||||||
|
else
|
||||||
|
data.calendar = calendars.val();
|
||||||
|
|
||||||
|
rcmail.http_post('plugin.event', { e:data });
|
||||||
|
$dialog.dialog("close");
|
||||||
|
};
|
||||||
|
|
||||||
|
if (event.id) {
|
||||||
|
buttons[rcmail.gettext('remove', 'calendar')] = function() {
|
||||||
|
me.delete_event(event);
|
||||||
|
$dialog.dialog('close');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons[rcmail.gettext('cancel', 'calendar')] = function() {
|
||||||
|
$dialog.dialog("close");
|
||||||
|
};
|
||||||
|
|
||||||
|
// show/hide tabs according to calendar's feature support
|
||||||
|
$('#edit-tab-attendees')[(calendar.attendees?'show':'hide')]();
|
||||||
|
$('#edit-tab-attachments')[(calendar.attachments?'show':'hide')]();
|
||||||
|
|
||||||
|
// activate the first tab
|
||||||
|
$('#eventtabs').tabs('select', 0);
|
||||||
|
|
||||||
|
// open jquery UI dialog
|
||||||
|
$dialog.dialog({
|
||||||
|
modal: true,
|
||||||
|
resizable: true,
|
||||||
|
title: rcmail.gettext((action == 'edit' ? 'edit_event' : 'new_event'), 'calendar'),
|
||||||
|
close: function() {
|
||||||
|
$dialog.dialog("destroy");
|
||||||
|
$dialog.hide();
|
||||||
|
},
|
||||||
|
buttons: buttons,
|
||||||
|
minWidth: 440,
|
||||||
|
width: 480
|
||||||
|
}).show();
|
||||||
|
|
||||||
|
title.select();
|
||||||
|
};
|
||||||
|
|
||||||
|
// mouse-click handler to check if the show dialog is still open and prevent default action
|
||||||
|
var dialog_check = function(e) {
|
||||||
|
var showd = $("#eventshow");
|
||||||
|
if (showd.is(':visible') && !$(e.target).closest('.ui-dialog').length) {
|
||||||
|
showd.dialog('close');
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
ignore_click = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (ignore_click) {
|
||||||
|
window.setTimeout(function(){ ignore_click = false; }, 20);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// general datepicker settings
|
||||||
|
this.datepicker_settings = {
|
||||||
|
// translate from fullcalendar format to datepicker format
|
||||||
|
dateFormat: settings['date_format'].replace(/M/g, 'm').replace(/mmmmm/, 'MM').replace(/mmm/, 'M').replace(/dddd/, 'DD').replace(/ddd/, 'D').replace(/yy/g, 'y'),
|
||||||
|
firstDay : settings['first_day'],
|
||||||
|
dayNamesMin: settings['days_short'],
|
||||||
|
monthNames: settings['months'],
|
||||||
|
monthNamesShort: settings['months'],
|
||||||
|
changeMonth: false,
|
||||||
|
showOtherMonths: true,
|
||||||
|
selectOtherMonths: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// from time and date strings to a real date object
|
||||||
|
this.parse_datetime = function(time, date) {
|
||||||
|
// we use the utility function from datepicker to parse dates
|
||||||
|
var date = $.datepicker.parseDate(me.datepicker_settings.dateFormat, date, me.datepicker_settings);
|
||||||
|
var time_arr = time.split(/[:.]/);
|
||||||
|
if (!isNaN(time_arr[0])) date.setHours(time_arr[0]);
|
||||||
|
if (!isNaN(time_arr[1])) date.setMinutes(time_arr[1]);
|
||||||
|
return date;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// public method to bring up the new event dialog
|
||||||
|
this.add_event = function() {
|
||||||
|
if (this.selected_calendar) {
|
||||||
|
var now = new Date();
|
||||||
|
var date = $('#calendar').fullCalendar('getDate') || now;
|
||||||
|
date.setHours(now.getHours()+1);
|
||||||
|
date.setMinutes(0);
|
||||||
|
var end = new Date(date.getTime());
|
||||||
|
end.setHours(date.getHours()+1);
|
||||||
|
event_edit_dialog('new', { start:date, end:end, allDay:false, calendar:this.selected_calendar });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// delete the given event after showing a confirmation dialog
|
||||||
|
this.delete_event = function(event) {
|
||||||
|
// send remove request to plugin
|
||||||
|
if (confirm(rcmail.gettext('deleteventconfirm', 'calendar'))) {
|
||||||
|
rcmail.http_post('plugin.event', { e:{ action:'remove', id:event.id } });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// create list of event sources AKA calendars
|
||||||
|
this.calendars = {};
|
||||||
|
var li, cal, event_sources = [];
|
||||||
|
for (var id in rcmail.env.calendars) {
|
||||||
|
cal = rcmail.env.calendars[id];
|
||||||
|
this.calendars[id] = $.extend({
|
||||||
|
events: function(start, end, callback) { load_events(id, start, end, callback); },
|
||||||
|
editable: !cal.readonly,
|
||||||
|
className: 'fc-event-cal-'+id,
|
||||||
|
id: id
|
||||||
|
}, cal);
|
||||||
|
event_sources.push(this.calendars[id]);
|
||||||
|
|
||||||
|
// init event handler on calendar list checkbox
|
||||||
|
if ((li = rcmail.get_folder_li(id, 'rcmlical'))) {
|
||||||
|
$('#'+li.id+' input').click(function(e){
|
||||||
|
var id = $(this).data('id');
|
||||||
|
if (me.calendars[id]) { // add or remove event source on click
|
||||||
|
var action = this.checked ? 'addEventSource' : 'removeEventSource';
|
||||||
|
$('#calendar').fullCalendar(action, me.calendars[id]);
|
||||||
|
}
|
||||||
|
}).data('id', id);
|
||||||
|
$(li).click(function(e){
|
||||||
|
rcmail.select_folder(id, me.selected_calendar, 'rcmlical');
|
||||||
|
me.selected_calendar = $(this).data('id');
|
||||||
|
}).data('id', id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cal.readonly) {
|
||||||
|
this.selected_calendar = id;
|
||||||
|
rcmail.enable_command('plugin.addevent', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initalize the fullCalendar plugin
|
||||||
|
$('#calendar').fullCalendar({
|
||||||
|
header: {
|
||||||
|
left: 'prev,next today',
|
||||||
|
center: 'title',
|
||||||
|
right: 'agendaDay,agendaWeek,month'
|
||||||
|
},
|
||||||
|
aspectRatio: 1,
|
||||||
|
height: $(window).height() - 95,
|
||||||
|
eventSources: event_sources,
|
||||||
|
monthNames : settings['months'],
|
||||||
|
monthNamesShort : settings['months_short'],
|
||||||
|
dayNames : settings['days'],
|
||||||
|
dayNamesShort : settings['days_short'],
|
||||||
|
firstDay : settings['first_day'],
|
||||||
|
firstHour : settings['first_hour'],
|
||||||
|
slotMinutes : 60/settings['timeslots'],
|
||||||
|
timeFormat: settings['time_format'],
|
||||||
|
axisFormat : settings['time_format'],
|
||||||
|
columnFormat: {
|
||||||
|
month: 'ddd', // Mon
|
||||||
|
week: 'ddd ' + settings['date_short'], // Mon 9/7
|
||||||
|
day: 'dddd ' + settings['date_short'] // Monday 9/7
|
||||||
|
},
|
||||||
|
defaultView: settings['default_view'],
|
||||||
|
allDayText: rcmail.gettext('all-day', 'calendar'),
|
||||||
|
buttonText: {
|
||||||
|
today: settings['today'],
|
||||||
|
day: rcmail.gettext('day', 'calendar'),
|
||||||
|
week: rcmail.gettext('week', 'calendar'),
|
||||||
|
month: rcmail.gettext('month', 'calendar')
|
||||||
|
},
|
||||||
|
selectable: true,
|
||||||
|
selectHelper: true,
|
||||||
|
loading : function(isLoading) {
|
||||||
|
this._rc_loading = rcmail.set_busy(isLoading, 'loading', this._rc_loading);
|
||||||
|
},
|
||||||
|
// event rendering
|
||||||
|
eventRender: function(event, element, view) {
|
||||||
|
if(view.name != "month") {
|
||||||
|
if (event.categories) {
|
||||||
|
if(!event.allDay)
|
||||||
|
element.find('span.fc-event-title').after('<span class="fc-event-categories">' + event.categories + '</span>');
|
||||||
|
}
|
||||||
|
if (event.location) {
|
||||||
|
element.find('span.fc-event-title').after('<span class="fc-event-location">@' + event.location + '</span>');
|
||||||
|
}
|
||||||
|
if (event.description) {
|
||||||
|
if (!event.allDay){
|
||||||
|
element.find('span.fc-event-title').after('<span class="fc-event-description">' + event.description + '</span>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// callback for date range selection
|
||||||
|
select: function(start, end, allDay, e, view) {
|
||||||
|
var range_select = (!allDay || start.getDate() != end.getDate())
|
||||||
|
if (dialog_check(e) && range_select)
|
||||||
|
event_edit_dialog('new', { start:start, end:end, allDay:allDay, calendar:me.selected_calendar });
|
||||||
|
if (range_select || ignore_click)
|
||||||
|
view.calendar.unselect();
|
||||||
|
},
|
||||||
|
// callback for clicks in all-day box
|
||||||
|
dayClick: function(date, allDay, e, view) {
|
||||||
|
var now = new Date().getTime();
|
||||||
|
if (now - day_clicked < 400) // emulate double-click on day
|
||||||
|
event_edit_dialog('new', { start:date, end:date, allDay:allDay, calendar:me.selected_calendar });
|
||||||
|
day_clicked = now;
|
||||||
|
if (!ignore_click) {
|
||||||
|
view.calendar.gotoDate(date);
|
||||||
|
fullcalendar_update();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// callback when a specific event is clicked
|
||||||
|
eventClick : function(event) {
|
||||||
|
event_show_dialog(event);
|
||||||
|
},
|
||||||
|
// callback when an event was dragged and finally dropped
|
||||||
|
eventDrop: function(event, dayDelta, minuteDelta, allDay, revertFunc) {
|
||||||
|
if (event.end == null) {
|
||||||
|
event.end = event.start;
|
||||||
|
}
|
||||||
|
// send move request to server
|
||||||
|
var data = {
|
||||||
|
action: 'move',
|
||||||
|
id: event.id,
|
||||||
|
start: event.start.getTime()/1000,
|
||||||
|
end: event.end.getTime()/1000,
|
||||||
|
allday: allDay?1:0
|
||||||
|
};
|
||||||
|
rcmail.http_post('plugin.event', { e:data });
|
||||||
|
},
|
||||||
|
// callback for event resizing
|
||||||
|
eventResize : function(event, delta) {
|
||||||
|
// send resize request to server
|
||||||
|
var data = {
|
||||||
|
action: 'resize',
|
||||||
|
id: event.id,
|
||||||
|
start: event.start.getTime()/1000,
|
||||||
|
end: event.end.getTime()/1000,
|
||||||
|
};
|
||||||
|
rcmail.http_post('plugin.event', { e:data });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// event handler for clicks on calendar week cell of the datepicker widget
|
||||||
|
var init_week_events = function(){
|
||||||
|
$('#datepicker table.ui-datepicker-calendar td.ui-datepicker-week-col').click(function(e){
|
||||||
|
var base_date = $("#datepicker").datepicker('getDate');
|
||||||
|
var day_off = base_date.getDay() - 1;
|
||||||
|
if (day_off < 0) day_off = 6;
|
||||||
|
var base_kw = $.datepicker.iso8601Week(base_date);
|
||||||
|
var kw = parseInt($(this).html());
|
||||||
|
var diff = (kw - base_kw) * 7 * 86400000;
|
||||||
|
// select monday of the chosen calendar week
|
||||||
|
var date = new Date(base_date.getTime() - day_off * 86400000 + diff);
|
||||||
|
$('#calendar').fullCalendar('gotoDate', date).fullCalendar('setDate', date).fullCalendar('changeView', 'agendaWeek');
|
||||||
|
$("#datepicker").datepicker('setDate', date);
|
||||||
|
window.setTimeout(init_week_events, 10);
|
||||||
|
}).css('cursor', 'pointer');
|
||||||
|
};
|
||||||
|
|
||||||
|
// initialize small calendar widget using jQuery UI datepicker
|
||||||
|
$('#datepicker').datepicker($.extend(this.datepicker_settings, {
|
||||||
|
inline: true,
|
||||||
|
showWeek: true,
|
||||||
|
changeMonth: false, // maybe enable?
|
||||||
|
changeYear: false, // maybe enable?
|
||||||
|
onSelect: function(dateText, inst) {
|
||||||
|
ignore_click = true;
|
||||||
|
var d = $("#datepicker").datepicker('getDate'); //parse_datetime('0:0', dateText);
|
||||||
|
$('#calendar').fullCalendar('gotoDate', d).fullCalendar('select', d, d, true);
|
||||||
|
window.setTimeout(init_week_events, 10);
|
||||||
|
},
|
||||||
|
onChangeMonthYear: function(year, month, inst) {
|
||||||
|
window.setTimeout(init_week_events, 10);
|
||||||
|
var d = $("#datepicker").datepicker('getDate');
|
||||||
|
d.setYear(year);
|
||||||
|
d.setMonth(month - 1);
|
||||||
|
$("#datepicker").data('year', year).data('month', month);
|
||||||
|
//$('#calendar').fullCalendar('gotoDate', d).fullCalendar('setDate', d);
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
window.setTimeout(init_week_events, 10);
|
||||||
|
|
||||||
|
// react on fullcalendar buttons
|
||||||
|
var fullcalendar_update = function() {
|
||||||
|
var d = $('#calendar').fullCalendar('getDate');
|
||||||
|
$("#datepicker").datepicker('setDate', d);
|
||||||
|
window.setTimeout(init_week_events, 10);
|
||||||
|
};
|
||||||
|
$("#calendar .fc-button-prev").click(fullcalendar_update);
|
||||||
|
$("#calendar .fc-button-next").click(fullcalendar_update);
|
||||||
|
$("#calendar .fc-button-today").click(fullcalendar_update);
|
||||||
|
|
||||||
|
// hide event dialog when clicking somewhere into document
|
||||||
|
$(document).bind('mousedown', dialog_check);
|
||||||
|
|
||||||
|
} // end rcube_calendar class
|
||||||
|
|
||||||
|
|
||||||
|
// configure toobar buttons
|
||||||
|
rcmail.register_command('plugin.addevent', function(){ cal.add_event(); }, true);
|
||||||
|
|
||||||
|
// export events
|
||||||
|
rcmail.register_command('plugin.export', function(){ rcmail.goto_url('plugin.export_events', { source:cal.selected_calendar }); }, true);
|
||||||
|
rcmail.enable_command('plugin.export', true);
|
||||||
|
|
||||||
|
// reload calendar
|
||||||
|
rcmail.addEventListener('plugin.reload_calendar', reload_calendar);
|
||||||
|
function reload_calendar() {
|
||||||
|
$('#calendar').fullCalendar('refetchEvents');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var formattime = function(hour, minutes) {
|
||||||
|
return ((hour < 10) ? "0" : "") + hour + ((minutes < 10) ? ":0" : ":") + minutes;
|
||||||
|
};
|
||||||
|
|
||||||
|
// if start date is changed, shift end date according to initial duration
|
||||||
|
var shift_enddate = function(dateText) {
|
||||||
|
var newstart = cal.parse_datetime('0', dateText);
|
||||||
|
var newend = new Date(newstart.getTime() + $('#edit-startdate').data('duration') * 1000);
|
||||||
|
$('#edit-enddate').val($.fullCalendar.formatDate(newend, cal.settings['date_format']));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// let's go
|
||||||
|
var cal = new rcube_calendar(rcmail.env.calendar_settings);
|
||||||
|
|
||||||
|
$(window).resize(function() {
|
||||||
|
$('#calendar').fullCalendar('option', 'height', $(window).height() - 95);
|
||||||
|
}).resize();
|
||||||
|
|
||||||
|
// show toolbar
|
||||||
|
$('#toolbar').show();
|
||||||
|
|
||||||
|
// init event dialog
|
||||||
|
$('#eventtabs').tabs();
|
||||||
|
$('#edit-enddate, input.edit-alarm-date').datepicker(cal.datepicker_settings);
|
||||||
|
$('#edit-startdate').datepicker(cal.datepicker_settings).datepicker('option', 'onSelect', shift_enddate).change(function(){ shift_enddate(this.value); });
|
||||||
|
$('#edit-allday').click(function(){ $('#edit-starttime, #edit-endtime')[(this.checked?'hide':'show')](); });
|
||||||
|
|
||||||
|
// configure drop-down menu on time input fields based on jquery UI autocomplete
|
||||||
|
$('#edit-starttime, #edit-endtime, input.edit-alarm-time')
|
||||||
|
.attr('autocomplete', "off")
|
||||||
|
.autocomplete({
|
||||||
|
delay: 100,
|
||||||
|
minLength: 1,
|
||||||
|
source: function(p, callback) {
|
||||||
|
/* Time completions */
|
||||||
|
var result = [];
|
||||||
|
var now = new Date();
|
||||||
|
var full = p.term - 1 > 0 || p.term.length > 1;
|
||||||
|
var hours = full? p.term - 0 : now.getHours();
|
||||||
|
var step = 15;
|
||||||
|
var minutes = hours * 60 + (full ? 0 : now.getMinutes());
|
||||||
|
var min = Math.ceil(minutes / step) * step % 60;
|
||||||
|
var hour = Math.floor(Math.ceil(minutes / step) * step / 60);
|
||||||
|
// list hours from 0:00 till now
|
||||||
|
for (var h = 0; h < hours; h++)
|
||||||
|
result.push(formattime(h, 0));
|
||||||
|
// list 15min steps for the next two hours
|
||||||
|
for (; h < hour + 2; h++) {
|
||||||
|
while (min < 60) {
|
||||||
|
result.push(formattime(h, min));
|
||||||
|
min += step;
|
||||||
|
}
|
||||||
|
min = 0;
|
||||||
|
}
|
||||||
|
// list the remaining hours till 23:00
|
||||||
|
while (h < 24)
|
||||||
|
result.push(formattime((h++), 0));
|
||||||
|
return callback(result);
|
||||||
|
},
|
||||||
|
open: function(event, ui) {
|
||||||
|
// scroll to current time
|
||||||
|
var widget = $(this).autocomplete('widget');
|
||||||
|
var menu = $(this).data('autocomplete').menu;
|
||||||
|
var val = $(this).val();
|
||||||
|
var li, html, offset = 0;
|
||||||
|
widget.children().each(function(){
|
||||||
|
li = $(this);
|
||||||
|
html = li.children().first().html();
|
||||||
|
if (html < val)
|
||||||
|
offset += li.height();
|
||||||
|
if (html == val)
|
||||||
|
menu.activate($.Event({ type: 'mouseenter' }), li);
|
||||||
|
});
|
||||||
|
widget.scrollTop(offset - 1);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.click(function() { // show drop-down upon clicks
|
||||||
|
$(this).autocomplete('search', $(this).val() ? $(this).val().replace(/\D.*/, "") : " ");
|
||||||
|
});
|
||||||
|
|
||||||
|
// register events on alarm fields
|
||||||
|
$('select.edit-alarm-type').change(function(){
|
||||||
|
$(this).parent().find('span.edit-alarm-values')[(this.selectedIndex>0?'show':'hide')]();
|
||||||
|
});
|
||||||
|
$('select.edit-alarm-offset').change(function(){
|
||||||
|
var mode = $(this).val() == '@' ? 'show' : 'hide';
|
||||||
|
$(this).parent().find('.edit-alarm-date, .edit-alarm-time')[mode]();
|
||||||
|
$(this).parent().find('.edit-alarm-value').prop('disabled', mode == 'show');
|
||||||
|
});
|
||||||
|
|
||||||
|
// toggle recurrence frequency forms
|
||||||
|
$('#edit-recurrence-frequency').change(function(e){
|
||||||
|
var freq = $(this).val().toLowerCase();
|
||||||
|
$('.recurrence-form').hide();
|
||||||
|
if (freq)
|
||||||
|
$('#recurrence-form-'+freq+', #recurrence-form-until').show();
|
||||||
|
});
|
||||||
|
$('#edit-recurrence-enddate').datepicker(cal.datepicker_settings).click(function(){ $("#edit-recurrence-repeat-until").prop('checked', true) });
|
||||||
|
|
||||||
|
// avoid unselecting all weekdays, monthdays and months
|
||||||
|
$('input.edit-recurrence-weekly-byday, input.edit-recurrence-monthly-bymonthday, input.edit-recurrence-yearly-bymonth').click(function(){
|
||||||
|
if (!$('input.'+this.className+':checked').length)
|
||||||
|
this.checked = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// initialize sidebar toggle
|
||||||
|
$('#sidebartoggle').click(function() {
|
||||||
|
var width = $(this).data('sidebarwidth');
|
||||||
|
var offset = $(this).data('offset');
|
||||||
|
var $sidebar = $('#sidebar'), time = 250;
|
||||||
|
|
||||||
|
if ($sidebar.is(':visible')) {
|
||||||
|
$sidebar.animate({ left:'-'+(width+10)+'px' }, time, function(){ $('#sidebar').hide(); });
|
||||||
|
$(this).animate({ left:'6px'}, time, function(){ $('#sidebartoggle').addClass('sidebarclosed') });
|
||||||
|
$('#calendar').animate({ left:'20px'}, time, function(){ $(this).fullCalendar('render'); });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$sidebar.show().animate({ left:'10px' }, time);
|
||||||
|
$(this).animate({ left:offset+'px'}, time, function(){ $('#sidebartoggle').removeClass('sidebarclosed'); });
|
||||||
|
$('#calendar').animate({ left:(width+20)+'px'}, time, function(){ $(this).fullCalendar('render'); });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.data('offset', $('#sidebartoggle').position().left)
|
||||||
|
.data('sidebarwidth', $('#sidebar').width() + $('#sidebar').position().left);
|
||||||
|
|
||||||
|
});
|
450
plugins/calendar/calendar.php
Normal file
|
@ -0,0 +1,450 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Calendar plugin for Roundcube |
|
||||||
|
| Version 0.3 beta |
|
||||||
|
| |
|
||||||
|
| This program is free software; you can redistribute it and/or modify |
|
||||||
|
| it under the terms of the GNU General Public License version 2 |
|
||||||
|
| as published by the Free Software Foundation. |
|
||||||
|
| |
|
||||||
|
| 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 General Public License for more details. |
|
||||||
|
| |
|
||||||
|
| You should have received a copy of the GNU General Public License along |
|
||||||
|
| with this program; if not, write to the Free Software Foundation, Inc., |
|
||||||
|
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
|
||||||
|
| |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Author: Lazlo Westerhof <hello@lazlo.me> |
|
||||||
|
| Thomas Bruederli <roundcube@gmail.com> |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
class calendar extends rcube_plugin
|
||||||
|
{
|
||||||
|
public $task = '?(?!login|logout).*';
|
||||||
|
public $rc;
|
||||||
|
public $driver;
|
||||||
|
|
||||||
|
public $ical;
|
||||||
|
public $ui;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin initialization.
|
||||||
|
*/
|
||||||
|
function init()
|
||||||
|
{
|
||||||
|
$this->rc = rcmail::get_instance();
|
||||||
|
|
||||||
|
$this->register_task('calendar', 'calendar');
|
||||||
|
|
||||||
|
// load calendar configuration
|
||||||
|
if(file_exists($this->home . "/config.inc.php")) {
|
||||||
|
$this->load_config('config.inc.php');
|
||||||
|
} else {
|
||||||
|
$this->load_config('config.inc.php.dist');
|
||||||
|
}
|
||||||
|
|
||||||
|
// load localizations
|
||||||
|
$this->add_texts('localization/', true);
|
||||||
|
|
||||||
|
// load Calendar user interface which includes jquery-ui
|
||||||
|
$this->require_plugin('jqueryui');
|
||||||
|
|
||||||
|
require('lib/calendar_ui.php');
|
||||||
|
$this->ui = new calendar_ui($this);
|
||||||
|
$this->ui->init();
|
||||||
|
|
||||||
|
$skin = $this->rc->config->get('skin');
|
||||||
|
$this->include_stylesheet('skins/' . $skin . '/calendar.css');
|
||||||
|
|
||||||
|
if ($this->rc->task == 'calendar') {
|
||||||
|
$this->load_driver();
|
||||||
|
|
||||||
|
// load iCalendar functions
|
||||||
|
require('lib/calendar_ical.php');
|
||||||
|
$this->ical = new calendar_ical($this->rc, $this->driver);
|
||||||
|
|
||||||
|
// register calendar actions
|
||||||
|
$this->register_action('index', array($this, 'calendar_view'));
|
||||||
|
$this->register_action('plugin.calendar', array($this, 'calendar_view'));
|
||||||
|
$this->register_action('plugin.load_events', array($this, 'load_events'));
|
||||||
|
$this->register_action('plugin.event', array($this, 'event'));
|
||||||
|
$this->register_action('plugin.export_events', array($this, 'export_events'));
|
||||||
|
|
||||||
|
// set user's timezone
|
||||||
|
if ($this->rc->config->get('timezone') === 'auto')
|
||||||
|
$this->timezone = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z');
|
||||||
|
else
|
||||||
|
$this->timezone = ($this->rc->config->get('timezone') + intval($this->rc->config->get('dst_active')));
|
||||||
|
|
||||||
|
$this->gmt_offset = $this->timezone * 3600;
|
||||||
|
}
|
||||||
|
else if ($this->rc->task == 'settings') {
|
||||||
|
$this->load_driver();
|
||||||
|
|
||||||
|
// add hooks for Calendar settings
|
||||||
|
$this->add_hook('preferences_sections_list', array($this, 'preferences_sections_list'));
|
||||||
|
$this->add_hook('preferences_list', array($this, 'preferences_list'));
|
||||||
|
$this->add_hook('preferences_save', array($this, 'preferences_save'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function load_driver()
|
||||||
|
{
|
||||||
|
$driver_name = $this->rc->config->get('calendar_driver', 'database');
|
||||||
|
$driver_class = $driver_name . '_driver';
|
||||||
|
|
||||||
|
require_once('drivers/calendar_driver.php');
|
||||||
|
require_once('drivers/' . $driver_name . '/' . $driver_class . '.php');
|
||||||
|
|
||||||
|
switch ($driver_name) {
|
||||||
|
case "kolab":
|
||||||
|
$this->require_plugin('kolab_core');
|
||||||
|
default:
|
||||||
|
$this->driver = new $driver_class($this);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calendar_view()
|
||||||
|
{
|
||||||
|
$this->rc->output->set_pagetitle($this->gettext('calendar'));
|
||||||
|
|
||||||
|
// Add CSS stylesheets to the page header
|
||||||
|
$this->ui->addCSS();
|
||||||
|
|
||||||
|
// Add JS files to the page header
|
||||||
|
$this->ui->addJS();
|
||||||
|
|
||||||
|
$this->register_handler('plugin.calendar_css', array($this->ui, 'calendar_css'));
|
||||||
|
$this->register_handler('plugin.calendar_list', array($this->ui, 'calendar_list'));
|
||||||
|
$this->register_handler('plugin.calendar_select', array($this->ui, 'calendar_select'));
|
||||||
|
$this->register_handler('plugin.category_select', array($this->ui, 'category_select'));
|
||||||
|
$this->register_handler('plugin.freebusy_select', array($this->ui, 'freebusy_select'));
|
||||||
|
$this->register_handler('plugin.priority_select', array($this->ui, 'priority_select'));
|
||||||
|
$this->register_handler('plugin.alarm_select', array($this->ui, 'alarm_select'));
|
||||||
|
$this->register_handler('plugin.recurrence_form', array($this->ui, 'recurrence_form'));
|
||||||
|
|
||||||
|
$this->rc->output->set_env('calendar_settings', $this->load_settings());
|
||||||
|
$this->rc->output->add_label('low','normal','high');
|
||||||
|
|
||||||
|
$this->rc->output->send("calendar.calendar");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for preferences_sections_list hook.
|
||||||
|
* Adds Calendar settings sections into preferences sections list.
|
||||||
|
*
|
||||||
|
* @param array Original parameters
|
||||||
|
* @return array Modified parameters
|
||||||
|
*/
|
||||||
|
function preferences_sections_list($p)
|
||||||
|
{
|
||||||
|
$p['list']['calendar'] = array(
|
||||||
|
'id' => 'calendar', 'section' => $this->gettext('calendar'),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for preferences_list hook.
|
||||||
|
* Adds options blocks into Calendar settings sections in Preferences.
|
||||||
|
*
|
||||||
|
* @param array Original parameters
|
||||||
|
* @return array Modified parameters
|
||||||
|
*/
|
||||||
|
function preferences_list($p)
|
||||||
|
{
|
||||||
|
if ($p['section'] == 'calendar') {
|
||||||
|
$p['blocks']['view']['name'] = $this->gettext('mainoptions');
|
||||||
|
|
||||||
|
$field_id = 'rcmfd_default_view';
|
||||||
|
$select = new html_select(array('name' => '_default_view', 'id' => $field_id));
|
||||||
|
$select->add($this->gettext('day'), "agendaDay");
|
||||||
|
$select->add($this->gettext('week'), "agendaWeek");
|
||||||
|
$select->add($this->gettext('month'), "month");
|
||||||
|
$p['blocks']['view']['options']['default_view'] = array(
|
||||||
|
'title' => html::label($field_id, Q($this->gettext('default_view'))),
|
||||||
|
'content' => $select->show($this->rc->config->get('calendar_default_view', "agendaWeek")),
|
||||||
|
);
|
||||||
|
|
||||||
|
$field_id = 'rcmfd_time_format';
|
||||||
|
$choices = array('HH:mm', 'H:mm', 'h:mmt');
|
||||||
|
$select = new html_select(array('name' => '_time_format', 'id' => $field_id));
|
||||||
|
$select->add($choices);
|
||||||
|
$p['blocks']['view']['options']['time_format'] = array(
|
||||||
|
'title' => html::label($field_id, Q($this->gettext('time_format'))),
|
||||||
|
'content' => $select->show($this->rc->config->get('calendar_time_format', "HH:mm")),
|
||||||
|
);
|
||||||
|
|
||||||
|
$field_id = 'rcmfd_timeslot';
|
||||||
|
$choices = array('1', '2', '3', '4', '6');
|
||||||
|
$select = new html_select(array('name' => '_timeslots', 'id' => $field_id));
|
||||||
|
$select->add($choices);
|
||||||
|
$p['blocks']['view']['options']['timeslots'] = array(
|
||||||
|
'title' => html::label($field_id, Q($this->gettext('timeslots'))),
|
||||||
|
'content' => $select->show($this->rc->config->get('calendar_timeslots', 2)),
|
||||||
|
);
|
||||||
|
|
||||||
|
$field_id = 'rcmfd_timeslot';
|
||||||
|
$select = new html_select(array('name' => '_first_day', 'id' => $field_id));
|
||||||
|
$select->add(rcube_label('sunday'), '0');
|
||||||
|
$select->add(rcube_label('monday'), '1');
|
||||||
|
$select->add(rcube_label('tuesday'), '2');
|
||||||
|
$select->add(rcube_label('wednesday'), '3');
|
||||||
|
$select->add(rcube_label('thursday'), '4');
|
||||||
|
$select->add(rcube_label('friday'), '5');
|
||||||
|
$select->add(rcube_label('saturday'), '6');
|
||||||
|
$p['blocks']['view']['options']['first_day'] = array(
|
||||||
|
'title' => html::label($field_id, Q($this->gettext('first_day'))),
|
||||||
|
'content' => $select->show($this->rc->config->get('calendar_first_day', 1)),
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// category definitions
|
||||||
|
$p['blocks']['categories']['name'] = $this->gettext('categories');
|
||||||
|
|
||||||
|
$categories = $this->rc->config->get('calendar_categories', array());
|
||||||
|
$categories_list = '';
|
||||||
|
foreach ($categories as $name => $color){
|
||||||
|
$key = md5($name);
|
||||||
|
$field_class = 'rcmfd_category_' . str_replace(' ', '_', $name);
|
||||||
|
$category_remove = new html_inputfield(array('type' => 'button', 'value' => 'X', 'class' => 'button', 'onclick' => '$(this).parent().remove()', 'title' => $this->gettext('remove_category')));
|
||||||
|
$category_name = new html_inputfield(array('name' => "_categories[$key]", 'class' => $field_class, 'size' => 30));
|
||||||
|
$category_color = new html_inputfield(array('name' => "_colors[$key]", 'class' => $field_class, 'size' => 6));
|
||||||
|
$categories_list .= html::div(null, $category_name->show($name) . ' ' . $category_color->show($color) . ' ' . $category_remove->show());
|
||||||
|
}
|
||||||
|
|
||||||
|
$p['blocks']['categories']['options']['category_' . $name] = array(
|
||||||
|
'content' => html::div(array('id' => 'calendarcategories'), $categories_list),
|
||||||
|
);
|
||||||
|
|
||||||
|
$field_id = 'rcmfd_new_category';
|
||||||
|
$new_category = new html_inputfield(array('name' => '_new_category', 'id' => $field_id, 'size' => 30));
|
||||||
|
$add_category = new html_inputfield(array('type' => 'button', 'class' => 'button', 'value' => $this->gettext('add_category'), 'onclick' => "rcube_calendar_add_category()"));
|
||||||
|
$p['blocks']['categories']['options']['categories'] = array(
|
||||||
|
'content' => $new_category->show('') . ' ' . $add_category->show(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->rc->output->add_script('function rcube_calendar_add_category(){
|
||||||
|
var name = $("#rcmfd_new_category").val();
|
||||||
|
if (name.length) {
|
||||||
|
var input = $("<input>").attr("type", "text").attr("name", "_categories[]").attr("size", 30).val(name);
|
||||||
|
var color = $("<input>").attr("type", "text").attr("name", "_colors[]").attr("size", 6).val("000000");
|
||||||
|
var button = $("<input>").attr("type", "button").attr("value", "X").addClass("button").click(function(){ $(this).parent().remove() });
|
||||||
|
$("<div>").append(input).append(" ").append(color).append(" ").append(button).appendTo("#calendarcategories");
|
||||||
|
}
|
||||||
|
}');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for preferences_save hook.
|
||||||
|
* Executed on Calendar settings form submit.
|
||||||
|
*
|
||||||
|
* @param array Original parameters
|
||||||
|
* @return array Modified parameters
|
||||||
|
*/
|
||||||
|
function preferences_save($p)
|
||||||
|
{
|
||||||
|
if ($p['section'] == 'calendar') {
|
||||||
|
// categories
|
||||||
|
$old_categories = $new_categories = array();
|
||||||
|
foreach ($this->driver->list_categories() as $name => $color) {
|
||||||
|
$old_categories[md5($name)] = $name;
|
||||||
|
}
|
||||||
|
$categories = get_input_value('_categories', RCUBE_INPUT_POST);
|
||||||
|
$colors = get_input_value('_colors', RCUBE_INPUT_POST);
|
||||||
|
foreach ($categories as $key => $name) {
|
||||||
|
$color = preg_replace('/^#/', '', strval($colors[$key]));
|
||||||
|
|
||||||
|
// rename categories in existing events -> driver's job
|
||||||
|
if ($oldname = $old_categories[$key]) {
|
||||||
|
$this->driver->replace_category($oldname, $name, $color);
|
||||||
|
unset($old_categories[$key]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$this->driver->add_category($name, $color);
|
||||||
|
|
||||||
|
$new_categories[$name] = $color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// these old categories have been removed, alter events accordingly -> driver's job
|
||||||
|
foreach ((array)$old_categories[$key] as $key => $name) {
|
||||||
|
$this->driver->remove_category($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
$p['prefs'] = array(
|
||||||
|
'calendar_default_view' => get_input_value('_default_view', RCUBE_INPUT_POST),
|
||||||
|
'calendar_time_format' => get_input_value('_time_format', RCUBE_INPUT_POST),
|
||||||
|
'calendar_timeslots' => get_input_value('_timeslots', RCUBE_INPUT_POST),
|
||||||
|
'calendar_first_day' => get_input_value('_first_day', RCUBE_INPUT_POST),
|
||||||
|
'calendar_categories' => $new_categories,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $p;
|
||||||
|
}
|
||||||
|
|
||||||
|
function event()
|
||||||
|
{
|
||||||
|
$event = get_input_value('e', RCUBE_INPUT_POST);
|
||||||
|
$success = false;
|
||||||
|
|
||||||
|
switch ($event['action']) {
|
||||||
|
case "new":
|
||||||
|
// create UID for new event
|
||||||
|
$events['uid'] = strtoupper(md5(time() . uniqid(rand())) . '-' . substr(md5($this->rc->user->get_username()), 0, 16));
|
||||||
|
$success = $this->driver->new_event($event);
|
||||||
|
break;
|
||||||
|
case "edit":
|
||||||
|
$success = $this->driver->edit_event($event);
|
||||||
|
break;
|
||||||
|
case "resize":
|
||||||
|
$success = $this->driver->resize_event($event);
|
||||||
|
break;
|
||||||
|
case "move":
|
||||||
|
$success = $this->driver->move_event($event);
|
||||||
|
break;
|
||||||
|
case "remove":
|
||||||
|
$success = $this->driver->remove_event($event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($success) {
|
||||||
|
$this->rc->output->command('plugin.reload_calendar', array());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->rc->output->show_message('calendar.errorsaving', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_events()
|
||||||
|
{
|
||||||
|
$events = $this->driver->load_events(get_input_value('start', RCUBE_INPUT_GET), get_input_value('end', RCUBE_INPUT_GET), get_input_value('source', RCUBE_INPUT_GET));
|
||||||
|
echo $this->encode($events);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function export_events()
|
||||||
|
{
|
||||||
|
$start = get_input_value('start', RCUBE_INPUT_GET);
|
||||||
|
$end = get_input_value('end', RCUBE_INPUT_GET);
|
||||||
|
if (!$start) $start = mktime(0, 0, 0, 1, date('n'), date('Y')-1);
|
||||||
|
if (!$end) $end = mktime(0, 0, 0, 31, 12, date('Y')+10);
|
||||||
|
$events = $this->driver->load_events($start, $end, get_input_value('source', RCUBE_INPUT_GET));
|
||||||
|
|
||||||
|
header("Content-Type: text/calendar");
|
||||||
|
header("Content-Disposition: inline; filename=calendar.ics");
|
||||||
|
|
||||||
|
echo $this->ical->export($events);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_settings()
|
||||||
|
{
|
||||||
|
$settings = array();
|
||||||
|
|
||||||
|
// configuration
|
||||||
|
$settings['default_view'] = (string)$this->rc->config->get('calendar_default_view', "agendaWeek");
|
||||||
|
$settings['date_format'] = (string)$this->rc->config->get('calendar_date_format', "yyyy/MM/dd");
|
||||||
|
$settings['date_short'] = (string)$this->rc->config->get('calendar_date_short', "M/d");
|
||||||
|
$settings['time_format'] = (string)$this->rc->config->get('calendar_time_format', "HH:mm");
|
||||||
|
$settings['timeslots'] = (int)$this->rc->config->get('calendar_timeslots', 2);
|
||||||
|
$settings['first_day'] = (int)$this->rc->config->get('calendar_first_day', 1);
|
||||||
|
$settings['first_hour'] = (int)$this->rc->config->get('calendar_first_hour', 6);
|
||||||
|
$settings['timezone'] = $this->timezone;
|
||||||
|
|
||||||
|
// localization
|
||||||
|
$settings['days'] = array(
|
||||||
|
rcube_label('sunday'), rcube_label('monday'),
|
||||||
|
rcube_label('tuesday'), rcube_label('wednesday'),
|
||||||
|
rcube_label('thursday'), rcube_label('friday'),
|
||||||
|
rcube_label('saturday')
|
||||||
|
);
|
||||||
|
$settings['days_short'] = array(
|
||||||
|
rcube_label('sun'), rcube_label('mon'),
|
||||||
|
rcube_label('tue'), rcube_label('wed'),
|
||||||
|
rcube_label('thu'), rcube_label('fri'),
|
||||||
|
rcube_label('sat')
|
||||||
|
);
|
||||||
|
$settings['months'] = array(
|
||||||
|
$this->rc->gettext('longjan'), $this->rc->gettext('longfeb'),
|
||||||
|
$this->rc->gettext('longmar'), $this->rc->gettext('longapr'),
|
||||||
|
$this->rc->gettext('longmay'), $this->rc->gettext('longjun'),
|
||||||
|
$this->rc->gettext('longjul'), $this->rc->gettext('longaug'),
|
||||||
|
$this->rc->gettext('longsep'), $this->rc->gettext('longoct'),
|
||||||
|
$this->rc->gettext('longnov'), $this->rc->gettext('longdec')
|
||||||
|
);
|
||||||
|
$settings['months_short'] = array(
|
||||||
|
$this->rc->gettext('jan'), $this->rc->gettext('feb'),
|
||||||
|
$this->rc->gettext('mar'), $this->rc->gettext('apr'),
|
||||||
|
$this->rc->gettext('may'), $this->rc->gettext('jun'),
|
||||||
|
$this->rc->gettext('jul'), $this->rc->gettext('aug'),
|
||||||
|
$this->rc->gettext('sep'), $this->rc->gettext('oct'),
|
||||||
|
$this->rc->gettext('nov'), $this->rc->gettext('dec')
|
||||||
|
);
|
||||||
|
$settings['today'] = rcube_label('today');
|
||||||
|
|
||||||
|
return $settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the given time stamp to a GMT date string
|
||||||
|
*/
|
||||||
|
function toGMT($time, $user_tz = true)
|
||||||
|
{
|
||||||
|
$tz = $user_tz ? $this->gmt_offset : date('Z');
|
||||||
|
return date('Y-m-d H:i:s', $time - $tz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shift the given time stamo to a GMT time zone
|
||||||
|
*/
|
||||||
|
function toGMTTS($time, $user_tz = true)
|
||||||
|
{
|
||||||
|
$tz = $user_tz ? $this->gmt_offset : date('Z');
|
||||||
|
return $time - $tz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the given date string into a GMT-based time stamp
|
||||||
|
*/
|
||||||
|
function fromGMT($datetime, $user_tz = true)
|
||||||
|
{
|
||||||
|
$tz = $user_tz ? $this->gmt_offset : date('Z');
|
||||||
|
return strtotime($datetime) + $tz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode events as JSON
|
||||||
|
*
|
||||||
|
* @param array Events as array
|
||||||
|
* @return string JSON encoded events
|
||||||
|
*/
|
||||||
|
function encode($events)
|
||||||
|
{
|
||||||
|
$json = array();
|
||||||
|
foreach ($events as $event) {
|
||||||
|
// TODO: compose a human readable string for recurrence_text
|
||||||
|
$json[] = array(
|
||||||
|
'start' => date('c', $event['start']), // ISO 8601 date (added in PHP 5)
|
||||||
|
'end' => date('c', $event['end']), // ISO 8601 date (added in PHP 5)
|
||||||
|
'description' => $event['description'],
|
||||||
|
'location' => $event['location'],
|
||||||
|
'className' => 'cat-' . asciiwords($event['categories'], true),
|
||||||
|
'allDay' => ($event['all_day'] == 1)?true:false,
|
||||||
|
) + $event;
|
||||||
|
}
|
||||||
|
return json_encode($json);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
54
plugins/calendar/config.inc.php.dist
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Configuration for the Calendar plugin |
|
||||||
|
| Version 0.3 alpha |
|
||||||
|
| |
|
||||||
|
| This program is free software; you can redistribute it and/or modify |
|
||||||
|
| it under the terms of the GNU General Public License version 2 |
|
||||||
|
| as published by the Free Software Foundation. |
|
||||||
|
| |
|
||||||
|
| 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 General Public License for more details. |
|
||||||
|
| |
|
||||||
|
| You should have received a copy of the GNU General Public License along |
|
||||||
|
| with this program; if not, write to the Free Software Foundation, Inc., |
|
||||||
|
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
|
||||||
|
| |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Author: Lazlo Westerhof <hello@lazlo.me> |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
// backend type (database, google, kolab)
|
||||||
|
$rcmail_config['calendar_driver'] = "database";
|
||||||
|
|
||||||
|
// default calendar view (agendaDay, agendaWeek, month)
|
||||||
|
$rcmail_config['calendar_default_view'] = "agendaWeek";
|
||||||
|
|
||||||
|
// general date format
|
||||||
|
$rcmail_config['calendar_date_format'] = "yyyy-MM-dd";
|
||||||
|
|
||||||
|
// time format (HH:mm, H:mm, h:mmt)
|
||||||
|
$rcmail_config['calendar_time_format'] = "HH:mm";
|
||||||
|
|
||||||
|
// short date format (used for column titles)
|
||||||
|
$rcmail_config['calendar_date_short'] = 'M-d';
|
||||||
|
|
||||||
|
// timeslots per hour (1, 2, 3, 4, 6)
|
||||||
|
$rcmail_config['calendar_timeslots'] = 2;
|
||||||
|
|
||||||
|
// first day of the week (0-6)
|
||||||
|
$rcmail_config['calendar_first_day'] = 1;
|
||||||
|
|
||||||
|
// first hour of the calendar (0-23)
|
||||||
|
$rcmail_config['calendar_first_hour'] = 6;
|
||||||
|
|
||||||
|
// event categories
|
||||||
|
$rcmail_config['calendar_categories'] = array('Personal' => 'c0c0c0',
|
||||||
|
'Work' => 'ff0000',
|
||||||
|
'Family' => '00ff00',
|
||||||
|
'Holiday' => 'ff6600');
|
||||||
|
?>
|
550
plugins/calendar/drivers/caldav/caldav-client.php
Normal file
|
@ -0,0 +1,550 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* A Class for connecting to a caldav server
|
||||||
|
*
|
||||||
|
* @package awl
|
||||||
|
* removed curl - now using fsockopen
|
||||||
|
* changed 2009 by Andres Obrero - Switzerland andres@obrero.ch
|
||||||
|
*
|
||||||
|
* @subpackage caldav
|
||||||
|
* @author Andrew McMillan <debian@mcmillan.net.nz>
|
||||||
|
* @copyright Andrew McMillan
|
||||||
|
* @license http://gnu.org/copyleft/gpl.html GNU GPL v2
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for accessing DAViCal via CalDAV, as a client
|
||||||
|
*
|
||||||
|
* @package awl
|
||||||
|
*/
|
||||||
|
class CalDAVClient {
|
||||||
|
/**
|
||||||
|
* Server, username, password, calendar
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
var $base_url, $user, $pass, $calendar, $entry, $protocol, $server, $port;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The useragent which is send to the caldav server
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
var $user_agent = 'DAViCalClient';
|
||||||
|
|
||||||
|
var $headers = array();
|
||||||
|
var $body = "";
|
||||||
|
var $requestMethod = "GET";
|
||||||
|
var $httpRequest = ""; // for debugging http headers sent
|
||||||
|
var $xmlRequest = ""; // for debugging xml sent
|
||||||
|
var $httpResponse = ""; // for debugging http headers received
|
||||||
|
var $xmlResponse = ""; // for debugging xml received
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor, initialises the class
|
||||||
|
*
|
||||||
|
* @param string $base_url The URL for the calendar server
|
||||||
|
* @param string $user The name of the user logging in
|
||||||
|
* @param string $pass The password for that user
|
||||||
|
* @param string $calendar The name of the calendar (not currently used)
|
||||||
|
*/
|
||||||
|
function CalDAVClient( $base_url, $user, $pass, $calendar = '' ) {
|
||||||
|
$this->user = $user;
|
||||||
|
$this->pass = $pass;
|
||||||
|
$this->calendar = $calendar;
|
||||||
|
$this->headers = array();
|
||||||
|
|
||||||
|
if ( preg_match( '#^(https?)://([a-z0-9.-]+)(:([0-9]+))?(/.*)$#', $base_url, $matches ) ) {
|
||||||
|
$this->server = $matches[2];
|
||||||
|
$this->base_url = $matches[5];
|
||||||
|
if ( $matches[1] == 'https' ) {
|
||||||
|
$this->protocol = 'ssl';
|
||||||
|
$this->port = 443;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->protocol = 'tcp';
|
||||||
|
$this->port = 80;
|
||||||
|
}
|
||||||
|
if ( $matches[4] != '' ) {
|
||||||
|
$this->port = intval($matches[4]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
trigger_error("Invalid URL: '".$base_url."'", E_USER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an If-Match or If-None-Match header
|
||||||
|
*
|
||||||
|
* @param bool $match to Match or Not to Match, that is the question!
|
||||||
|
* @param string $etag The etag to match / not match against.
|
||||||
|
*/
|
||||||
|
function SetMatch( $match, $etag = '*' ) {
|
||||||
|
$this->headers[] = sprintf( "%s-Match: %s", ($match ? "If" : "If-None"), $etag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a Depth: header. Valid values are 0, 1 or infinity
|
||||||
|
*
|
||||||
|
* @param int $depth The depth, default to infinity
|
||||||
|
*/
|
||||||
|
function SetDepth( $depth = '0' ) {
|
||||||
|
$this->headers[] = 'Depth: '. ($depth == '1' ? "1" : ($depth == 'infinity' ? $depth : "0") );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a Depth: header. Valid values are 1 or infinity
|
||||||
|
*
|
||||||
|
* @param int $depth The depth, default to infinity
|
||||||
|
*/
|
||||||
|
function SetUserAgent( $user_agent = null ) {
|
||||||
|
if ( !isset($user_agent) ) $user_agent = $this->user_agent;
|
||||||
|
$this->user_agent = $user_agent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a Content-type: header.
|
||||||
|
*
|
||||||
|
* @param int $type The content type
|
||||||
|
*/
|
||||||
|
function SetContentType( $type ) {
|
||||||
|
$this->headers[] = "Content-type: $type";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split response into httpResponse and xmlResponse
|
||||||
|
*
|
||||||
|
* @param string Response from server
|
||||||
|
*/
|
||||||
|
function ParseResponse( $response ) {
|
||||||
|
$pos = strpos($response, '<?xml');
|
||||||
|
if ($pos === false) {
|
||||||
|
$this->httpResponse = trim($response);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->httpResponse = trim(substr($response, 0, $pos));
|
||||||
|
$this->xmlResponse = trim(substr($response, $pos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output http request headers
|
||||||
|
*
|
||||||
|
* @return HTTP headers
|
||||||
|
*/
|
||||||
|
function GetHttpRequest() {
|
||||||
|
return $this->httpRequest;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Output http response headers
|
||||||
|
*
|
||||||
|
* @return HTTP headers
|
||||||
|
*/
|
||||||
|
function GetHttpResponse() {
|
||||||
|
return $this->httpResponse;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Output xml request
|
||||||
|
*
|
||||||
|
* @return raw xml
|
||||||
|
*/
|
||||||
|
function GetXmlRequest() {
|
||||||
|
return $this->xmlRequest;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Output xml response
|
||||||
|
*
|
||||||
|
* @return raw xml
|
||||||
|
*/
|
||||||
|
function GetXmlResponse() {
|
||||||
|
return $this->xmlResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a request to the server
|
||||||
|
*
|
||||||
|
* @param string $relative_url The URL to make the request to, relative to $base_url
|
||||||
|
*
|
||||||
|
* @return string The content of the response from the server
|
||||||
|
*/
|
||||||
|
function DoRequest( $relative_url = "" ) {
|
||||||
|
if(!defined("_FSOCK_TIMEOUT")){ define("_FSOCK_TIMEOUT", 10); }
|
||||||
|
$headers = array();
|
||||||
|
|
||||||
|
$headers[] = $this->requestMethod." ". $this->base_url . $relative_url . " HTTP/1.1";
|
||||||
|
$headers[] = "Authorization: Basic ".base64_encode($this->user .":". $this->pass );
|
||||||
|
$headers[] = "Host: ".$this->server .":".$this->port;
|
||||||
|
|
||||||
|
foreach( $this->headers as $ii => $head ) {
|
||||||
|
$headers[] = $head;
|
||||||
|
}
|
||||||
|
$headers[] = "Content-Length: " . strlen($this->body);
|
||||||
|
$headers[] = "User-Agent: " . $this->user_agent;
|
||||||
|
$headers[] = 'Connection: close';
|
||||||
|
$this->httpRequest = join("\r\n",$headers);
|
||||||
|
$this->xmlRequest = $this->body;
|
||||||
|
|
||||||
|
$fip = fsockopen( $this->protocol . '://' . $this->server, $this->port, $errno, $errstr, _FSOCK_TIMEOUT); //error handling?
|
||||||
|
if ( !(get_resource_type($fip) == 'stream') ) return false;
|
||||||
|
if ( !fwrite($fip, $this->httpRequest."\r\n\r\n".$this->body) ) { fclose($fip); return false; }
|
||||||
|
$rsp = "";
|
||||||
|
while( !feof($fip) ) { $rsp .= fgets($fip,8192); }
|
||||||
|
fclose($fip);
|
||||||
|
|
||||||
|
$this->headers = array(); // reset the headers array for our next request
|
||||||
|
$this->ParseResponse($rsp);
|
||||||
|
return $rsp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an OPTIONS request to the server
|
||||||
|
*
|
||||||
|
* @param string $relative_url The URL to make the request to, relative to $base_url
|
||||||
|
*
|
||||||
|
* @return array The allowed options
|
||||||
|
*/
|
||||||
|
function DoOptionsRequest( $relative_url = "" ) {
|
||||||
|
$this->requestMethod = "OPTIONS";
|
||||||
|
$this->body = "";
|
||||||
|
$headers = $this->DoRequest($relative_url);
|
||||||
|
$options_header = preg_replace( '/^.*Allow: ([a-z, ]+)\r?\n.*/is', '$1', $headers );
|
||||||
|
$options = array_flip( preg_split( '/[, ]+/', $options_header ));
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an XML request to the server (e.g. PROPFIND, REPORT, MKCALENDAR)
|
||||||
|
*
|
||||||
|
* @param string $method The method (PROPFIND, REPORT, etc) to use with the request
|
||||||
|
* @param string $xml The XML to send along with the request
|
||||||
|
* @param string $relative_url The URL to make the request to, relative to $base_url
|
||||||
|
*
|
||||||
|
* @return array An array of the allowed methods
|
||||||
|
*/
|
||||||
|
function DoXMLRequest( $request_method, $xml, $relative_url = '' ) {
|
||||||
|
$this->body = $xml;
|
||||||
|
$this->requestMethod = $request_method;
|
||||||
|
$this->SetContentType("text/xml");
|
||||||
|
return $this->DoRequest($relative_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single item from the server.
|
||||||
|
*
|
||||||
|
* @param string $relative_url The part of the URL after the calendar
|
||||||
|
*/
|
||||||
|
function DoGETRequest( $relative_url ) {
|
||||||
|
$this->body = "";
|
||||||
|
$this->requestMethod = "GET";
|
||||||
|
return $this->DoRequest( $relative_url );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PUT a text/icalendar resource, returning the etag
|
||||||
|
*
|
||||||
|
* @param string $relative_url The URL to make the request to, relative to $base_url
|
||||||
|
* @param string $icalendar The iCalendar resource to send to the server
|
||||||
|
* @param string $etag The etag of an existing resource to be overwritten, or '*' for a new resource.
|
||||||
|
*
|
||||||
|
* @return string The content of the response from the server
|
||||||
|
*/
|
||||||
|
function DoPUTRequest( $relative_url, $icalendar, $etag = null ) {
|
||||||
|
$this->body = $icalendar;
|
||||||
|
|
||||||
|
$this->requestMethod = "PUT";
|
||||||
|
if ( $etag != null ) {
|
||||||
|
$this->SetMatch( ($etag != '*'), $etag );
|
||||||
|
}
|
||||||
|
$this->SetContentType("text/icalendar");
|
||||||
|
$headers = $this->DoRequest($relative_url);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RSCDS will always return the real etag on PUT. Other CalDAV servers may need
|
||||||
|
* more work, but we are assuming we are running against RSCDS in this case.
|
||||||
|
*/
|
||||||
|
$etag = preg_replace( '/^.*Etag: "?([^"\r\n]+)"?\r?\n.*/is', '$1', $headers );
|
||||||
|
return $etag;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE a text/icalendar resource
|
||||||
|
*
|
||||||
|
* @param string $relative_url The URL to make the request to, relative to $base_url
|
||||||
|
* @param string $etag The etag of an existing resource to be deleted, or '*' for any resource at that URL.
|
||||||
|
*
|
||||||
|
* @return int The HTTP Result Code for the DELETE
|
||||||
|
*/
|
||||||
|
function DoDELETERequest( $relative_url, $etag = null ) {
|
||||||
|
$this->body = "";
|
||||||
|
|
||||||
|
$this->requestMethod = "DELETE";
|
||||||
|
if ( $etag != null ) {
|
||||||
|
$this->SetMatch( true, $etag );
|
||||||
|
}
|
||||||
|
$this->DoRequest($relative_url);
|
||||||
|
return $this->resultcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given XML for a calendar query, return an array of the events (/todos) in the
|
||||||
|
* response. Each event in the array will have a 'href', 'etag' and '$response_type'
|
||||||
|
* part, where the 'href' is relative to the calendar and the '$response_type' contains the
|
||||||
|
* definition of the calendar data in iCalendar format.
|
||||||
|
*
|
||||||
|
* @param string $filter XML fragment which is the <filter> element of a calendar-query
|
||||||
|
* @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
|
||||||
|
* @param string $report_type Used as a name for the array element containing the calendar data. @deprecated
|
||||||
|
*
|
||||||
|
* @return array An array of the relative URLs, etags, and events from the server. Each element of the array will
|
||||||
|
* be an array with 'href', 'etag' and 'data' elements, corresponding to the URL, the server-supplied
|
||||||
|
* etag (which only varies when the data changes) and the calendar data in iCalendar format.
|
||||||
|
*/
|
||||||
|
function DoCalendarQuery( $filter, $relative_url = '' ) {
|
||||||
|
|
||||||
|
$xml = <<<EOXML
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||||
|
<D:prop>
|
||||||
|
<C:calendar-data/>
|
||||||
|
<D:getetag/>
|
||||||
|
</D:prop>$filter
|
||||||
|
</C:calendar-query>
|
||||||
|
EOXML;
|
||||||
|
|
||||||
|
$this->DoXMLRequest( 'REPORT', $xml, $relative_url );
|
||||||
|
$xml_parser = xml_parser_create_ns('UTF-8');
|
||||||
|
$this->xml_tags = array();
|
||||||
|
xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
|
||||||
|
xml_parse_into_struct( $xml_parser, $this->xmlResponse, $this->xml_tags );
|
||||||
|
xml_parser_free($xml_parser);
|
||||||
|
|
||||||
|
$report = array();
|
||||||
|
foreach( $this->xml_tags as $k => $v ) {
|
||||||
|
switch( $v['tag'] ) {
|
||||||
|
case 'DAV::RESPONSE':
|
||||||
|
if ( $v['type'] == 'open' ) {
|
||||||
|
$response = array();
|
||||||
|
}
|
||||||
|
elseif ( $v['type'] == 'close' ) {
|
||||||
|
$report[] = $response;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'DAV::HREF':
|
||||||
|
$response['href'] = basename( $v['value'] );
|
||||||
|
break;
|
||||||
|
case 'DAV::GETETAG':
|
||||||
|
$response['etag'] = preg_replace('/^"?([^"]+)"?/', '$1', $v['value']);
|
||||||
|
break;
|
||||||
|
case 'URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-DATA':
|
||||||
|
$response['data'] = $v['value'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $report;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the events in a range from $start to $finish. The dates should be in the
|
||||||
|
* format yyyymmddThhmmssZ and should be in GMT. The events are returned as an
|
||||||
|
* array of event arrays. Each event array will have a 'href', 'etag' and 'event'
|
||||||
|
* part, where the 'href' is relative to the calendar and the event contains the
|
||||||
|
* definition of the event in iCalendar format.
|
||||||
|
*
|
||||||
|
* @param timestamp $start The start time for the period
|
||||||
|
* @param timestamp $finish The finish time for the period
|
||||||
|
* @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
|
||||||
|
*
|
||||||
|
* @return array An array of the relative URLs, etags, and events, returned from DoCalendarQuery() @see DoCalendarQuery()
|
||||||
|
*/
|
||||||
|
function GetEvents( $start = null, $finish = null, $relative_url = '' ) {
|
||||||
|
$filter = "";
|
||||||
|
if ( isset($start) && isset($finish) )
|
||||||
|
$range = "<C:time-range start=\"$start\" end=\"$finish\"/>";
|
||||||
|
else
|
||||||
|
$range = '';
|
||||||
|
|
||||||
|
$filter = <<<EOFILTER
|
||||||
|
<C:filter>
|
||||||
|
<C:comp-filter name="VCALENDAR">
|
||||||
|
<C:comp-filter name="VEVENT">
|
||||||
|
$range
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:filter>
|
||||||
|
EOFILTER;
|
||||||
|
|
||||||
|
return $this->DoCalendarQuery($filter, $relative_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the todo's in a range from $start to $finish. The dates should be in the
|
||||||
|
* format yyyymmddThhmmssZ and should be in GMT. The events are returned as an
|
||||||
|
* array of event arrays. Each event array will have a 'href', 'etag' and 'event'
|
||||||
|
* part, where the 'href' is relative to the calendar and the event contains the
|
||||||
|
* definition of the event in iCalendar format.
|
||||||
|
*
|
||||||
|
* @param timestamp $start The start time for the period
|
||||||
|
* @param timestamp $finish The finish time for the period
|
||||||
|
* @param boolean $completed Whether to include completed tasks
|
||||||
|
* @param boolean $cancelled Whether to include cancelled tasks
|
||||||
|
* @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
|
||||||
|
*
|
||||||
|
* @return array An array of the relative URLs, etags, and events, returned from DoCalendarQuery() @see DoCalendarQuery()
|
||||||
|
*/
|
||||||
|
function GetTodos( $start, $finish, $completed = false, $cancelled = false, $relative_url = "" ) {
|
||||||
|
|
||||||
|
if ( $start && $finish ) {
|
||||||
|
$time_range = <<<EOTIME
|
||||||
|
<C:time-range start="$start" end="$finish"/>
|
||||||
|
EOTIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning! May contain traces of double negatives...
|
||||||
|
$neg_cancelled = ( $cancelled === true ? "no" : "yes" );
|
||||||
|
$neg_completed = ( $cancelled === true ? "no" : "yes" );
|
||||||
|
|
||||||
|
$filter = <<<EOFILTER
|
||||||
|
<C:filter>
|
||||||
|
<C:comp-filter name="VCALENDAR">
|
||||||
|
<C:comp-filter name="VTODO">
|
||||||
|
<C:prop-filter name="STATUS">
|
||||||
|
<C:text-match negate-condition="$neg_completed">COMPLETED</C:text-match>
|
||||||
|
</C:prop-filter>
|
||||||
|
<C:prop-filter name="STATUS">
|
||||||
|
<C:text-match negate-condition="$neg_cancelled">CANCELLED</C:text-match>
|
||||||
|
</C:prop-filter>$time_range
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:filter>
|
||||||
|
EOFILTER;
|
||||||
|
|
||||||
|
return $this->DoCalendarQuery($filter, $relative_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the calendar entry by UID
|
||||||
|
*
|
||||||
|
* @param uid
|
||||||
|
* @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
|
||||||
|
*
|
||||||
|
* @return array An array of the relative URL, etag, and calendar data returned from DoCalendarQuery() @see DoCalendarQuery()
|
||||||
|
*/
|
||||||
|
function GetEntryByUid( $uid, $relative_url = '' ) {
|
||||||
|
$filter = "";
|
||||||
|
if ( $uid ) {
|
||||||
|
$filter = <<<EOFILTER
|
||||||
|
<C:filter>
|
||||||
|
<C:comp-filter name="VCALENDAR">
|
||||||
|
<C:comp-filter name="VEVENT">
|
||||||
|
<C:prop-filter name="UID">
|
||||||
|
<C:text-match icollation="i;octet">$uid</C:text-match>
|
||||||
|
</C:prop-filter>
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:filter>
|
||||||
|
EOFILTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->DoCalendarQuery($filter, $relative_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the calendar entry by HREF
|
||||||
|
*
|
||||||
|
* @param string $href The href from a call to GetEvents or GetTodos etc.
|
||||||
|
* @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
|
||||||
|
*
|
||||||
|
* @return string The iCalendar of the calendar entry
|
||||||
|
*/
|
||||||
|
function GetEntryByHref( $href, $relative_url = '' ) {
|
||||||
|
return $this->DoGETRequest( $relative_url . $href );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usage example
|
||||||
|
*
|
||||||
|
* $cal = new CalDAVClient( "http://calendar.example.com/caldav.php/username/calendar/", "username", "password", "calendar" );
|
||||||
|
* $options = $cal->DoOptionsRequest();
|
||||||
|
* if ( isset($options["PROPFIND"]) ) {
|
||||||
|
* // Fetch some information about the events in that calendar
|
||||||
|
* $cal->SetDepth(1);
|
||||||
|
* $folder_xml = $cal->DoXMLRequest("PROPFIND", '<?xml version="1.0" encoding="utf-8" ?><propfind xmlns="DAV:"><prop><getcontentlength/><getcontenttype/><resourcetype/><getetag/></prop></propfind>' );
|
||||||
|
* }
|
||||||
|
* // Fetch all events for February
|
||||||
|
* $events = $cal->GetEvents("20070101T000000Z","20070201T000000Z");
|
||||||
|
* foreach ( $events AS $k => $event ) {
|
||||||
|
* do_something_with_event_data( $event['data'] );
|
||||||
|
* }
|
||||||
|
* $acc = array();
|
||||||
|
* $acc["google"] = array(
|
||||||
|
* "user"=>"kunsttherapie@gmail.com",
|
||||||
|
* "pass"=>"xxxxx",
|
||||||
|
* "server"=>"ssl://www.google.com",
|
||||||
|
* "port"=>"443",
|
||||||
|
* "uri"=>"https://www.google.com/calendar/dav/kunsttherapie@gmail.com/events/",
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* $acc["davical"] = array(
|
||||||
|
* "user"=>"some_user",
|
||||||
|
* "pass"=>"big secret",
|
||||||
|
* "server"=>"calendar.foo.bar",
|
||||||
|
* "port"=>"80",
|
||||||
|
* "uri"=>"http://calendar.foo.bar/caldav.php/some_user/home/",
|
||||||
|
* );
|
||||||
|
* //*******************************
|
||||||
|
*
|
||||||
|
* $account = $acc["davical"];
|
||||||
|
*
|
||||||
|
* //*******************************
|
||||||
|
* $cal = new CalDAVClient( $account["uri"], $account["user"], $account["pass"], "", $account["server"], $account["port"] );
|
||||||
|
* $options = $cal->DoOptionsRequest();
|
||||||
|
* print_r($options);
|
||||||
|
*
|
||||||
|
* //*******************************
|
||||||
|
* //*******************************
|
||||||
|
*
|
||||||
|
* $xmlC = <<<PROPP
|
||||||
|
* <?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
* <D:propfind xmlns:D="DAV:" xmlns:C="http://calendarserver.org/ns/">
|
||||||
|
* <D:prop>
|
||||||
|
* <D:displayname />
|
||||||
|
* <C:getctag />
|
||||||
|
* <D:resourcetype />
|
||||||
|
*
|
||||||
|
* </D:prop>
|
||||||
|
* </D:propfind>
|
||||||
|
* PROPP;
|
||||||
|
* //if ( isset($options["PROPFIND"]) ) {
|
||||||
|
* // Fetch some information about the events in that calendar
|
||||||
|
* // $cal->SetDepth(1);
|
||||||
|
* // $folder_xml = $cal->DoXMLRequest("PROPFIND", $xmlC);
|
||||||
|
* // print_r( $folder_xml);
|
||||||
|
* //}
|
||||||
|
*
|
||||||
|
* // Fetch all events for February
|
||||||
|
* $events = $cal->GetEvents("20090201T000000Z","20090301T000000Z");
|
||||||
|
* foreach ( $events as $k => $event ) {
|
||||||
|
* print_r($event['data']);
|
||||||
|
* print "\n---------------------------------------------\n";
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* //*******************************
|
||||||
|
* //*******************************
|
||||||
|
*/
|
||||||
|
?>
|
158
plugins/calendar/drivers/caldav/caldav_driver.php
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RoundCube Calendar
|
||||||
|
*
|
||||||
|
* CalDAV backend based on exemplary DAViCal / AWL client.
|
||||||
|
*
|
||||||
|
* @version 0.2 BETA 2
|
||||||
|
* @author Michael Duelli
|
||||||
|
* @url http://rc-calendar.lazlo.me
|
||||||
|
* @licence GNU GPL
|
||||||
|
* @copyright (c) 2010 Lazlo Westerhof - Netherlands
|
||||||
|
*/
|
||||||
|
require_once('caldav-client.php');
|
||||||
|
|
||||||
|
class caldav_driver extends calendar_driver
|
||||||
|
{
|
||||||
|
private $rcmail = null;
|
||||||
|
private $cal = null;
|
||||||
|
private $calendar = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param object rcmail The RoundCube instance.
|
||||||
|
* @param string server The CalDAV server.
|
||||||
|
* @param string user The user name.
|
||||||
|
* @param string pass The user's password.
|
||||||
|
* @param string calendar The user calendar.
|
||||||
|
*/
|
||||||
|
public function __construct($rcmail, $server, $user, $pass, $calendar) {
|
||||||
|
$this->rcmail = $rcmail;
|
||||||
|
$this->calendar = '/' . $calendar;
|
||||||
|
|
||||||
|
$this->cal = new CalDAVClient($server. "/" . $user, $user, $pass, $calendar /* is ignored currently */);
|
||||||
|
$this->cal->setUserAgent('RoundCube');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function new_event($event) {
|
||||||
|
// FIXME Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit_event($event) {
|
||||||
|
// FIXME Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
public function move_event($event) {
|
||||||
|
// FIXME Implement. Can be done via editEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resize_event($event) {
|
||||||
|
// FIXME Implement. Can be done via editEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remove_event($event) {
|
||||||
|
// FIXME Implement.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function load_events($start, $end, $calendars = null) {
|
||||||
|
if (!empty($this->rcmail->user->ID)) {
|
||||||
|
// Fetch events.
|
||||||
|
$result = $this->cal->GetEvents($this->GMT_to_iCalendar($start), $this->GMT_to_iCalendar($end), $this->calendar);
|
||||||
|
|
||||||
|
$events = array();
|
||||||
|
foreach ($result as $k => $event) {
|
||||||
|
$lines = explode("\n", $event['data']);
|
||||||
|
|
||||||
|
$n = count($lines);
|
||||||
|
$eventid = null;
|
||||||
|
|
||||||
|
$flag = true;
|
||||||
|
for ($i = 0; $i < $n; $i++) {
|
||||||
|
if ($flag) {
|
||||||
|
if (strpos($lines[$i], "BEGIN:VEVENT") === 0)
|
||||||
|
$flag = false;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($lines[$i], "END:VEVENT") === 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (empty($lines[$i]))
|
||||||
|
continue; // FIXME
|
||||||
|
|
||||||
|
$tmp = explode(":", $lines[$i]);
|
||||||
|
|
||||||
|
if (count($tmp) !== 2)
|
||||||
|
continue; // FIXME
|
||||||
|
|
||||||
|
list($id, $value) = $tmp;
|
||||||
|
|
||||||
|
if (!isset($id) || !isset($value))
|
||||||
|
continue; // FIXME
|
||||||
|
|
||||||
|
if (is_null($eventid) && strpos($id, "UID") === 0)
|
||||||
|
$eventid = $value;
|
||||||
|
elseif (!isset($event['start']) && strpos($id, "DTSTART") === 0) {
|
||||||
|
$event['start'] = $this->iCalendar_to_Unix($value);
|
||||||
|
|
||||||
|
// Check for all-day event.
|
||||||
|
$event['all_day'] = (strlen($value) === 8 ? 0 : 1);
|
||||||
|
} elseif (!isset($event['end']) && strpos($id, "DTEND") === 0)
|
||||||
|
$event['end'] = $this->iCalendar_to_Unix($value);
|
||||||
|
elseif (!isset($event['title']) && strpos($id, "SUMMARY") === 0)
|
||||||
|
$event['title'] = $value;
|
||||||
|
elseif (!isset($event['description']) && strpos($id, "DESCRIPTION") === 0) {
|
||||||
|
$event['description'] = $value;
|
||||||
|
|
||||||
|
// FIXME Problem with multiple lines!
|
||||||
|
// if ($i+1 < $n && $lines[$i+1] does not contain keyword...) {
|
||||||
|
// Add line to description
|
||||||
|
// $i++;
|
||||||
|
// }
|
||||||
|
} elseif (!isset($event['location']) && strpos($id, "LOCATION") === 0)
|
||||||
|
$event['location'] = $value;
|
||||||
|
elseif (!isset($event['categories']) && strpos($id, "CATEGORIES") === 0)
|
||||||
|
$event['categories'] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$events[]=array(
|
||||||
|
'event_id' => $eventid,
|
||||||
|
'start' => $event['start'],
|
||||||
|
'end' => $event['end'],
|
||||||
|
'title' => strval($event['title']),
|
||||||
|
'description' => strval($event['description']),
|
||||||
|
'location' => strval($event['location']),
|
||||||
|
'categories' => $event['categories'],
|
||||||
|
'allDay' => $event['all_day'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $events;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a GMT time stamp ('Y-m-d H:i:s') to the iCalendar format as defined in
|
||||||
|
* RFC 5545, Section 3.2.19, http://tools.ietf.org/html/rfc5545#section-3.2.19.
|
||||||
|
*
|
||||||
|
* @param timestamp A GMT time stamp ('Y-m-d H:i:s')
|
||||||
|
* @return An iCalendar time stamp, e.g. yyyymmddThhmmssZ
|
||||||
|
*/
|
||||||
|
private function GMT_to_iCalendar($timestamp) {
|
||||||
|
$unix_timestamp = strtotime($timestamp);
|
||||||
|
return date("Ymd", $unix_timestamp) . "T" . date("His", $unix_timestamp) . "Z";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a time stamp in iCalendar format as defined in
|
||||||
|
* RFC 5545, Section 3.2.19, http://tools.ietf.org/html/rfc5545#section-3.2.19
|
||||||
|
* to a Unix time stamp. Further conversion is done in jsonEvents.
|
||||||
|
*
|
||||||
|
* @param timestamp An iCalendar time stamp, e.g. yyyymmddThhmmssZ
|
||||||
|
* @return A Unix time stamp
|
||||||
|
*/
|
||||||
|
private function iCalendar_to_Unix($timestamp) {
|
||||||
|
return strtotime($timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
171
plugins/calendar/drivers/calendar_driver.php
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Driver interface for the Calendar Plugin |
|
||||||
|
| Version 0.3 beta |
|
||||||
|
| |
|
||||||
|
| This program is free software; you can redistribute it and/or modify |
|
||||||
|
| it under the terms of the GNU General Public License version 2 |
|
||||||
|
| as published by the Free Software Foundation. |
|
||||||
|
| |
|
||||||
|
| 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 General Public License for more details. |
|
||||||
|
| |
|
||||||
|
| You should have received a copy of the GNU General Public License along |
|
||||||
|
| with this program; if not, write to the Free Software Foundation, Inc., |
|
||||||
|
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
|
||||||
|
| |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Author: Lazlo Westerhof <hello@lazlo.me> |
|
||||||
|
| Thomas Bruederli <roundcube@gmail.com> |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
*/
|
||||||
|
abstract class calendar_driver
|
||||||
|
{
|
||||||
|
// backend features
|
||||||
|
public $attendees = false;
|
||||||
|
public $attachments = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of available calendars from this source
|
||||||
|
*/
|
||||||
|
abstract function list_calendars();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new calendar assigned to the current user
|
||||||
|
*
|
||||||
|
* @param array Hash array with calendar properties
|
||||||
|
* name: Calendar name
|
||||||
|
* color: The color of the calendar
|
||||||
|
* @return mixed ID of the calendar on success, False on error
|
||||||
|
*/
|
||||||
|
abstract function create_calendar($prop);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a single event to the database
|
||||||
|
*
|
||||||
|
* @param array Hash array with vent properties:
|
||||||
|
* calendar: Calendar identifier to add event to (optional)
|
||||||
|
* uid: Unique identifier of this event
|
||||||
|
* start: Event start date/time as unix timestamp
|
||||||
|
* end: Event end date/time as unix timestamp
|
||||||
|
* allday: Boolean flag if this is an all-day event
|
||||||
|
* title: Event title/summary
|
||||||
|
* location: Location string
|
||||||
|
* description: Event description
|
||||||
|
* recurrence: Recurrence definition according to iCalendar specification
|
||||||
|
* categories: Event categories (comma-separated list)
|
||||||
|
* free_busy: Show time as free/busy/outofoffice
|
||||||
|
* priority: Event priority
|
||||||
|
* alarms: Reminder settings (TBD.)
|
||||||
|
* @return mixed New event ID on success, False on error
|
||||||
|
*/
|
||||||
|
abstract function new_event($event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an event entry with the given data
|
||||||
|
*
|
||||||
|
* @see Driver:new_event()
|
||||||
|
* @return boolean True on success, False on error
|
||||||
|
*/
|
||||||
|
abstract function edit_event($event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move a single event
|
||||||
|
*
|
||||||
|
* @param array Hash array with event properties:
|
||||||
|
* id: Event identifier
|
||||||
|
* start: Event start date/time as unix timestamp
|
||||||
|
* end: Event end date/time as unix timestamp
|
||||||
|
* allday: Boolean flag if this is an all-day event
|
||||||
|
* @return boolean True on success, False on error
|
||||||
|
*/
|
||||||
|
abstract function move_event($event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resize a single event
|
||||||
|
*
|
||||||
|
* @param array Hash array with event properties:
|
||||||
|
* id: Event identifier
|
||||||
|
* start: Event start date/time as unix timestamp in user timezone
|
||||||
|
* end: Event end date/time as unix timestamp in user timezone
|
||||||
|
* @return boolean True on success, False on error
|
||||||
|
*/
|
||||||
|
abstract function resize_event($event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a single event from the database
|
||||||
|
*
|
||||||
|
* @param array Hash array with event properties:
|
||||||
|
* id: Event identifier
|
||||||
|
* @return boolean True on success, False on error
|
||||||
|
*/
|
||||||
|
abstract function remove_event($event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get events from source.
|
||||||
|
*
|
||||||
|
* @param integer Event's new start (unix timestamp)
|
||||||
|
* @param integer Event's new end (unix timestamp)
|
||||||
|
* @param mixed List of calendar IDs to load events from (either as array or comma-separated string)
|
||||||
|
* @return array A list of event records
|
||||||
|
*/
|
||||||
|
abstract function load_events($start, $end, $calendars = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search events using the given query
|
||||||
|
*
|
||||||
|
* @param integer Event's new start (unix timestamp)
|
||||||
|
* @param integer Event's new end (unix timestamp)
|
||||||
|
* @param string Search query
|
||||||
|
* @param mixed List of calendar IDs to load events from (either as array or comma-separated string)
|
||||||
|
* @return array A list of event records
|
||||||
|
*/
|
||||||
|
abstract function search_events($start, $end, $query, $calendars = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save an attachment related to the given event
|
||||||
|
*/
|
||||||
|
public function add_attachment($attachment, $event_id) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a specific attachment from the given event
|
||||||
|
*/
|
||||||
|
public function remove_attachment($attachment, $event_id) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List availabale categories
|
||||||
|
* The default implementation reads them from config/user prefs
|
||||||
|
*/
|
||||||
|
public function list_categories()
|
||||||
|
{
|
||||||
|
$rcmail = rcmail::get_instance();
|
||||||
|
return $rcmail->config->get('calendar_categories', array());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new category
|
||||||
|
*/
|
||||||
|
public function add_category($name, $color) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the given category
|
||||||
|
*/
|
||||||
|
public function remove_category($name) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update/replace a category
|
||||||
|
*/
|
||||||
|
public function replace_category($oldname, $name, $color) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch free/busy information from a person within the given range
|
||||||
|
*/
|
||||||
|
public function get_freebusy_list($email, $start, $end)
|
||||||
|
{
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
388
plugins/calendar/drivers/database/database_driver.php
Normal file
|
@ -0,0 +1,388 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Database driver for the Calendar Plugin |
|
||||||
|
| Version 0.3 beta |
|
||||||
|
| |
|
||||||
|
| This program is free software; you can redistribute it and/or modify |
|
||||||
|
| it under the terms of the GNU General Public License version 2 |
|
||||||
|
| as published by the Free Software Foundation. |
|
||||||
|
| |
|
||||||
|
| 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 General Public License for more details. |
|
||||||
|
| |
|
||||||
|
| You should have received a copy of the GNU General Public License along |
|
||||||
|
| with this program; if not, write to the Free Software Foundation, Inc., |
|
||||||
|
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
|
||||||
|
| |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Author: Lazlo Westerhof <hello@lazlo.me> |
|
||||||
|
| Thomas Bruederli <roundcube@gmail.com> |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
class database_driver extends calendar_driver
|
||||||
|
{
|
||||||
|
// features this backend supports
|
||||||
|
public $attendees = true;
|
||||||
|
public $attachments = true;
|
||||||
|
|
||||||
|
private $rc;
|
||||||
|
private $cal;
|
||||||
|
private $calendars = array();
|
||||||
|
private $calendar_ids = '';
|
||||||
|
private $free_busy_map = array('free' => 0, 'busy' => 1, 'out-of-office' => 2, 'outofoffice' => 2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor
|
||||||
|
*/
|
||||||
|
public function __construct($cal)
|
||||||
|
{
|
||||||
|
$this->cal = $cal;
|
||||||
|
$this->rc = $cal->rc;
|
||||||
|
$this->_read_calendars();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read available calendars for the current user and store them internally
|
||||||
|
*/
|
||||||
|
private function _read_calendars()
|
||||||
|
{
|
||||||
|
if (!empty($this->rc->user->ID)) {
|
||||||
|
$calendar_ids = array();
|
||||||
|
$result = $this->rc->db->query(
|
||||||
|
"SELECT * FROM calendars
|
||||||
|
WHERE user_id=?",
|
||||||
|
$this->rc->user->ID
|
||||||
|
);
|
||||||
|
while ($result && ($arr = $this->rc->db->fetch_assoc($result))) {
|
||||||
|
$this->calendars[$arr['calendar_id']] = $arr;
|
||||||
|
$calendar_ids[] = $this->rc->db->quote($arr['calendar_id']);
|
||||||
|
}
|
||||||
|
$this->calendar_ids = join(',', $calendar_ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of available calendars from this source
|
||||||
|
*/
|
||||||
|
public function list_calendars()
|
||||||
|
{
|
||||||
|
// attempt to create a default calendar for this user
|
||||||
|
if (empty($this->calendars)) {
|
||||||
|
if ($this->create_calendar(array('name' => 'Default', 'color' => 'cc0000')))
|
||||||
|
$this->_read_calendars();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->calendars;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new calendar assigned to the current user
|
||||||
|
*
|
||||||
|
* @param array Hash array with calendar properties
|
||||||
|
* name: Calendar name
|
||||||
|
* color: The color of the calendar
|
||||||
|
* @return mixed ID of the calendar on success, False on error
|
||||||
|
*/
|
||||||
|
public function create_calendar($prop)
|
||||||
|
{
|
||||||
|
$result = $this->rc->db->query(
|
||||||
|
"INSERT INTO calendars
|
||||||
|
(user_id, name, color)
|
||||||
|
VALUES (?, ?, ?)",
|
||||||
|
$this->rc->user->ID,
|
||||||
|
$prop['name'],
|
||||||
|
$prop['color']
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($result)
|
||||||
|
return $this->rc->db->insert_id('calendars');
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a single event to the database
|
||||||
|
*
|
||||||
|
* @param array Hash array with event properties
|
||||||
|
* @see Driver:new_event()
|
||||||
|
*/
|
||||||
|
public function new_event($event)
|
||||||
|
{
|
||||||
|
if (!empty($this->calendars)) {
|
||||||
|
if ($event['calendar'] && !$this->calendars[$event['calendar']])
|
||||||
|
return false;
|
||||||
|
if (!$event['calendar'])
|
||||||
|
$event['calendar'] = reset(array_keys($this->calendars));
|
||||||
|
|
||||||
|
$event = $this->_save_preprocess($event);
|
||||||
|
$query = $this->rc->db->query(sprintf(
|
||||||
|
"INSERT INTO events
|
||||||
|
(calendar_id, created, changed, uid, start, end, all_day, recurrence, title, description, location, categories, free_busy, priority, alarms)
|
||||||
|
VALUES (?, %s, %s, ?, %s, %s, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
$this->rc->db->now(),
|
||||||
|
$this->rc->db->now(),
|
||||||
|
$this->rc->db->fromunixtime($event['start']),
|
||||||
|
$this->rc->db->fromunixtime($event['end'])
|
||||||
|
),
|
||||||
|
$event['calendar'],
|
||||||
|
strval($event['uid']),
|
||||||
|
intval($event['allday']),
|
||||||
|
$event['recurrence'],
|
||||||
|
strval($event['title']),
|
||||||
|
strval($event['description']),
|
||||||
|
strval($event['location']),
|
||||||
|
strval($event['categories']),
|
||||||
|
intval($event['free_busy']),
|
||||||
|
intval($event['priority']),
|
||||||
|
$event['alarms']
|
||||||
|
);
|
||||||
|
return $this->rc->db->insert_id('events');
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an event entry with the given data
|
||||||
|
*
|
||||||
|
* @param array Hash array with event properties
|
||||||
|
* @see Driver:new_event()
|
||||||
|
*/
|
||||||
|
public function edit_event($event)
|
||||||
|
{
|
||||||
|
if (!empty($this->calendars)) {
|
||||||
|
$event = $this->_save_preprocess($event);
|
||||||
|
$query = $this->rc->db->query(sprintf(
|
||||||
|
"UPDATE events
|
||||||
|
SET changed=%s, start=%s, end=%s, all_day=?, recurrence=?, title=?, description=?, location=?, categories=?, free_busy=?, priority=?, alarms=?
|
||||||
|
WHERE event_id=?
|
||||||
|
AND calendar_id IN (" . $this->calendar_ids . ")",
|
||||||
|
$this->rc->db->now(),
|
||||||
|
$this->rc->db->fromunixtime($event['start']),
|
||||||
|
$this->rc->db->fromunixtime($event['end'])
|
||||||
|
),
|
||||||
|
intval($event['allday']),
|
||||||
|
$event['recurrence'],
|
||||||
|
strval($event['title']),
|
||||||
|
strval($event['description']),
|
||||||
|
strval($event['location']),
|
||||||
|
strval($event['categories']),
|
||||||
|
intval($event['free_busy']),
|
||||||
|
intval($event['priority']),
|
||||||
|
$event['alarms'],
|
||||||
|
$event['id']
|
||||||
|
);
|
||||||
|
return $this->rc->db->affected_rows($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert save data to be used in SQL statements
|
||||||
|
*/
|
||||||
|
private function _save_preprocess($event)
|
||||||
|
{
|
||||||
|
// compose vcalendar-style recurrencue rule from structured data
|
||||||
|
$rrule = '';
|
||||||
|
if (is_array($event['recurrence'])) {
|
||||||
|
foreach ($event['recurrence'] as $k => $val) {
|
||||||
|
$k = strtoupper($k);
|
||||||
|
switch ($k) {
|
||||||
|
case 'UNTIL':
|
||||||
|
$val = gmdate('Ymd\THis', $val);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$rrule .= $k . '=' . $val . ';';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (is_string($event['recurrence']))
|
||||||
|
$rrule = $event['recurrence'];
|
||||||
|
|
||||||
|
$event['recurrence'] = rtrim($rrule, ';');
|
||||||
|
$event['free_busy'] = intval($this->free_busy_map[strtolower($event['free_busy'])]);
|
||||||
|
$event['allday'] = $event['allday'] ? 1 : 0;
|
||||||
|
|
||||||
|
return $event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move a single event
|
||||||
|
*
|
||||||
|
* @param array Hash array with event properties
|
||||||
|
* @see Driver:move_event()
|
||||||
|
*/
|
||||||
|
public function move_event($event)
|
||||||
|
{
|
||||||
|
if (!empty($this->calendars)) {
|
||||||
|
$query = $this->rc->db->query(sprintf(
|
||||||
|
"UPDATE events
|
||||||
|
SET changed=%s, start=%s, end=%s, all_day=?
|
||||||
|
WHERE event_id=?
|
||||||
|
AND calendar_id IN (" . $this->calendar_ids . ")",
|
||||||
|
$this->rc->db->now(),
|
||||||
|
$this->rc->db->fromunixtime($event['start']),
|
||||||
|
$this->rc->db->fromunixtime($event['end'])
|
||||||
|
),
|
||||||
|
$event['allday'] ? 1 : 0,
|
||||||
|
$event['id']
|
||||||
|
);
|
||||||
|
return $this->rc->db->affected_rows($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resize a single event
|
||||||
|
*
|
||||||
|
* @param array Hash array with event properties
|
||||||
|
* @see Driver:resize_event()
|
||||||
|
*/
|
||||||
|
public function resize_event($event)
|
||||||
|
{
|
||||||
|
if (!empty($this->calendars)) {
|
||||||
|
$query = $this->rc->db->query(sprintf(
|
||||||
|
"UPDATE events
|
||||||
|
SET changed=%s, start=%s, end=%s
|
||||||
|
WHERE event_id=?
|
||||||
|
AND calendar_id IN (" . $this->calendar_ids . ")",
|
||||||
|
$this->rc->db->now(),
|
||||||
|
$this->rc->db->fromunixtime($event['start']),
|
||||||
|
$this->rc->db->fromunixtime($event['end'])
|
||||||
|
),
|
||||||
|
$event['id']
|
||||||
|
);
|
||||||
|
return $this->rc->db->affected_rows($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a single event from the database
|
||||||
|
*
|
||||||
|
* @param array Hash array with event properties
|
||||||
|
* @see Driver:remove_event()
|
||||||
|
*/
|
||||||
|
public function remove_event($event)
|
||||||
|
{
|
||||||
|
if (!empty($this->calendars)) {
|
||||||
|
$query = $this->rc->db->query(
|
||||||
|
"DELETE FROM events
|
||||||
|
WHERE event_id=?
|
||||||
|
AND calendar_id IN (" . $this->calendar_ids . ")",
|
||||||
|
$event['id']
|
||||||
|
);
|
||||||
|
return $this->rc->db->affected_rows($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get event data
|
||||||
|
*
|
||||||
|
* @see Driver:load_events()
|
||||||
|
*/
|
||||||
|
public function load_events($start, $end, $calendars = null)
|
||||||
|
{
|
||||||
|
if (empty($calendars))
|
||||||
|
$calendars = array_keys($this->calendars);
|
||||||
|
else if (is_string($calendars))
|
||||||
|
$calendars = explode(',', $calendars);
|
||||||
|
|
||||||
|
// only allow to select from calendars of this use
|
||||||
|
$calendars = array_intersect($calendars, array_keys($this->calendars));
|
||||||
|
|
||||||
|
$events = array();
|
||||||
|
$free_busy_map = array_flip($this->free_busy_map);
|
||||||
|
|
||||||
|
if (!empty($calendars)) {
|
||||||
|
$result = $this->rc->db->query(sprintf(
|
||||||
|
"SELECT * FROM events
|
||||||
|
WHERE calendar_id IN (%s)
|
||||||
|
AND start >= %s AND end <= %s",
|
||||||
|
$this->calendar_ids,
|
||||||
|
$this->rc->db->fromunixtime($start),
|
||||||
|
$this->rc->db->fromunixtime($end)
|
||||||
|
));
|
||||||
|
|
||||||
|
while ($result && ($event = $this->rc->db->fetch_assoc($result))) {
|
||||||
|
$event['id'] = $event['event_id'];
|
||||||
|
$event['start'] = strtotime($event['start']);
|
||||||
|
$event['end'] = strtotime($event['end']);
|
||||||
|
$event['free_busy'] = $free_busy_map[$event['free_busy']];
|
||||||
|
$event['calendar'] = $event['calendar_id'];
|
||||||
|
|
||||||
|
// parse recurrence rule
|
||||||
|
if ($event['recurrence'] && preg_match_all('/([A-Z]+)=([^;]+);?/', $event['recurrence'], $m, PREG_SET_ORDER)) {
|
||||||
|
$event['recurrence'] = array();
|
||||||
|
foreach ($m as $rr) {
|
||||||
|
if (is_numeric($rr[2]))
|
||||||
|
$rr[2] = intval($rr[2]);
|
||||||
|
else if ($rr[1] == 'UNTIL')
|
||||||
|
$rr[2] = strtotime($rr[2]);
|
||||||
|
$event['recurrence'][$rr[1]] = $rr[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($event['event_id'], $event['calendar_id']);
|
||||||
|
$events[] = $event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $events;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search events
|
||||||
|
*
|
||||||
|
* @see Driver:search_events()
|
||||||
|
*/
|
||||||
|
public function search_events($start, $end, $query, $calendars = null)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save an attachment related to the given event
|
||||||
|
*/
|
||||||
|
function add_attachment($attachment, $event_id)
|
||||||
|
{
|
||||||
|
// TBD.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a specific attachment from the given event
|
||||||
|
*/
|
||||||
|
function remove_attachment($attachment, $event_id)
|
||||||
|
{
|
||||||
|
// TBD.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the given category
|
||||||
|
*/
|
||||||
|
public function remove_category($name)
|
||||||
|
{
|
||||||
|
// TBD. alter events accordingly
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update/replace a category
|
||||||
|
*/
|
||||||
|
public function replace_category($oldname, $name, $color)
|
||||||
|
{
|
||||||
|
// TBD. alter events accordingly
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
70
plugins/calendar/drivers/database/sql/mysql.sql
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/**
|
||||||
|
* Roundcube Calendar
|
||||||
|
*
|
||||||
|
* Plugin to add a calendar to Roundcube.
|
||||||
|
*
|
||||||
|
* @version 0.3 beta
|
||||||
|
* @author Lazlo Westerhof
|
||||||
|
* @author Thomas Bruederli
|
||||||
|
* @url http://rc-calendar.lazlo.me
|
||||||
|
* @licence GNU GPL
|
||||||
|
* @copyright (c) 2010 Lazlo Westerhof - Netherlands
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
|
||||||
|
CREATE TABLE `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,
|
||||||
|
PRIMARY KEY(`calendar_id`),
|
||||||
|
CONSTRAINT `fk_calendars_user_id` FOREIGN KEY (`user_id`)
|
||||||
|
REFERENCES `users`(`user_id`)
|
||||||
|
/*!40008
|
||||||
|
ON DELETE CASCADE
|
||||||
|
ON UPDATE CASCADE */
|
||||||
|
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
|
||||||
|
|
||||||
|
CREATE TABLE `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',
|
||||||
|
`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 '',
|
||||||
|
`all_day` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
|
`free_busy` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
|
`priority` tinyint(1) NOT NULL DEFAULT '1',
|
||||||
|
`alarms` varchar(255) DEFAULT NULL,
|
||||||
|
`attendees` text DEFAULT NULL,
|
||||||
|
PRIMARY KEY(`event_id`),
|
||||||
|
CONSTRAINT `fk_events_calendar_id` FOREIGN KEY (`calendar_id`)
|
||||||
|
REFERENCES `calendars`(`calendar_id`)
|
||||||
|
/*!40008
|
||||||
|
ON DELETE CASCADE
|
||||||
|
ON UPDATE CASCADE */
|
||||||
|
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
|
||||||
|
|
||||||
|
CREATE TABLE `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 DEFAULT '',
|
||||||
|
PRIMARY KEY(`attachment_id`),
|
||||||
|
CONSTRAINT `fk_attachments_event_id` FOREIGN KEY (`event_id`)
|
||||||
|
REFERENCES `events`(`event_id`)
|
||||||
|
/*!40008
|
||||||
|
ON DELETE CASCADE
|
||||||
|
ON UPDATE CASCADE */
|
||||||
|
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
|
||||||
|
|
||||||
|
|
40
plugins/calendar/drivers/database/sql/postgresql.sql
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/**
|
||||||
|
* RoundCube Calendar
|
||||||
|
*
|
||||||
|
* Plugin to add a calendar to RoundCube.
|
||||||
|
*
|
||||||
|
* @version 0.2 BETA 2
|
||||||
|
* @author Lazlo Westerhof
|
||||||
|
* @author Albert Lee
|
||||||
|
* @url http://rc-calendar.lazlo.me
|
||||||
|
* @licence GNU GPL
|
||||||
|
* @copyright (c) 2010 Lazlo Westerhof - Netherlands
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
|
||||||
|
CREATE SEQUENCE event_ids
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MAXVALUE
|
||||||
|
NO MINVALUE
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
CREATE TABLE events (
|
||||||
|
event_id integer DEFAULT nextval('event_ids'::regclass) NOT NULL,
|
||||||
|
user_id integer NOT NULL,
|
||||||
|
"start" timestamp without time zone DEFAULT now() NOT NULL,
|
||||||
|
"end" timestamp without time zone DEFAULT now() NOT NULL,
|
||||||
|
"title" character varying(255) NOT NULL,
|
||||||
|
"description" text NOT NULL,
|
||||||
|
"location" character varying(255) NOT NULL,
|
||||||
|
"categories" character varying(255) NOT NULL,
|
||||||
|
"all_day" smallint NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX events_event_id_idx ON events USING btree (event_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Constraints Table `events`
|
||||||
|
--
|
||||||
|
ALTER TABLE ONLY events
|
||||||
|
ADD CONSTRAINT events_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(user_id) ON UPDATE CASCADE ON DELETE CASCADE;
|
58
plugins/calendar/drivers/database/sql/sqlite.sql
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* Roundcube Calendar
|
||||||
|
*
|
||||||
|
* Plugin to add a calendar to Roundcube.
|
||||||
|
*
|
||||||
|
* @version 0.3 beta
|
||||||
|
* @author Lazlo Westerhof
|
||||||
|
* @author Thomas Bruederli
|
||||||
|
* @author Albert Lee
|
||||||
|
* @url http://rc-calendar.lazlo.me
|
||||||
|
* @licence GNU GPL
|
||||||
|
* @copyright (c) 2010 Lazlo Westerhof - Netherlands
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
|
||||||
|
CREATE TABLE calendars (
|
||||||
|
calendar_id integer NOT NULL PRIMARY KEY,
|
||||||
|
user_id integer NOT NULL default '0',
|
||||||
|
name varchar(255) NOT NULL default '',
|
||||||
|
color varchar(255) NOT NULL default '',
|
||||||
|
CONSTRAINT fk_calendars_user_id FOREIGN KEY (user_id)
|
||||||
|
REFERENCES users(user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE events (
|
||||||
|
event_id integer NOT NULL PRIMARY KEY,
|
||||||
|
calendar_id integer NOT NULL default '0',
|
||||||
|
recurrence_id integer 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',
|
||||||
|
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 '',
|
||||||
|
all_day tinyint(1) NOT NULL default '0',
|
||||||
|
free_busy tinyint(1) NOT NULL default '0',
|
||||||
|
priority tinyint(1) NOT NULL default '1',
|
||||||
|
alarms varchar(255) default NULL,
|
||||||
|
attendees text default NULL,
|
||||||
|
CONSTRAINT fk_events_calendar_id FOREIGN KEY (calendar_id)
|
||||||
|
REFERENCES calendars(calendar_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE attachments (
|
||||||
|
attachment_id integer NOT NULL PRIMARY KEY,
|
||||||
|
event_id integer NOT NULL default '0',
|
||||||
|
filename varchar(255) NOT NULL default '',
|
||||||
|
mimetype varchar(255) NOT NULL default '',
|
||||||
|
size integer NOT NULL default '0',
|
||||||
|
data text NOT NULL default '',
|
||||||
|
CONSTRAINT fk_attachment_event_id FOREIGN KEY (event_id)
|
||||||
|
REFERENCES events(event_id)
|
||||||
|
);
|
||||||
|
|
176
plugins/calendar/drivers/kolab/kolab_calendar.php
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Kolab calendar storage class |
|
||||||
|
| |
|
||||||
|
| This program is free software; you can redistribute it and/or modify |
|
||||||
|
| it under the terms of the GNU General Public License version 2 |
|
||||||
|
| as published by the Free Software Foundation. |
|
||||||
|
| |
|
||||||
|
| PURPOSE: |
|
||||||
|
| Storage object for a single calendar folder on Kolab |
|
||||||
|
| |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Kolab_calendar
|
||||||
|
{
|
||||||
|
public $id;
|
||||||
|
public $ready = false;
|
||||||
|
public $readonly = true;
|
||||||
|
|
||||||
|
private $storage;
|
||||||
|
private $events;
|
||||||
|
private $id2uid;
|
||||||
|
private $imap_folder = 'INBOX/Calendar';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor
|
||||||
|
*/
|
||||||
|
public function __construct($imap_folder = null)
|
||||||
|
{
|
||||||
|
if ($imap_folder)
|
||||||
|
$this->imap_folder = $imap_folder;
|
||||||
|
|
||||||
|
// ID is derrived from folder name
|
||||||
|
$this->id = strtolower(asciiwords(strtr($this->imap_folder, '/.', '--')));
|
||||||
|
|
||||||
|
// fetch objects from the given IMAP folder
|
||||||
|
$this->storage = rcube_kolab::get_storage($this->imap_folder);
|
||||||
|
|
||||||
|
$this->ready = !PEAR::isError($this->storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter for a nice and human readable name for this calendar
|
||||||
|
* See http://wiki.kolab.org/UI-Concepts/Folder-Listing for reference
|
||||||
|
*
|
||||||
|
* @return string Name of this calendar
|
||||||
|
*/
|
||||||
|
public function get_name()
|
||||||
|
{
|
||||||
|
$dispname = preg_replace(array('!INBOX/Calendar/!', '!^INBOX/!', '!^shared/!', '!^user/([^/]+)/!'), array('','','','(\\1) '), $this->imap_folder);
|
||||||
|
return strlen($dispname) ? $dispname : $this->imap_folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return color to display this calendar
|
||||||
|
*/
|
||||||
|
public function get_color()
|
||||||
|
{
|
||||||
|
// TODO: read color from backend (not yet supported)
|
||||||
|
return '0000cc';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter for a single event object
|
||||||
|
*/
|
||||||
|
public function get_event($id)
|
||||||
|
{
|
||||||
|
$this->_fetch_events();
|
||||||
|
return $this->events[$id];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param integer Event's new start (unix timestamp)
|
||||||
|
* @param integer Event's new end (unix timestamp)
|
||||||
|
* @return array A list of event records
|
||||||
|
*/
|
||||||
|
public function list_events($start, $end)
|
||||||
|
{
|
||||||
|
$this->_fetch_events();
|
||||||
|
|
||||||
|
$events = array();
|
||||||
|
foreach ($this->events as $id => $event) {
|
||||||
|
// TODO: also list recurring events
|
||||||
|
if ($event['start'] >= $start && $event['end'] <= $end) {
|
||||||
|
$events[] = $event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $events;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event record
|
||||||
|
*
|
||||||
|
* @see Driver:new_event()
|
||||||
|
* @return mixed The created record ID on success, False on error
|
||||||
|
*/
|
||||||
|
public function insert_event($event)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a specific event record
|
||||||
|
*
|
||||||
|
* @see Driver:new_event()
|
||||||
|
* @return boolean True on success, False on error
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function update_event($event)
|
||||||
|
{
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simply fetch all records and store them in private member vars
|
||||||
|
* We thereby rely on cahcing done by the Horde classes
|
||||||
|
*/
|
||||||
|
private function _fetch_events()
|
||||||
|
{
|
||||||
|
if (!isset($this->events)) {
|
||||||
|
$this->events = array();
|
||||||
|
foreach ((array)$this->storage->getObjects() as $record) {
|
||||||
|
$event = $this->_to_rcube_event($record);
|
||||||
|
$this->events[$event['id']] = $event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert from Kolab_Format to internal representation
|
||||||
|
*/
|
||||||
|
private function _to_rcube_event($rec)
|
||||||
|
{
|
||||||
|
$start_time = date('H:i:s', $rec['start-date']);
|
||||||
|
$allday = $start_time == '00:00:00' && $start_time == date('H:i:s', $rec['end-date']);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'id' => $rec['uid'],
|
||||||
|
'uid' => $rec['uid'],
|
||||||
|
'title' => $rec['summary'],
|
||||||
|
'location' => $rec['location'],
|
||||||
|
'description' => $rec['body'],
|
||||||
|
'start' => $rec['start-date'],
|
||||||
|
'end' => $rec['end-date'],
|
||||||
|
'all_day' => $allday,
|
||||||
|
'categories' => $rec['categories'],
|
||||||
|
'free_busy' => $rec['show-time-as'],
|
||||||
|
'priority' => 1, // normal
|
||||||
|
'calendar' => $this->id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the given event record into a data structure that can be passed to Kolab_Storage backend for saving
|
||||||
|
* (opposite of self::_to_rcube_event())
|
||||||
|
*/
|
||||||
|
private function _from_rcube_eventt($event)
|
||||||
|
{
|
||||||
|
$object = array();
|
||||||
|
|
||||||
|
|
||||||
|
return $object;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
272
plugins/calendar/drivers/kolab/kolab_driver.php
Normal file
|
@ -0,0 +1,272 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Kolab driver for the Calendar Plugin |
|
||||||
|
| Version 0.3 beta |
|
||||||
|
| |
|
||||||
|
| This program is free software; you can redistribute it and/or modify |
|
||||||
|
| it under the terms of the GNU General Public License version 2 |
|
||||||
|
| as published by the Free Software Foundation. |
|
||||||
|
| |
|
||||||
|
| PURPOSE: |
|
||||||
|
| Kolab bindings for the calendar backend |
|
||||||
|
| |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once(dirname(__FILE__) . '/kolab_calendar.php');
|
||||||
|
|
||||||
|
class kolab_driver extends calendar_driver
|
||||||
|
{
|
||||||
|
// features this backend supports
|
||||||
|
public $attendees = false;
|
||||||
|
public $attachments = false;
|
||||||
|
|
||||||
|
private $rc;
|
||||||
|
private $cal;
|
||||||
|
private $calendars;
|
||||||
|
private $folders;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor
|
||||||
|
*/
|
||||||
|
public function __construct($cal)
|
||||||
|
{
|
||||||
|
$this->cal = $cal;
|
||||||
|
$this->rc = $cal->rc;
|
||||||
|
$this->_read_calendars();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read available calendars from server
|
||||||
|
*/
|
||||||
|
private function _read_calendars()
|
||||||
|
{
|
||||||
|
// already read sources
|
||||||
|
if (isset($this->calendars))
|
||||||
|
return $this->calendars;
|
||||||
|
|
||||||
|
// get all folders that have "event" type
|
||||||
|
$folders = rcube_kolab::get_folders('event');
|
||||||
|
$this->folders = $this->calendars = array();
|
||||||
|
|
||||||
|
if (PEAR::isError($folders)) {
|
||||||
|
raise_error(array(
|
||||||
|
'code' => 600, 'type' => 'php',
|
||||||
|
'file' => __FILE__, 'line' => __LINE__,
|
||||||
|
'message' => "Failed to list calendar folders from Kolab server:" . $folders->getMessage()),
|
||||||
|
true, false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
foreach ($folders as $c_folder) {
|
||||||
|
$calendar = new kolab_calendar($c_folder->name);
|
||||||
|
$this->folders[$calendar->id] = $calendar;
|
||||||
|
if ($calendar->ready) {
|
||||||
|
$this->calendars[$calendar->id] = array(
|
||||||
|
'id' => $calendar->id,
|
||||||
|
'name' => $calendar->get_name(),
|
||||||
|
'color' => $calendar->get_color(),
|
||||||
|
'readonly' => $c_folder->_owner != $_SESSION['username'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->calendars;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function _get_storage($cid, $readonly = false)
|
||||||
|
{
|
||||||
|
if ($readonly)
|
||||||
|
return $this->folders[$cid];
|
||||||
|
else if (!$this->calendars[$cid]['readonly'])
|
||||||
|
return $this->folders[$cid];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of available calendars from this source
|
||||||
|
*/
|
||||||
|
public function list_calendars()
|
||||||
|
{
|
||||||
|
// attempt to create a default calendar for this user
|
||||||
|
if (empty($this->calendars)) {
|
||||||
|
if ($this->create_calendar(array('name' => 'Default', 'color' => 'cc0000')))
|
||||||
|
$this->_read_calendars();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->calendars;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new calendar assigned to the current user
|
||||||
|
*
|
||||||
|
* @param array Hash array with calendar properties
|
||||||
|
* name: Calendar name
|
||||||
|
* color: The color of the calendar
|
||||||
|
* @return mixed ID of the calendar on success, False on error
|
||||||
|
*/
|
||||||
|
public function create_calendar($prop)
|
||||||
|
{
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a single event to the database
|
||||||
|
*
|
||||||
|
* @see Driver:new_event()
|
||||||
|
*/
|
||||||
|
public function new_event($event)
|
||||||
|
{
|
||||||
|
$cid = $event['calendar'] ? $event['calendar'] : reset(array_keys($this->calendars));
|
||||||
|
if ($storage = $this->_get_storage($cid))
|
||||||
|
return $storage->insert_event($event);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an event entry with the given data
|
||||||
|
*
|
||||||
|
* @see Driver:new_event()
|
||||||
|
* @return boolean True on success, False on error
|
||||||
|
*/
|
||||||
|
public function edit_event($event)
|
||||||
|
{
|
||||||
|
if ($storage = $this->_get_storage($event['calendar']))
|
||||||
|
return $storage->update_event($event);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move a single event
|
||||||
|
*
|
||||||
|
* @see Driver:move_event()
|
||||||
|
* @return boolean True on success, False on error
|
||||||
|
*/
|
||||||
|
public function move_event($event)
|
||||||
|
{
|
||||||
|
if (($storage = $this->_get_storage($event['calendar'])) && ($ev = $storage->get_event($event['id'])))
|
||||||
|
return $storage->update_event($event + $ev);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resize a single event
|
||||||
|
*
|
||||||
|
* @see Driver:resize_event()
|
||||||
|
* @return boolean True on success, False on error
|
||||||
|
*/
|
||||||
|
public function resize_event($event)
|
||||||
|
{
|
||||||
|
if (($storage = $this->_get_storage($event['calendar'])) && ($ev = $storage->get_event($event['id'])))
|
||||||
|
return $storage->update_event($event + $ev);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a single event from the database
|
||||||
|
*
|
||||||
|
* @param array Hash array with event properties:
|
||||||
|
* id: Event identifier
|
||||||
|
* @return boolean True on success, False on error
|
||||||
|
*/
|
||||||
|
public function remove_event($event)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get events from source.
|
||||||
|
*
|
||||||
|
* @param integer Event's new start (unix timestamp)
|
||||||
|
* @param integer Event's new end (unix timestamp)
|
||||||
|
* @param mixed List of calendar IDs to load events from (either as array or comma-separated string)
|
||||||
|
* @return array A list of event records
|
||||||
|
*/
|
||||||
|
public function load_events($start, $end, $calendars = null)
|
||||||
|
{
|
||||||
|
if ($calendars && is_string($calendars))
|
||||||
|
$calendars = explode(',', $calendars);
|
||||||
|
|
||||||
|
$events = array();
|
||||||
|
foreach ($this->calendars as $cid => $calendar) {
|
||||||
|
if ($calendars && !in_array($cid, $calendars))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$events = array_merge($this->folders[$cid]->list_events($start, $end));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $events;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search events using the given query
|
||||||
|
*
|
||||||
|
* @see Driver::search_events()
|
||||||
|
* @return array A list of event records
|
||||||
|
*/
|
||||||
|
public function search_events($start, $end, $query, $calendars = null)
|
||||||
|
{
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save an attachment related to the given event
|
||||||
|
*/
|
||||||
|
public function add_attachment($attachment, $event_id)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a specific attachment from the given event
|
||||||
|
*/
|
||||||
|
public function remove_attachment($attachment, $event_id)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new category
|
||||||
|
*/
|
||||||
|
public function add_category($name, $color)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the given category
|
||||||
|
*/
|
||||||
|
public function remove_category($name)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update/replace a category
|
||||||
|
*/
|
||||||
|
public function replace_category($oldname, $name, $color)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch free/busy information from a person within the given range
|
||||||
|
*/
|
||||||
|
public function get_freebusy_list($email, $start, $end)
|
||||||
|
{
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
82
plugins/calendar/lib/calendar_ical.php
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| iCalendar functions for the Calendar Plugin |
|
||||||
|
| Version 0.3 beta |
|
||||||
|
| |
|
||||||
|
| This program is free software; you can redistribute it and/or modify |
|
||||||
|
| it under the terms of the GNU General Public License version 2 |
|
||||||
|
| as published by the Free Software Foundation. |
|
||||||
|
| |
|
||||||
|
| 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 General Public License for more details. |
|
||||||
|
| |
|
||||||
|
| You should have received a copy of the GNU General Public License along |
|
||||||
|
| with this program; if not, write to the Free Software Foundation, Inc., |
|
||||||
|
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
|
||||||
|
| |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Author: Lazlo Westerhof <hello@lazlo.me> |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
class calendar_ical
|
||||||
|
{
|
||||||
|
private $rc;
|
||||||
|
private $driver;
|
||||||
|
|
||||||
|
function __construct($rc, $driver) {
|
||||||
|
$this->rc = $rc;
|
||||||
|
$this->driver = $driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import events from iCalendar format
|
||||||
|
*
|
||||||
|
* @param array Associative events array
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
public function import($events) {
|
||||||
|
//TODO
|
||||||
|
// for ($events as $event)
|
||||||
|
// $this->backend->newEvent(...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export events to iCalendar format
|
||||||
|
*
|
||||||
|
* @param array Events as array
|
||||||
|
* @return string Events in iCalendar format (http://tools.ietf.org/html/rfc5545)
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
public function export($events) {
|
||||||
|
if (!empty($this->rc->user->ID)) {
|
||||||
|
$ical = "BEGIN:VCALENDAR\n";
|
||||||
|
$ical .= "VERSION:2.0\n";
|
||||||
|
$ical .= "PRODID:-//Roundcube Webmail//NONSGML Calendar//EN\n";
|
||||||
|
foreach ($events as $event) {
|
||||||
|
$ical .= "BEGIN:VEVENT\n";
|
||||||
|
$ical .= "DTSTART:" . date('Ymd\THis\Z', $event['start'] - date('Z')) . "\n";
|
||||||
|
$ical .= "DTEND:" . date('Ymd\THis\Z', $event['end'] - date('Z')) . "\n";
|
||||||
|
$ical .= "SUMMARY:" . $event['title'] . "\n";
|
||||||
|
$ical .= "DESCRIPTION:" . $event['description'] . "\n";
|
||||||
|
if (!empty($event['location'])) {
|
||||||
|
$ical .= "LOCATION:" . $event['location'] . "\n";
|
||||||
|
}
|
||||||
|
if(!empty($event['categories'])) {
|
||||||
|
$ical .= "CATEGORIES:" . strtoupper($event['categories']) . "\n";
|
||||||
|
}
|
||||||
|
if ($event['private']) {
|
||||||
|
$ical .= 'X-CALENDARSERVER-ACCESS:CONFIDENTIAL';
|
||||||
|
}
|
||||||
|
$ical .= 'TRANSP:' . ($event['free_busy'] == 'free' ? 'TRANSPARENT' : 'OPAQUE');
|
||||||
|
$ical .= "END:VEVENT\n";
|
||||||
|
}
|
||||||
|
$ical .= "END:VCALENDAR";
|
||||||
|
|
||||||
|
return $ical;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
394
plugins/calendar/lib/calendar_ui.php
Normal file
|
@ -0,0 +1,394 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| User Interface for the Calendar Plugin |
|
||||||
|
| Version 0.3 beta |
|
||||||
|
| |
|
||||||
|
| This program is free software; you can redistribute it and/or modify |
|
||||||
|
| it under the terms of the GNU General Public License version 2 |
|
||||||
|
| as published by the Free Software Foundation. |
|
||||||
|
| |
|
||||||
|
| 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 General Public License for more details. |
|
||||||
|
| |
|
||||||
|
| You should have received a copy of the GNU General Public License along |
|
||||||
|
| with this program; if not, write to the Free Software Foundation, Inc., |
|
||||||
|
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
|
||||||
|
| |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Author: Lazlo Westerhof <hello@lazlo.me> |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
class calendar_ui
|
||||||
|
{
|
||||||
|
private $rc;
|
||||||
|
private $calendar;
|
||||||
|
|
||||||
|
function __construct($calendar)
|
||||||
|
{
|
||||||
|
$this->calendar = $calendar;
|
||||||
|
$this->rc = $calendar->rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calendar UI initialization and requests handlers
|
||||||
|
*/
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
// add taskbar button
|
||||||
|
$this->calendar->add_button(array(
|
||||||
|
'name' => 'calendar',
|
||||||
|
'class' => 'button-calendar',
|
||||||
|
'label' => 'calendar.calendar',
|
||||||
|
'href' => './?_task=calendar',
|
||||||
|
), 'taskbar');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds CSS stylesheets to the page header
|
||||||
|
*/
|
||||||
|
public function addCSS()
|
||||||
|
{
|
||||||
|
$skin = $this->rc->config->get('skin');
|
||||||
|
$this->calendar->include_stylesheet('skins/' . $skin . '/fullcalendar.css');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds JS files to the page header
|
||||||
|
*/
|
||||||
|
public function addJS()
|
||||||
|
{
|
||||||
|
$this->calendar->include_script('lib/js/fullcalendar.js');
|
||||||
|
$this->calendar->include_script('calendar.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the Calendar toolbar
|
||||||
|
*/
|
||||||
|
public function toolbar()
|
||||||
|
{
|
||||||
|
$skin = $this->rc->config->get('skin');
|
||||||
|
$this->calendar->add_button(array(
|
||||||
|
'command' => 'plugin.export_events',
|
||||||
|
'href' => './?_task=calendar&_action=plugin.export_events',
|
||||||
|
'title' => 'calendar.export',
|
||||||
|
'imagepas' => 'skins/' . $skin . '/images/export.png',
|
||||||
|
'imageact' => 'skins/' . $skin . '/images/export.png'),
|
||||||
|
'toolbar'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function calendar_css()
|
||||||
|
{
|
||||||
|
$categories = $this->rc->config->get('calendar_categories', array());
|
||||||
|
|
||||||
|
$css = "\n";
|
||||||
|
|
||||||
|
foreach ((array)$categories as $class => $color) {
|
||||||
|
$class = 'cat-' . asciiwords($class, true);
|
||||||
|
$css .= "." . $class . ",\n";
|
||||||
|
$css .= ".fc-event-" . $class . ",\n";
|
||||||
|
$css .= "." . $class . " a {\n";
|
||||||
|
$css .= "color: #" . $color . ";\n";
|
||||||
|
$css .= "border-color: #" . $color . ";\n";
|
||||||
|
$css .= "}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$calendars = $this->calendar->driver->list_calendars();
|
||||||
|
foreach ((array)$calendars as $id => $prop) {
|
||||||
|
if (!$prop['color'])
|
||||||
|
continue;
|
||||||
|
$color = $prop['color'];
|
||||||
|
$class = 'cal-' . asciiwords($id, true);
|
||||||
|
$css .= "li." . $class . ", ";
|
||||||
|
$css .= "#eventshow ." . $class . " { ";
|
||||||
|
$css .= "color: #" . $color . " }\n";
|
||||||
|
$css .= ".fc-event-" . $class . ", ";
|
||||||
|
$css .= ".fc-event-" . $class . " .fc-event-inner, ";
|
||||||
|
$css .= ".fc-event-" . $class . " .fc-event-time {\n";
|
||||||
|
$css .= "background-color: #" . $color . ";\n";
|
||||||
|
$css .= "border-color: #" . $color . ";\n";
|
||||||
|
$css .= "}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return html::tag('style', array('type' => 'text/css'), $css);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function calendar_list($attrib = array())
|
||||||
|
{
|
||||||
|
$calendars = $this->calendar->driver->list_calendars();
|
||||||
|
|
||||||
|
$li = '';
|
||||||
|
foreach ((array)$calendars as $id => $prop) {
|
||||||
|
unset($prop['user_id']);
|
||||||
|
$prop['attendees'] = $this->calendar->driver->attendees;
|
||||||
|
$prop['attachments'] = $this->calendar->driver->attachments;
|
||||||
|
$jsenv[$id] = $prop;
|
||||||
|
|
||||||
|
$html_id = html_identifier($id);
|
||||||
|
$li .= html::tag('li', array('id' => 'rcmlical' . $html_id, 'class' =>'cal-' . asciiwords($id, true)),
|
||||||
|
html::tag('input', array('type' => 'checkbox', 'name' => '_cal[]', 'value' => $id, 'checked' => true), '') . html::span(null, Q($prop['name'])));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->rc->output->set_env('calendars', $jsenv);
|
||||||
|
$this->rc->output->add_gui_object('folderlist', $attrib['id']);
|
||||||
|
|
||||||
|
unset($attrib['name']);
|
||||||
|
return html::tag('ul', $attrib, $li);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a HTML select box for calendar selection
|
||||||
|
*/
|
||||||
|
function calendar_select($attrib = array())
|
||||||
|
{
|
||||||
|
$attrib['name'] = 'calendar';
|
||||||
|
$select = new html_select($attrib);
|
||||||
|
foreach ((array)$this->calendar->driver->list_calendars() as $id => $prop) {
|
||||||
|
$select->add($prop['name'], $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $select->show(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a HTML select box to select an event category
|
||||||
|
*/
|
||||||
|
function category_select($attrib = array())
|
||||||
|
{
|
||||||
|
$attrib['name'] = 'categories';
|
||||||
|
$select = new html_select($attrib);
|
||||||
|
$select->add('---', '');
|
||||||
|
foreach ((array)$this->calendar->driver->list_categories() as $cat => $color) {
|
||||||
|
$select->add($cat, $cat);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $select->show(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a HTML select box for free/busy/out-of-office property
|
||||||
|
*/
|
||||||
|
function freebusy_select($attrib = array())
|
||||||
|
{
|
||||||
|
$attrib['name'] = 'freebusy';
|
||||||
|
$select = new html_select($attrib);
|
||||||
|
$select->add($this->calendar->gettext('free'), 'free');
|
||||||
|
$select->add($this->calendar->gettext('busy'), 'busy');
|
||||||
|
$select->add($this->calendar->gettext('outofoffice'), 'outofoffice');
|
||||||
|
return $select->show(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a HTML select for event priorities
|
||||||
|
*/
|
||||||
|
function priority_select($attrib = array())
|
||||||
|
{
|
||||||
|
$attrib['name'] = 'priority';
|
||||||
|
$select = new html_select($attrib);
|
||||||
|
$select->add($this->calendar->gettext('normal'), '1');
|
||||||
|
$select->add($this->calendar->gettext('low'), '0');
|
||||||
|
$select->add($this->calendar->gettext('high'), '2');
|
||||||
|
return $select->show(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render HTML form for alarm configuration
|
||||||
|
*/
|
||||||
|
function alarm_select($attrib = array())
|
||||||
|
{
|
||||||
|
unset($attrib['name']);
|
||||||
|
$select_type = new html_select(array('name' => 'alarmtype[]', 'class' => 'edit-alarm-type'));
|
||||||
|
$select_type->add(
|
||||||
|
array($this->calendar->gettext('none'), $this->calendar->gettext('showmessage'), $this->calendar->gettext('byemail')),
|
||||||
|
array('','message','email'));
|
||||||
|
|
||||||
|
$input_value = new html_inputfield(array('name' => 'alarmvalue[]', 'class' => 'edit-alarm-value', 'size' => 3));
|
||||||
|
$input_date = new html_inputfield(array('name' => 'alarmdate[]', 'class' => 'edit-alarm-date', 'size' => 10));
|
||||||
|
$input_time = new html_inputfield(array('name' => 'alarmtime[]', 'class' => 'edit-alarm-time', 'size' => 6));
|
||||||
|
|
||||||
|
$select_offset = new html_select(array('name' => 'alarmoffset[]', 'class' => 'edit-alarm-offset'));
|
||||||
|
$select_offset->add(
|
||||||
|
array(
|
||||||
|
$this->calendar->gettext('minutesbefore'), $this->calendar->gettext('hoursbefore'), $this->calendar->gettext('daysbefore'),
|
||||||
|
$this->calendar->gettext('minutesafter'), $this->calendar->gettext('hoursafter'), $this->calendar->gettext('daysafter'),
|
||||||
|
$this->calendar->gettext('ondate'),
|
||||||
|
),
|
||||||
|
array('-m','-h','-d','+m','+h','+d','@'));
|
||||||
|
|
||||||
|
// TODO: pre-set with default values from user settings
|
||||||
|
$hidden = array('style' => 'display:none');
|
||||||
|
$html = html::span('edit-alarm-set',
|
||||||
|
$select_type->show('') . ' ' .
|
||||||
|
html::span(array('class' => 'edit-alarm-values', 'style' => 'display:none'),
|
||||||
|
$input_value->show(15) . ' ' .
|
||||||
|
$select_offset->show('-m') . ' ' .
|
||||||
|
$input_date->show('', $hidden) . ' ' .
|
||||||
|
$input_time->show('', $hidden)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: support adding more alarms
|
||||||
|
#$html .= html::a(array('href' => '#', 'id' => 'edit-alam-add', 'title' => $this->calendar->gettext('addalarm')),
|
||||||
|
# $attrib['addicon'] ? html::img(array('src' => $attrib['addicon'], 'alt' => 'add')) : '(+)');
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the form for recurrence settings
|
||||||
|
*/
|
||||||
|
function recurrence_form($attrib = array())
|
||||||
|
{
|
||||||
|
switch ($attrib['part']) {
|
||||||
|
// frequency selector
|
||||||
|
case 'frequency':
|
||||||
|
$select = new html_select(array('name' => 'frequency', 'id' => 'edit-recurrence-frequency'));
|
||||||
|
$select->add($this->calendar->gettext('never'), '');
|
||||||
|
$select->add($this->calendar->gettext('daily'), 'DAILY');
|
||||||
|
$select->add($this->calendar->gettext('weekly'), 'WEEKLY');
|
||||||
|
$select->add($this->calendar->gettext('monthly'), 'MONTHLY');
|
||||||
|
$select->add($this->calendar->gettext('yearly'), 'YEARLY');
|
||||||
|
$html = html::label('edit-frequency', $this->calendar->gettext('frequency')) . $select->show('');
|
||||||
|
break;
|
||||||
|
|
||||||
|
// daily recurrence
|
||||||
|
case 'daily':
|
||||||
|
$select = $this->interval_selector(array('name' => 'interval', 'class' => 'edit-recurrence-interval', 'id' => 'edit-recurrence-interval-daily'));
|
||||||
|
$html = html::div($attrib, html::label(null, $this->calendar->gettext('every')) . $select->show(1) . html::span('label-after', $this->calendar->gettext('days')));
|
||||||
|
break;
|
||||||
|
|
||||||
|
// weekly recurrence form
|
||||||
|
case 'weekly':
|
||||||
|
$select = $this->interval_selector(array('name' => 'interval', 'class' => 'edit-recurrence-interval', 'id' => 'edit-recurrence-interval-weekly'));
|
||||||
|
$html = html::div($attrib, html::label(null, $this->calendar->gettext('every')) . $select->show(1) . html::span('label-after', $this->calendar->gettext('weeks')));
|
||||||
|
// weekday selection
|
||||||
|
$daymap = array('sun','mon','tue','wed','thu','fri','sat');
|
||||||
|
$checkbox = new html_checkbox(array('name' => 'byday', 'class' => 'edit-recurrence-weekly-byday'));
|
||||||
|
$first = $this->rc->config->get('calendar_first_day', 1);
|
||||||
|
for ($weekdays = '', $j = $first; $j <= $first+6; $j++) {
|
||||||
|
$d = $j % 7;
|
||||||
|
$weekdays .= html::label(array('class' => 'weekday'), $checkbox->show('', array('value' => strtoupper(substr($daymap[$d], 0, 2)))) . $this->calendar->gettext($daymap[$d])) . ' ';
|
||||||
|
}
|
||||||
|
$html .= html::div($attrib, html::label(null, $this->calendar->gettext('bydays')) . $weekdays);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// monthly recurrence form
|
||||||
|
case 'monthly':
|
||||||
|
$select = $this->interval_selector(array('name' => 'interval', 'class' => 'edit-recurrence-interval', 'id' => 'edit-recurrence-interval-monthly'));
|
||||||
|
$html = html::div($attrib, html::label(null, $this->calendar->gettext('every')) . $select->show(1) . html::span('label-after', $this->calendar->gettext('months')));
|
||||||
|
// day of month selection
|
||||||
|
$checkbox = new html_checkbox(array('name' => 'bymonthday', 'class' => 'edit-recurrence-monthly-bymonthday'));
|
||||||
|
for ($monthdays = '', $d = 1; $d <= 31; $d++) {
|
||||||
|
$monthdays .= html::label(array('class' => 'monthday'), $checkbox->show('', array('value' => $d)) . $d);
|
||||||
|
$monthdays .= $d % 7 ? ' ' : html::br();
|
||||||
|
}
|
||||||
|
|
||||||
|
// rule selectors
|
||||||
|
$radio = new html_radiobutton(array('name' => 'repeatmode', 'class' => 'edit-recurrence-monthly-mode'));
|
||||||
|
$table = new html_table(array('cols' => 2, 'border' => 0, 'cellpadding' => 0, 'class' => 'formtable'));
|
||||||
|
$table->add('label topalign', html::label(null, $radio->show('BYMONTHDAY', array('value' => 'BYMONTHDAY')) . ' ' . $this->calendar->gettext('each')));
|
||||||
|
$table->add(null, $monthdays);
|
||||||
|
$table->add('label', html::label(null, $radio->show('', array('value' => 'BYDAY')) . ' ' . $this->calendar->gettext('onevery')));
|
||||||
|
$table->add(null, $this->rrule_selectors($attrib['part']));
|
||||||
|
|
||||||
|
$html .= html::div($attrib, $table->show());
|
||||||
|
break;
|
||||||
|
|
||||||
|
// annually recurrence form
|
||||||
|
case 'yearly':
|
||||||
|
$select = $this->interval_selector(array('name' => 'interval', 'class' => 'edit-recurrence-interval', 'id' => 'edit-recurrence-interval-yearly'));
|
||||||
|
$html = html::div($attrib, html::label(null, $this->calendar->gettext('every')) . $select->show(1) . html::span('label-after', $this->calendar->gettext('years')));
|
||||||
|
// month selector
|
||||||
|
$monthmap = array('','jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec');
|
||||||
|
$checkbox = new html_checkbox(array('name' => 'bymonth', 'class' => 'edit-recurrence-yearly-bymonth'));
|
||||||
|
for ($months = '', $m = 1; $m <= 12; $m++) {
|
||||||
|
$months .= html::label(array('class' => 'month'), $checkbox->show('', array('value' => $m)) . $this->calendar->gettext($monthmap[$m]));
|
||||||
|
$months .= $m % 4 ? ' ' : html::br();
|
||||||
|
}
|
||||||
|
$html .= html::div($attrib + array('id' => 'edit-recurrence-yearly-bymonthblock'), $months);
|
||||||
|
|
||||||
|
// day rule selection
|
||||||
|
$html .= html::div($attrib, html::label(null, $this->calendar->gettext('onevery')) . $this->rrule_selectors($attrib['part'], '---'));
|
||||||
|
break;
|
||||||
|
|
||||||
|
// end of recurrence form
|
||||||
|
case 'until':
|
||||||
|
$radio = new html_radiobutton(array('name' => 'repeat', 'class' => 'edit-recurrence-until'));
|
||||||
|
$select = $this->interval_selector(array('name' => 'times', 'id' => 'edit-recurrence-repeat-times'));
|
||||||
|
$input = new html_inputfield(array('name' => 'untildate', 'id' => 'edit-recurrence-enddate', 'size' => "10"));
|
||||||
|
|
||||||
|
$table = new html_table(array('cols' => 2, 'border' => 0, 'cellpadding' => 0, 'class' => 'formtable'));
|
||||||
|
|
||||||
|
$table->add('label', $this->calendar->gettext('recurrencend'));
|
||||||
|
$table->add(null, html::label(null, $radio->show('', array('value' => '', 'id' => 'edit-recurrence-repeat-forever')) . ' ' .
|
||||||
|
$this->calendar->gettext('forever')));
|
||||||
|
|
||||||
|
$table->add('label', '');
|
||||||
|
$table->add(null, html::label(null, $radio->show('', array('value' => 'count', 'id' => 'edit-recurrence-repeat-count')) . ' ' .
|
||||||
|
$this->calendar->gettext(array(
|
||||||
|
'name' => 'forntimes',
|
||||||
|
'vars' => array('nr' => $select->show(1)))
|
||||||
|
)));
|
||||||
|
|
||||||
|
$table->add('label', '');
|
||||||
|
$table->add(null, $radio->show('', array('value' => 'until', 'id' => 'edit-recurrence-repeat-until')) . ' ' .
|
||||||
|
$this->calendar->gettext('until') . ' ' . $input->show(''));
|
||||||
|
$html = $table->show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input field for interval selection
|
||||||
|
*/
|
||||||
|
private function interval_selector($attrib)
|
||||||
|
{
|
||||||
|
$select = new html_select($attrib);
|
||||||
|
$select->add(range(1,30), range(1,30));
|
||||||
|
return $select;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop-down menus for recurrence rules like "each last sunday of"
|
||||||
|
*/
|
||||||
|
private function rrule_selectors($part, $noselect = null)
|
||||||
|
{
|
||||||
|
// rule selectors
|
||||||
|
$select_prefix = new html_select(array('name' => 'bydayprefix', 'id' => "edit-recurrence-$part-prefix"));
|
||||||
|
if ($noselect) $select_prefix->add($noselect, '');
|
||||||
|
$select_prefix->add(array(
|
||||||
|
$this->calendar->gettext('first'),
|
||||||
|
$this->calendar->gettext('second'),
|
||||||
|
$this->calendar->gettext('third'),
|
||||||
|
$this->calendar->gettext('fourth'),
|
||||||
|
$this->calendar->gettext('last')
|
||||||
|
),
|
||||||
|
array(1, 2, 3, 4, -1));
|
||||||
|
|
||||||
|
$select_wday = new html_select(array('name' => 'byday', 'id' => "edit-recurrence-$part-byday"));
|
||||||
|
if ($noselect) $select_wday->add($noselect, '');
|
||||||
|
|
||||||
|
$daymap = array('sunday','monday','tuesday','wednesday','thursday','friday','saturday');
|
||||||
|
$first = $this->rc->config->get('calendar_first_day', 1);
|
||||||
|
for ($j = $first; $j <= $first+6; $j++) {
|
||||||
|
$d = $j % 7;
|
||||||
|
$select_wday->add($this->calendar->gettext($daymap[$d]), strtoupper(substr($daymap[$d], 0, 2)));
|
||||||
|
}
|
||||||
|
if ($part == 'monthly')
|
||||||
|
$select_wday->add($this->calendar->gettext('dayofmonth'), '');
|
||||||
|
|
||||||
|
return $select_prefix->show() . ' ' . $select_wday->show();
|
||||||
|
}
|
||||||
|
}
|
5208
plugins/calendar/lib/js/fullcalendar.js
Normal file
38
plugins/calendar/localization/bg_BG.inc
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RoundCube Calendar
|
||||||
|
*
|
||||||
|
* Plugin to add a calendar to RoundCube.
|
||||||
|
*
|
||||||
|
* @version 0.2 BETA 2
|
||||||
|
* @author Lazlo Westerhof
|
||||||
|
* @url http://rc-calendar.lazlo.me
|
||||||
|
* @licence GNU GPL
|
||||||
|
* @copyright (c) 2010 Lazlo Westerhof - Netherlands
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
|
||||||
|
$labels = array();
|
||||||
|
|
||||||
|
// config
|
||||||
|
$labels['default_view'] = 'Изглед';
|
||||||
|
$labels['time_format'] = 'Формат на часовете';
|
||||||
|
$labels['timeslots'] = 'Полета на час';
|
||||||
|
$labels['first_day'] = 'Първи ден от седмицата';
|
||||||
|
|
||||||
|
// calendar
|
||||||
|
$labels['calendar'] = 'Календар';
|
||||||
|
$labels['day'] = 'Ден';
|
||||||
|
$labels['week'] = 'Седмица';
|
||||||
|
$labels['month'] = 'Месец';
|
||||||
|
$labels['new_event'] = 'Добавяне на събитие';
|
||||||
|
$labels['edit_event'] = 'Промяна на събитие';
|
||||||
|
$labels['save'] = 'Запис';
|
||||||
|
$labels['remove'] = 'Изтриване';
|
||||||
|
$labels['cancel'] = 'Отказ';
|
||||||
|
$labels['title'] = 'Заглавие';
|
||||||
|
$labels['description'] = 'Описание';
|
||||||
|
$labels['all-day'] = 'цял ден';
|
||||||
|
$labels['export'] = 'Запази като ICS';
|
||||||
|
$labels['category'] = 'Категория';
|
||||||
|
?>
|
38
plugins/calendar/localization/cs_CZ.inc
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RoundCube Calendar
|
||||||
|
*
|
||||||
|
* Plugin to add a calendar to RoundCube.
|
||||||
|
*
|
||||||
|
* @version 0.2 BETA 2
|
||||||
|
* @author Lazlo Westerhof
|
||||||
|
* @url http://rc-calendar.lazlo.me
|
||||||
|
* @licence GNU GPL
|
||||||
|
* @copyright (c) 2010 Lazlo Westerhof - Netherlands
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
|
||||||
|
$labels = array();
|
||||||
|
|
||||||
|
// config
|
||||||
|
$labels['default_view'] = 'Výchozí pohled';
|
||||||
|
$labels['time_format'] = 'Formát data';
|
||||||
|
$labels['timeslots'] = 'Slotů na hodinu';
|
||||||
|
$labels['first_day'] = 'První den v týdnu';
|
||||||
|
|
||||||
|
// calendar
|
||||||
|
$labels['calendar'] = 'Kalendář';
|
||||||
|
$labels['day'] = 'Den';
|
||||||
|
$labels['week'] = 'Týden';
|
||||||
|
$labels['month'] = 'Měsíc';
|
||||||
|
$labels['new_event'] = 'Nová událost';
|
||||||
|
$labels['edit_event'] = 'Editovat událost';
|
||||||
|
$labels['save'] = 'Uložit';
|
||||||
|
$labels['remove'] = 'Odstranit';
|
||||||
|
$labels['cancel'] = 'Storno';
|
||||||
|
$labels['title'] = 'Souhrn';
|
||||||
|
$labels['description'] = 'Popis';
|
||||||
|
$labels['all-day'] = 'celý den';
|
||||||
|
$labels['export'] = 'Exportovat do ICS';
|
||||||
|
$labels['category'] = 'Kategorie';
|
||||||
|
?>
|
51
plugins/calendar/localization/de_DE.inc
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RoundCube Calendar
|
||||||
|
*
|
||||||
|
* Plugin to add a calendar to RoundCube.
|
||||||
|
*
|
||||||
|
* @version 0.2 BETA 2
|
||||||
|
* @author Lazlo Westerhof
|
||||||
|
* @url http://rc-calendar.lazlo.me
|
||||||
|
* @licence GNU GPL
|
||||||
|
* @copyright (c) 2010 Lazlo Westerhof - Netherlands
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
|
||||||
|
$labels = array();
|
||||||
|
|
||||||
|
// config
|
||||||
|
$labels['default_view'] = 'Ansicht';
|
||||||
|
$labels['time_format'] = 'Zeitformatierung';
|
||||||
|
$labels['timeslots'] = 'Zeitfenster pro Stunde';
|
||||||
|
$labels['first_day'] = 'Erster Wochentag';
|
||||||
|
|
||||||
|
// calendar
|
||||||
|
$labels['calendar'] = 'Kalender';
|
||||||
|
$labels['day'] = 'Tag';
|
||||||
|
$labels['week'] = 'Woche';
|
||||||
|
$labels['month'] = 'Monat';
|
||||||
|
$labels['new_event'] = 'Neuer Termin';
|
||||||
|
$labels['edit_event'] = 'Termin bearbeiten';
|
||||||
|
$labels['save'] = 'Speichern';
|
||||||
|
$labels['remove'] = 'Löschen';
|
||||||
|
$labels['cancel'] = 'Abbrechen';
|
||||||
|
$labels['edit'] = 'Bearbeiten';
|
||||||
|
$labels['title'] = 'Titel';
|
||||||
|
$labels['description'] = 'Beschreibung';
|
||||||
|
$labels['all-day'] = 'ganztägig';
|
||||||
|
$labels['export'] = 'Als ICS exportieren';
|
||||||
|
$labels['category'] = 'Kategorie';
|
||||||
|
$labels['location'] = 'Ort';
|
||||||
|
$labels['date'] = 'Datum';
|
||||||
|
$labels['start'] = 'Beginn';
|
||||||
|
$labels['end'] = 'Ende';
|
||||||
|
$labels['toggle_view'] = 'Ansicht wechseln';
|
||||||
|
$labels['generated'] = 'erstellt am';
|
||||||
|
$labels['selectdate'] = 'Datum auswählen';
|
||||||
|
$labels['prev_year'] = 'Vorheriges Jahr';
|
||||||
|
$labels['prev_month'] = 'Vorheriger Monat';
|
||||||
|
$labels['next_year'] = 'Nächstes Jahr';
|
||||||
|
$labels['next_month'] = 'Nächster Monat';
|
||||||
|
$labels['choose_date'] = 'Datum auswählen';
|
||||||
|
?>
|
99
plugins/calendar/localization/en_US.inc
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
<?php
|
||||||
|
$labels = array();
|
||||||
|
|
||||||
|
// preferences
|
||||||
|
$labels['default_view'] = 'Default view';
|
||||||
|
$labels['time_format'] = 'Time format';
|
||||||
|
$labels['timeslots'] = 'Timeslots per hour';
|
||||||
|
$labels['first_day'] = 'First weekday';
|
||||||
|
$labels['add_category'] = 'Add category';
|
||||||
|
$labels['remove_category'] = 'Remove category';
|
||||||
|
$labels['add_calendar'] = 'Add calendar';
|
||||||
|
$labels['remove_calendar'] = 'Remove calendar';
|
||||||
|
|
||||||
|
// calendar
|
||||||
|
$labels['calendar'] = 'Calendar';
|
||||||
|
$labels['calendars'] = 'Calendars';
|
||||||
|
$labels['category'] = 'Category';
|
||||||
|
$labels['categories'] = 'Categories';
|
||||||
|
$labels['createcalendar'] = 'Create new calendar';
|
||||||
|
$labels['day'] = 'Day';
|
||||||
|
$labels['week'] = 'Week';
|
||||||
|
$labels['month'] = 'Month';
|
||||||
|
$labels['new_event'] = 'New event';
|
||||||
|
$labels['edit_event'] = 'Edit event';
|
||||||
|
$labels['edit'] = 'Edit';
|
||||||
|
$labels['save'] = 'Save';
|
||||||
|
$labels['remove'] = 'Remove';
|
||||||
|
$labels['cancel'] = 'Cancel';
|
||||||
|
$labels['title'] = 'Summary';
|
||||||
|
$labels['description'] = 'Description';
|
||||||
|
$labels['all-day'] = 'all-day';
|
||||||
|
$labels['export'] = 'Export to ICS';
|
||||||
|
$labels['location'] = 'Location';
|
||||||
|
$labels['date'] = 'Date';
|
||||||
|
$labels['start'] = 'Start';
|
||||||
|
$labels['end'] = 'End';
|
||||||
|
$labels['choose_date'] = 'Choose date';
|
||||||
|
$labels['freebusy'] = 'Show as';
|
||||||
|
$labels['free'] = 'Free';
|
||||||
|
$labels['busy'] = 'Busy';
|
||||||
|
$labels['outofoffice'] = 'Out of Office';
|
||||||
|
$labels['priority'] = 'Priority';
|
||||||
|
$labels['alarms'] = 'Reminder';
|
||||||
|
$labels['generated'] = 'generated at';
|
||||||
|
$labels['selectdate'] = 'Select date';
|
||||||
|
$labels['prev_year'] = 'Previous year';
|
||||||
|
$labels['prev_month'] = 'Previous month';
|
||||||
|
$labels['next_year'] = 'Next year';
|
||||||
|
$labels['next_month'] = 'Next month';
|
||||||
|
$labels['choose_date'] = 'Choose date';
|
||||||
|
|
||||||
|
$labels['showmessage'] = 'Message';
|
||||||
|
$labels['byemail'] = 'E-mail';
|
||||||
|
$labels['ondate'] = 'on date';
|
||||||
|
$labels['minutesbefore'] = 'minutes before';
|
||||||
|
$labels['hoursbefore'] = 'hours before';
|
||||||
|
$labels['daysbefore'] = 'days before';
|
||||||
|
$labels['minutesafter'] = 'minutes after';
|
||||||
|
$labels['hoursafter'] = 'hours after';
|
||||||
|
$labels['daysafter'] = 'days after';
|
||||||
|
$labels['addalarm'] = 'add alarm';
|
||||||
|
|
||||||
|
$labels['tabsummary'] = 'Summary';
|
||||||
|
$labels['tabrecurrence'] = 'Recurrence';
|
||||||
|
$labels['tabattendees'] = 'Participants';
|
||||||
|
$labels['tabattachments'] = 'Attachments';
|
||||||
|
|
||||||
|
// messages
|
||||||
|
$labels['deleteventconfirm'] = "Do you really want to delete this event?";
|
||||||
|
$labels['errorsaving'] = "Failed to save changes";
|
||||||
|
$labels['operationfailed'] = "The requested operation failed";
|
||||||
|
|
||||||
|
// recurrence form
|
||||||
|
$labels['frequency'] = 'Repeat';
|
||||||
|
$labels['never'] = 'never';
|
||||||
|
$labels['daily'] = 'daily';
|
||||||
|
$labels['weekly'] = 'weekly';
|
||||||
|
$labels['monthly'] = 'monthly';
|
||||||
|
$labels['yearly'] = 'annually';
|
||||||
|
$labels['every'] = 'Every';
|
||||||
|
$labels['days'] = 'day(s)';
|
||||||
|
$labels['weeks'] = 'week(s)';
|
||||||
|
$labels['months'] = 'month(s)';
|
||||||
|
$labels['years'] = 'year(s) in:';
|
||||||
|
$labels['bydays'] = 'On';
|
||||||
|
$labels['until'] = 'the';
|
||||||
|
$labels['each'] = 'Each';
|
||||||
|
$labels['onevery'] = 'On every';
|
||||||
|
$labels['forever'] = 'forever';
|
||||||
|
$labels['recurrencend'] = 'Until';
|
||||||
|
$labels['forntimes'] = 'for $nr time(s)';
|
||||||
|
$labels['first'] = 'first';
|
||||||
|
$labels['second'] = 'second';
|
||||||
|
$labels['third'] = 'third';
|
||||||
|
$labels['fourth'] = 'fourth';
|
||||||
|
$labels['last'] = 'last';
|
||||||
|
$labels['dayofmonth'] = 'Day of month';
|
||||||
|
|
||||||
|
?>
|
39
plugins/calendar/localization/es_ES.inc
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RoundCube Calendar
|
||||||
|
*
|
||||||
|
* Plugin to add a calendar to RoundCube.
|
||||||
|
*
|
||||||
|
* @version 0.2 BETA 3
|
||||||
|
* @author Lazlo Westerhof
|
||||||
|
* @author David Iribas
|
||||||
|
* @url http://rc-calendar.lazlo.me
|
||||||
|
* @licence GNU GPL
|
||||||
|
* @copyright (c) 2010 Lazlo Westerhof - Netherlands
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
|
||||||
|
$labels = array();
|
||||||
|
|
||||||
|
// config
|
||||||
|
$labels['default_view'] = 'Vista por defecto';
|
||||||
|
$labels['time_format'] = 'Formato hora';
|
||||||
|
$labels['timeslots'] = 'Periodos de hora';
|
||||||
|
$labels['first_day'] = 'Primer dia';
|
||||||
|
|
||||||
|
// calendar
|
||||||
|
$labels['calendar'] = 'Calendario';
|
||||||
|
$labels['day'] = 'Dia';
|
||||||
|
$labels['week'] = 'Semana';
|
||||||
|
$labels['month'] = 'Mes';
|
||||||
|
$labels['new_event'] = 'Nuevo evento';
|
||||||
|
$labels['edit_event'] = 'Editar evento';
|
||||||
|
$labels['save'] = 'Guardar';
|
||||||
|
$labels['remove'] = 'Borrar';
|
||||||
|
$labels['cancel'] = 'Cancelar';
|
||||||
|
$labels['title'] = 'Resumen';
|
||||||
|
$labels['description'] = 'Descripcion';
|
||||||
|
$labels['all-day'] = 'dia-completo';
|
||||||
|
$labels['export'] = 'Exportar a ICS';
|
||||||
|
$labels['category'] = 'Categoria';
|
||||||
|
?>
|
38
plugins/calendar/localization/fr_FR.inc
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RoundCube Calendar
|
||||||
|
*
|
||||||
|
* Plugin to add a calendar to RoundCube.
|
||||||
|
*
|
||||||
|
* @version 0.2 BETA 2
|
||||||
|
* @author Lazlo Westerhof
|
||||||
|
* @url http://rc-calendar.lazlo.me
|
||||||
|
* @licence GNU GPL
|
||||||
|
* @copyright (c) 2010 Lazlo Westerhof - Netherlands
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
|
||||||
|
$labels = array();
|
||||||
|
|
||||||
|
// config
|
||||||
|
$labels['default_view'] = 'Vue par défaut';
|
||||||
|
$labels['time_format'] = 'Format de l\'heure';
|
||||||
|
$labels['timeslots'] = 'Intervalle de temps par heure';
|
||||||
|
$labels['first_day'] = 'Premier jour de la semaine';
|
||||||
|
|
||||||
|
// calendar
|
||||||
|
$labels['calendar'] = 'Calendrier';
|
||||||
|
$labels['day'] = 'Jour';
|
||||||
|
$labels['week'] = 'Semaine';
|
||||||
|
$labels['month'] = 'Mois';
|
||||||
|
$labels['new_event'] = 'Nouvel évènement';
|
||||||
|
$labels['edit_event'] = 'Editer l\'évènement';
|
||||||
|
$labels['save'] = 'Enregistrer';
|
||||||
|
$labels['remove'] = 'Supprimer';
|
||||||
|
$labels['cancel'] = 'Annuler';
|
||||||
|
$labels['title'] = 'Sommaire';
|
||||||
|
$labels['description'] = 'Description';
|
||||||
|
$labels['all-day'] = 'Toute la journée';
|
||||||
|
$labels['export'] = 'Exporter en format ICS';
|
||||||
|
$labels['category'] = 'Categorie';
|
||||||
|
?>
|
38
plugins/calendar/localization/hu_HU.inc
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RoundCube Calendar
|
||||||
|
*
|
||||||
|
* Plugin to add a calendar to RoundCube.
|
||||||
|
*
|
||||||
|
* @version 0.2 BETA 2
|
||||||
|
* @author Lazlo Westerhof
|
||||||
|
* @url http://rc-calendar.lazlo.me
|
||||||
|
* @licence GNU GPL
|
||||||
|
* @copyright (c) 2010 Lazlo Westerhof - Netherlands
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
|
||||||
|
$labels = array();
|
||||||
|
|
||||||
|
// config
|
||||||
|
$labels['default_view'] = 'Alap nézet';
|
||||||
|
$labels['time_format'] = 'Idõ formátum';
|
||||||
|
$labels['timeslots'] = 'Idõrés óránként';
|
||||||
|
$labels['first_day'] = 'A hét elsõ napja';
|
||||||
|
|
||||||
|
// calendar
|
||||||
|
$labels['calendar'] = 'Naptár';
|
||||||
|
$labels['day'] = 'Nap';
|
||||||
|
$labels['week'] = 'Hét';
|
||||||
|
$labels['month'] = 'Hónap';
|
||||||
|
$labels['new_event'] = 'Új bejegyzés';
|
||||||
|
$labels['edit_event'] = 'Bejegyzés szerkesztése';
|
||||||
|
$labels['save'] = 'Mentés';
|
||||||
|
$labels['remove'] = 'Törlés';
|
||||||
|
$labels['cancel'] = 'Mégse';
|
||||||
|
$labels['title'] = 'Tárgy';
|
||||||
|
$labels['description'] = 'Leírás';
|
||||||
|
$labels['all-day'] = 'Egész nap';
|
||||||
|
$labels['export'] = 'Exportálás ICS formátumba';
|
||||||
|
$labels['category'] = 'Kategória';
|
||||||
|
?>
|
38
plugins/calendar/localization/it_IT.inc
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RoundCube Calendar
|
||||||
|
*
|
||||||
|
* Plugin to add a calendar to RoundCube.
|
||||||
|
*
|
||||||
|
* @version 0.2 BETA 2
|
||||||
|
* @author Lazlo Westerhof
|
||||||
|
* @url http://rc-calendar.lazlo.me
|
||||||
|
* @licence GNU GPL
|
||||||
|
* @copyright (c) 2010 Lazlo Westerhof - Netherlands
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
|
||||||
|
$labels = array();
|
||||||
|
|
||||||
|
// config
|
||||||
|
$labels['default_view'] = 'Visualizzazine di default';
|
||||||
|
$labels['time_format'] = 'Formato ora';
|
||||||
|
$labels['timeslots'] = 'Timeslots per ora';
|
||||||
|
$labels['first_day'] = 'Inizio settimana';
|
||||||
|
|
||||||
|
// calendar
|
||||||
|
$labels['calendar'] = 'Calendario';
|
||||||
|
$labels['day'] = 'Giorno';
|
||||||
|
$labels['week'] = 'Settimana';
|
||||||
|
$labels['month'] = 'Mese';
|
||||||
|
$labels['new_event'] = 'Nuovo evento';
|
||||||
|
$labels['edit_event'] = 'Modifica evento';
|
||||||
|
$labels['save'] = 'Salva';
|
||||||
|
$labels['remove'] = 'Elimina';
|
||||||
|
$labels['cancel'] = 'Annulla';
|
||||||
|
$labels['title'] = 'Oggetto';
|
||||||
|
$labels['description'] = 'Descrizione';
|
||||||
|
$labels['all-day'] = 'Giorno intero';
|
||||||
|
$labels['export'] = 'Esporta in ICS';
|
||||||
|
$labels['category'] = 'Categoria';
|
||||||
|
?>
|
39
plugins/calendar/localization/nl_NL.inc
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RoundCube Calendar
|
||||||
|
*
|
||||||
|
* Plugin to add a calendar to RoundCube.
|
||||||
|
*
|
||||||
|
* @version 0.2 BETA 2
|
||||||
|
* @author Lazlo Westerhof
|
||||||
|
* @url http://rc-calendar.lazlo.me
|
||||||
|
* @licence GNU GPL
|
||||||
|
* @copyright (c) 2010 Lazlo Westerhof - Netherlands
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
|
||||||
|
$labels = array();
|
||||||
|
|
||||||
|
// config
|
||||||
|
$labels['default_view'] = 'Standaard agenda';
|
||||||
|
$labels['time_format'] = 'Tijdsformaat';
|
||||||
|
$labels['timeslots'] = 'Timeslots per uur';
|
||||||
|
$labels['first_day'] = 'Eerste weekdag';
|
||||||
|
|
||||||
|
// calendar
|
||||||
|
$labels['calendar'] = 'Agenda';
|
||||||
|
$labels['day'] = 'Dag';
|
||||||
|
$labels['week'] = 'Week';
|
||||||
|
$labels['month'] = 'Maand';
|
||||||
|
$labels['new_event'] = 'Nieuw evenement';
|
||||||
|
$labels['edit_event'] = 'Wijzig evenement';
|
||||||
|
$labels['save'] = 'Bewaar';
|
||||||
|
$labels['remove'] = 'Verwijder';
|
||||||
|
$labels['cancel'] = 'Terug';
|
||||||
|
$labels['title'] = 'Samenvatting';
|
||||||
|
$labels['description'] = 'Beschrijving';
|
||||||
|
$labels['all-day'] = 'hele dag';
|
||||||
|
$labels['export'] = 'Exporteer naar ICS';
|
||||||
|
$labels['category'] = 'Categorie';
|
||||||
|
$labels['location'] = 'Locatie';
|
||||||
|
?>
|
39
plugins/calendar/localization/pl_PL.inc
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RoundCube Calendar
|
||||||
|
*
|
||||||
|
* Plugin to add a calendar to RoundCube.
|
||||||
|
*
|
||||||
|
* @version 0.2 BETA 2
|
||||||
|
* @author Lazlo Westerhof
|
||||||
|
* @url http://rc-calendar.lazlo.me
|
||||||
|
* @licence GNU GPL
|
||||||
|
* @copyright (c) 2010 Lazlo Westerhof - Netherlands
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
|
||||||
|
$labels = array();
|
||||||
|
|
||||||
|
// config
|
||||||
|
$labels['default_view'] = 'Domyślny widok';
|
||||||
|
$labels['time_format'] = 'Format czasu';
|
||||||
|
$labels['timeslots'] = 'Przedzialy czasowe na godzinę';
|
||||||
|
$labels['first_day'] = 'Pierwszy dzień tygodnia';
|
||||||
|
|
||||||
|
// calendar
|
||||||
|
$labels['calendar'] = 'Kalendarz';
|
||||||
|
$labels['day'] = 'Dzień';
|
||||||
|
$labels['week'] = 'Tydzień';
|
||||||
|
$labels['month'] = 'Miesiąc';
|
||||||
|
$labels['new_event'] = 'Nowe zdarzenie';
|
||||||
|
$labels['edit_event'] = 'Edycja zdarzenia';
|
||||||
|
$labels['save'] = 'Zapisz';
|
||||||
|
$labels['remove'] = 'Usuń';
|
||||||
|
$labels['cancel'] = 'Anuluj';
|
||||||
|
$labels['title'] = 'Temat';
|
||||||
|
$labels['description'] = 'Opis';
|
||||||
|
$labels['all-day'] = 'cały dzień';
|
||||||
|
$labels['export'] = 'Eksport do ICS';
|
||||||
|
$labels['category'] = 'Kategoria';
|
||||||
|
$labels['location'] = 'Lokalizacja';
|
||||||
|
?>
|
38
plugins/calendar/localization/pt_BR.inc
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RoundCube Calendar
|
||||||
|
*
|
||||||
|
* Plugin to add a calendar to RoundCube.
|
||||||
|
*
|
||||||
|
* @version 0.2 BETA 2
|
||||||
|
* @author Lazlo Westerhof
|
||||||
|
* @url http://rc-calendar.lazlo.me
|
||||||
|
* @licence GNU GPL
|
||||||
|
* @copyright (c) 2010 Lazlo Westerhof - Netherlands
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
|
||||||
|
$labels = array();
|
||||||
|
|
||||||
|
// config
|
||||||
|
$labels['default_view'] = 'Visualização padrão';
|
||||||
|
$labels['time_format'] = 'Formato da hora';
|
||||||
|
$labels['timeslots'] = 'Caixas de tempo por hora';
|
||||||
|
$labels['first_day'] = 'Primeiro dia da semana';
|
||||||
|
|
||||||
|
// calendar
|
||||||
|
$labels['calendar'] = 'Calendário';
|
||||||
|
$labels['day'] = 'Dia';
|
||||||
|
$labels['week'] = 'Semana';
|
||||||
|
$labels['month'] = 'Mês';
|
||||||
|
$labels['new_event'] = 'Novo evento';
|
||||||
|
$labels['edit_event'] = 'Editar evento';
|
||||||
|
$labels['save'] = 'Gravar';
|
||||||
|
$labels['remove'] = 'Remover';
|
||||||
|
$labels['cancel'] = 'Cancelar';
|
||||||
|
$labels['title'] = 'Sumário';
|
||||||
|
$labels['description'] = 'Descrição';
|
||||||
|
$labels['all-day'] = 'dia-a-dia';
|
||||||
|
$labels['export'] = 'Exportar para ICS';
|
||||||
|
$labels['category'] = 'Categoria';
|
||||||
|
?>
|
32
plugins/calendar/localization/ru_RU.inc
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RoundCube Calendar
|
||||||
|
*
|
||||||
|
* Plugin to add a calendar to RoundCube.
|
||||||
|
*
|
||||||
|
* @version 0.2 BETA 2
|
||||||
|
* @author Lazlo Westerhof
|
||||||
|
* @url http://rc-calendar.lazlo.me
|
||||||
|
* @licence GNU GPL
|
||||||
|
* @copyright (c) 2010 Lazlo Westerhof - Netherlands
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
|
||||||
|
$labels = array();
|
||||||
|
|
||||||
|
// config
|
||||||
|
$labels['time_format'] = 'Формат времени';
|
||||||
|
$labels['timeslots'] = 'Промежутков в час';
|
||||||
|
$labels['first_day'] = 'Первый день недели';
|
||||||
|
|
||||||
|
// calendar
|
||||||
|
$labels['calendar'] = 'Календарь';
|
||||||
|
$labels['day'] = 'День';
|
||||||
|
$labels['week'] = 'Неделя';
|
||||||
|
$labels['month'] = 'Месяц';
|
||||||
|
$labels['new_event'] = 'Новое событие';
|
||||||
|
$labels['edit_event'] = 'Изменить событие';
|
||||||
|
$labels['save'] = 'Сохранить';
|
||||||
|
$labels['remove'] = 'Удалить';
|
||||||
|
$labels['cancel'] = 'Завершить';
|
||||||
|
?>
|
5
plugins/calendar/skins/default/README.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
Icons by Fugue Icons <http://p.yusukekamiyamane.com/>
|
||||||
|
|
||||||
|
Copyright (C) 2010 Yusuke Kamiyamane. All rights reserved.
|
||||||
|
The icons are licensed under a Creative Commons Attribution
|
||||||
|
3.0 license. <http://creativecommons.org/licenses/by/3.0/>
|
392
plugins/calendar/skins/default/calendar.css
Normal file
|
@ -0,0 +1,392 @@
|
||||||
|
/*** Style for Calendar plugin ***/
|
||||||
|
|
||||||
|
body.calendarmain {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#taskbar a.button-calendar {
|
||||||
|
background: url('images/calendar.png') 0px 1px no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main {
|
||||||
|
position: absolute;
|
||||||
|
clear: both;
|
||||||
|
top: 85px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar {
|
||||||
|
position: absolute;
|
||||||
|
top: 37px;
|
||||||
|
left: 10px;
|
||||||
|
bottom: 0;
|
||||||
|
width: 230px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#datepicker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#datepicker .ui-datepicker {
|
||||||
|
width: 97% !important;
|
||||||
|
-moz-box-shadow: none;
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#datepicker .ui-priority-secondary {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebartoggle {
|
||||||
|
position: absolute;
|
||||||
|
left: 246px;
|
||||||
|
width: 8px;
|
||||||
|
top: 37px;
|
||||||
|
bottom: 0;
|
||||||
|
background: url('images/toggle.gif') 0 48% no-repeat transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sidebarclosed {
|
||||||
|
background-position: -8px 48% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebartoggle:hover {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendar {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 260px;
|
||||||
|
right: 10px;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print {
|
||||||
|
width: 680px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendars {
|
||||||
|
position: absolute;
|
||||||
|
top: 230px;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #F9F9F9;
|
||||||
|
border: 1px solid #999999;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendarslist {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendarslist li {
|
||||||
|
margin: 0;
|
||||||
|
padding: 2px;
|
||||||
|
display: block;
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid #EBEBEB;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendarslist li label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendarslist li span {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendarslist li input {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendarslist li.selected {
|
||||||
|
background-color: #ccc;
|
||||||
|
border-bottom: 1px solid #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendarslist li.selected span {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#agendalist {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-top: 60px;
|
||||||
|
border: 1px solid #C1DAD7;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#agendalist table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#agendalist td, th {
|
||||||
|
border-right: 1px solid #C1DAD7;
|
||||||
|
border-bottom: 1px solid #C1DAD7;
|
||||||
|
background: #fff;
|
||||||
|
padding: 6px 6px 6px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#agendalist tr {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
#agendalist th {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendartoolbar {
|
||||||
|
position: absolute;
|
||||||
|
top: 45px;
|
||||||
|
left: 260px;
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendartoolbar a {
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendartoolbar a.button,
|
||||||
|
#calendartoolbar a.buttonPas {
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
margin-right: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: url('images/toolbar.png') 0 0 no-repeat transparent;
|
||||||
|
opacity: 0.99; /* this is needed to make buttons appear correctly in Chrome */
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendartoolbar a.buttonPas {
|
||||||
|
opacity: 0.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendartoolbar a.addeventSel {
|
||||||
|
background-position: 0 -32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendartoolbar a.delete {
|
||||||
|
background-position: -32px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendartoolbar a.deleteSel {
|
||||||
|
background-position: -32px -32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendartoolbar a.print {
|
||||||
|
background-position: -64px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendartoolbar a.printSel {
|
||||||
|
background-position: -64px -32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendartoolbar a.export {
|
||||||
|
background-position: -128px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendartoolbar a.exportSel {
|
||||||
|
background-position: -128px -32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#quicksearchbar {
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventshow,
|
||||||
|
#eventedit {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#user {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 100px;
|
||||||
|
left: 100px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* jQuery UI overrides */
|
||||||
|
|
||||||
|
#eventshow h1 {
|
||||||
|
font-size: 20px;
|
||||||
|
margin: 0.1em 0 0.4em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventshow label,
|
||||||
|
#eventshow h5.label {
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #999;
|
||||||
|
margin: 0 0 0.2em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventshow div.event-line {
|
||||||
|
margin-top: 0.1em;
|
||||||
|
margin-bottom: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventedit {
|
||||||
|
padding: 0.5em 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventedit input.text,
|
||||||
|
#eventedit textarea {
|
||||||
|
width: 97%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventtabs {
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventshow div.event-section,
|
||||||
|
#eventtabs div.event-section {
|
||||||
|
margin-top: 0.2em;
|
||||||
|
margin-bottom: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventtabs .tabsbar {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventtabs .ui-tabs-panel {
|
||||||
|
padding: 1em 0.8em;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
border-width: 0 1px 1px 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventtabs .ui-tabs-nav {
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
border-width: 0 0 1px 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventtabs .border-after {
|
||||||
|
padding-bottom: 0.6em;
|
||||||
|
margin-bottom: 0.6em;
|
||||||
|
border-bottom: 1px solid #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventshow label,
|
||||||
|
#eventedit label {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 7em;
|
||||||
|
padding-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventedit .formtable td.label {
|
||||||
|
min-width: 6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.topalign {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventedit label.weekday,
|
||||||
|
#eventedit label.monthday {
|
||||||
|
min-width: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventedit label.month {
|
||||||
|
min-width: 5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#edit-recurrence-yearly-bymonthblock {
|
||||||
|
margin-left: 7.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventedit .recurrence-form {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventedit .formtable td {
|
||||||
|
padding: 0.2em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.dropdown-link {
|
||||||
|
color: #CC0000;
|
||||||
|
font-size: 12px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.dropdown-link:after {
|
||||||
|
content: ' ▼';
|
||||||
|
font-size: 11px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventedit .ui-tabs-panel {
|
||||||
|
min-height: 20em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-dialog-buttonset a.dropdown-link {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-datepicker-calendar .ui-datepicker-today .ui-state-default {
|
||||||
|
border-color: #cccccc;
|
||||||
|
background: #ffffcc;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-datepicker-calendar .ui-datepicker-week-col {
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-autocomplete {
|
||||||
|
max-height: 160px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
* html .ui-autocomplete {
|
||||||
|
height: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fullcalendar style overrides */
|
||||||
|
|
||||||
|
.fc-event-title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-event-cateories {
|
||||||
|
font-style:italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-agenda-slots td div {
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-mon, .fc-tue, .fc-wed, .fc-thu, .fc-fri {
|
||||||
|
background-color: #fdfdfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-widget-header {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Settings section */
|
||||||
|
|
||||||
|
fieldset #calendarcategories div {
|
||||||
|
margin-bottom: 0.3em;
|
||||||
|
}
|
||||||
|
|
618
plugins/calendar/skins/default/fullcalendar.css
Normal file
|
@ -0,0 +1,618 @@
|
||||||
|
/*
|
||||||
|
* FullCalendar v1.5.1 Stylesheet
|
||||||
|
*
|
||||||
|
* Copyright (c) 2011 Adam Shaw
|
||||||
|
* Dual licensed under the MIT and GPL licenses, located in
|
||||||
|
* MIT-LICENSE.txt and GPL-LICENSE.txt respectively.
|
||||||
|
*
|
||||||
|
* Date: Sat Apr 9 14:09:51 2011 -0700
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
.fc {
|
||||||
|
direction: ltr;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html .fc,
|
||||||
|
.fc table {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc td,
|
||||||
|
.fc th {
|
||||||
|
padding: 0;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Header
|
||||||
|
------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.fc-header td {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-header-left {
|
||||||
|
width: 25%;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-header-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-header-right {
|
||||||
|
width: 25%;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-header-title {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-header-title h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc .fc-header-space {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-header .fc-button {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* buttons edges butting together */
|
||||||
|
|
||||||
|
.fc-header .fc-button {
|
||||||
|
margin-right: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-header .fc-corner-right {
|
||||||
|
margin-right: 1px; /* back to normal */
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-header .ui-corner-right {
|
||||||
|
margin-right: 0; /* back to normal */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* button layering (for border precedence) */
|
||||||
|
|
||||||
|
.fc-header .fc-state-hover,
|
||||||
|
.fc-header .ui-state-hover {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-header .fc-state-down {
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-header .fc-state-active,
|
||||||
|
.fc-header .ui-state-active {
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Content
|
||||||
|
------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.fc-content {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-view {
|
||||||
|
width: 100%; /* needed for view switching (when view is absolute) */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Cell Styles
|
||||||
|
------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.fc-widget-header, /* <th>, usually */
|
||||||
|
.fc-widget-content { /* <td>, usually */
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-state-highlight { /* <td> today cell */ /* TODO: add .fc-today to <th> */
|
||||||
|
background: #ffc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-cell-overlay { /* semi-transparent rectangle while dragging */
|
||||||
|
background: #9cf;
|
||||||
|
opacity: .2;
|
||||||
|
filter: alpha(opacity=20); /* for IE */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Buttons
|
||||||
|
------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.fc-button {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-state-default { /* non-theme */
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-button-inner {
|
||||||
|
position: relative;
|
||||||
|
float: left;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-state-default .fc-button-inner { /* non-theme */
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-button-content {
|
||||||
|
position: relative;
|
||||||
|
float: left;
|
||||||
|
height: 1.9em;
|
||||||
|
line-height: 1.9em;
|
||||||
|
padding: 0 .6em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* icon (for jquery ui) */
|
||||||
|
|
||||||
|
.fc-button-content .fc-icon-wrap {
|
||||||
|
position: relative;
|
||||||
|
float: left;
|
||||||
|
top: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-button-content .ui-icon {
|
||||||
|
position: relative;
|
||||||
|
float: left;
|
||||||
|
margin-top: -50%;
|
||||||
|
*margin-top: 0;
|
||||||
|
*top: -50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* gloss effect */
|
||||||
|
|
||||||
|
.fc-state-default .fc-button-effect {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-state-default .fc-button-effect span {
|
||||||
|
position: absolute;
|
||||||
|
top: -100px;
|
||||||
|
left: 0;
|
||||||
|
width: 500px;
|
||||||
|
height: 100px;
|
||||||
|
border-width: 100px 0 0 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #fff;
|
||||||
|
background: #444;
|
||||||
|
opacity: .09;
|
||||||
|
filter: alpha(opacity=9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* button states (determines colors) */
|
||||||
|
|
||||||
|
.fc-state-default,
|
||||||
|
.fc-state-default .fc-button-inner {
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #ccc #bbb #aaa;
|
||||||
|
background: #F3F3F3;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-state-hover,
|
||||||
|
.fc-state-hover .fc-button-inner {
|
||||||
|
border-color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-state-down,
|
||||||
|
.fc-state-down .fc-button-inner {
|
||||||
|
border-color: #555;
|
||||||
|
background: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-state-active,
|
||||||
|
.fc-state-active .fc-button-inner {
|
||||||
|
border-color: #555;
|
||||||
|
background: #777;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-state-disabled,
|
||||||
|
.fc-state-disabled .fc-button-inner {
|
||||||
|
color: #999;
|
||||||
|
border-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-state-disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-state-disabled .fc-button-effect {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Global Event Styles
|
||||||
|
------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.fc-event {
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0;
|
||||||
|
font-size: .85em;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.fc-event,
|
||||||
|
.fc-event-draggable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.fc-event {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-rtl .fc-event {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-event-skin {
|
||||||
|
border-color: #36c; /* default BORDER color */
|
||||||
|
background-color: #36c; /* default BACKGROUND color */
|
||||||
|
color: #fff; /* default TEXT color */
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-event-inner {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-event-time,
|
||||||
|
.fc-event-title {
|
||||||
|
padding: 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc .ui-resizable-handle { /*** TODO: don't use ui-resizable anymore, change class ***/
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 99999;
|
||||||
|
overflow: hidden; /* hacky spaces (IE6/7) */
|
||||||
|
font-size: 300%; /* */
|
||||||
|
line-height: 50%; /* */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Horizontal Events
|
||||||
|
------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.fc-event-hori {
|
||||||
|
border-width: 1px 0;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* resizable */
|
||||||
|
|
||||||
|
.fc-event-hori .ui-resizable-e {
|
||||||
|
top: 0 !important; /* importants override pre jquery ui 1.7 styles */
|
||||||
|
right: -3px !important;
|
||||||
|
width: 7px !important;
|
||||||
|
height: 100% !important;
|
||||||
|
cursor: e-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-event-hori .ui-resizable-w {
|
||||||
|
top: 0 !important;
|
||||||
|
left: -3px !important;
|
||||||
|
width: 7px !important;
|
||||||
|
height: 100% !important;
|
||||||
|
cursor: w-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-event-hori .ui-resizable-handle {
|
||||||
|
_padding-bottom: 14px; /* IE6 had 0 height */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Fake Rounded Corners (for buttons and events)
|
||||||
|
------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.fc-corner-left {
|
||||||
|
margin-left: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-corner-left .fc-button-inner,
|
||||||
|
.fc-corner-left .fc-event-inner {
|
||||||
|
margin-left: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-corner-right {
|
||||||
|
margin-right: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-corner-right .fc-button-inner,
|
||||||
|
.fc-corner-right .fc-event-inner {
|
||||||
|
margin-right: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-corner-top {
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-corner-top .fc-event-inner {
|
||||||
|
margin-top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-corner-bottom {
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-corner-bottom .fc-event-inner {
|
||||||
|
margin-bottom: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Fake Rounded Corners SPECIFICALLY FOR EVENTS
|
||||||
|
-----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.fc-corner-left .fc-event-inner {
|
||||||
|
border-left-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-corner-right .fc-event-inner {
|
||||||
|
border-right-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-corner-top .fc-event-inner {
|
||||||
|
border-top-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-corner-bottom .fc-event-inner {
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Reusable Separate-border Table
|
||||||
|
------------------------------------------------------------*/
|
||||||
|
|
||||||
|
table.fc-border-separate {
|
||||||
|
border-collapse: separate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-border-separate th,
|
||||||
|
.fc-border-separate td {
|
||||||
|
border-width: 1px 0 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-border-separate th.fc-last,
|
||||||
|
.fc-border-separate td.fc-last {
|
||||||
|
border-right-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-border-separate tr.fc-last th,
|
||||||
|
.fc-border-separate tr.fc-last td {
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-border-separate tbody tr.fc-first td,
|
||||||
|
.fc-border-separate tbody tr.fc-first th {
|
||||||
|
border-top-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Month View, Basic Week View, Basic Day View
|
||||||
|
------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.fc-grid th {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-grid .fc-day-number {
|
||||||
|
float: right;
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-grid .fc-other-month .fc-day-number {
|
||||||
|
opacity: 0.3;
|
||||||
|
filter: alpha(opacity=30); /* for IE */
|
||||||
|
/* opacity with small font can sometimes look too faded
|
||||||
|
might want to set the 'color' property instead
|
||||||
|
making day-numbers bold also fixes the problem */
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-grid .fc-day-content {
|
||||||
|
clear: both;
|
||||||
|
padding: 2px 2px 1px; /* distance between events and day edges */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* event styles */
|
||||||
|
|
||||||
|
.fc-grid .fc-event-time {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* right-to-left */
|
||||||
|
|
||||||
|
.fc-rtl .fc-grid .fc-day-number {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-rtl .fc-grid .fc-event-time {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Agenda Week View, Agenda Day View
|
||||||
|
------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.fc-agenda table {
|
||||||
|
border-collapse: separate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-agenda-days th {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-agenda .fc-agenda-axis {
|
||||||
|
width: 50px;
|
||||||
|
padding: 0 4px;
|
||||||
|
vertical-align: middle;
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-agenda .fc-day-content {
|
||||||
|
padding: 2px 2px 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* make axis border take precedence */
|
||||||
|
|
||||||
|
.fc-agenda-days .fc-agenda-axis {
|
||||||
|
border-right-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-agenda-days .fc-col0 {
|
||||||
|
border-left-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* all-day area */
|
||||||
|
|
||||||
|
.fc-agenda-allday th {
|
||||||
|
border-width: 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-agenda-allday .fc-day-content {
|
||||||
|
min-height: 34px; /* TODO: doesnt work well in quirksmode */
|
||||||
|
_height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* divider (between all-day and slots) */
|
||||||
|
|
||||||
|
.fc-agenda-divider-inner {
|
||||||
|
height: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-widget-header .fc-agenda-divider-inner {
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* slot rows */
|
||||||
|
|
||||||
|
.fc-agenda-slots th {
|
||||||
|
border-width: 1px 1px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-agenda-slots td {
|
||||||
|
border-width: 1px 0 0;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-agenda-slots td div {
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-agenda-slots tr.fc-slot0 th,
|
||||||
|
.fc-agenda-slots tr.fc-slot0 td {
|
||||||
|
border-top-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-agenda-slots tr.fc-minor th,
|
||||||
|
.fc-agenda-slots tr.fc-minor td {
|
||||||
|
border-top-style: dotted;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-agenda-slots tr.fc-minor th.ui-widget-header {
|
||||||
|
*border-top-style: solid; /* doesn't work with background in IE6/7 */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Vertical Events
|
||||||
|
------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.fc-event-vert {
|
||||||
|
border-width: 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-event-vert .fc-event-head,
|
||||||
|
.fc-event-vert .fc-event-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-event-vert .fc-event-time {
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-event-vert .fc-event-bg { /* makes the event lighter w/ a semi-transparent overlay */
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #fff;
|
||||||
|
opacity: .3;
|
||||||
|
filter: alpha(opacity=30);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc .ui-draggable-dragging .fc-event-bg, /* TODO: something nicer like .fc-opacity */
|
||||||
|
.fc-select-helper .fc-event-bg {
|
||||||
|
display: none\9; /* for IE6/7/8. nested opacity filters while dragging don't work */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* resizable */
|
||||||
|
|
||||||
|
.fc-event-vert .ui-resizable-s {
|
||||||
|
bottom: 0 !important; /* importants override pre jquery ui 1.7 styles */
|
||||||
|
width: 100% !important;
|
||||||
|
height: 8px !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
line-height: 8px !important;
|
||||||
|
font-size: 11px !important;
|
||||||
|
font-family: monospace;
|
||||||
|
text-align: center;
|
||||||
|
cursor: s-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-agenda .ui-resizable-resizing { /* TODO: better selector */
|
||||||
|
_overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
61
plugins/calendar/skins/default/fullcalendar.print.css
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* FullCalendar v1.5.1 Print Stylesheet
|
||||||
|
*
|
||||||
|
* Include this stylesheet on your page to get a more printer-friendly calendar.
|
||||||
|
* When including this stylesheet, use the media='print' attribute of the <link> tag.
|
||||||
|
* Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2011 Adam Shaw
|
||||||
|
* Dual licensed under the MIT and GPL licenses, located in
|
||||||
|
* MIT-LICENSE.txt and GPL-LICENSE.txt respectively.
|
||||||
|
*
|
||||||
|
* Date: Sat Apr 9 14:09:51 2011 -0700
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/* Events
|
||||||
|
-----------------------------------------------------*/
|
||||||
|
|
||||||
|
.fc-event-skin {
|
||||||
|
background: none !important;
|
||||||
|
color: #000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* horizontal events */
|
||||||
|
|
||||||
|
.fc-event-hori {
|
||||||
|
border-width: 0 0 1px 0 !important;
|
||||||
|
border-bottom-style: dotted !important;
|
||||||
|
border-bottom-color: #000 !important;
|
||||||
|
padding: 1px 0 0 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-event-hori .fc-event-inner {
|
||||||
|
border-width: 0 !important;
|
||||||
|
padding: 0 1px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vertical events */
|
||||||
|
|
||||||
|
.fc-event-vert {
|
||||||
|
border-width: 0 0 0 1px !important;
|
||||||
|
border-left-style: dotted !important;
|
||||||
|
border-left-color: #000 !important;
|
||||||
|
padding: 0 1px 0 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-event-vert .fc-event-inner {
|
||||||
|
border-width: 0 !important;
|
||||||
|
padding: 1px 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-event-bg {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-event .ui-resizable-handle {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
BIN
plugins/calendar/skins/default/images/calendar-blue.png
Normal file
After Width: | Height: | Size: 896 B |
BIN
plugins/calendar/skins/default/images/calendar.png
Normal file
After Width: | Height: | Size: 888 B |
BIN
plugins/calendar/skins/default/images/export.png
Normal file
After Width: | Height: | Size: 746 B |
BIN
plugins/calendar/skins/default/images/preview.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
plugins/calendar/skins/default/images/print.png
Normal file
After Width: | Height: | Size: 869 B |
BIN
plugins/calendar/skins/default/images/spacer.gif
Normal file
After Width: | Height: | Size: 807 B |
BIN
plugins/calendar/skins/default/images/toggle.gif
Normal file
After Width: | Height: | Size: 110 B |
BIN
plugins/calendar/skins/default/images/toolbar.png
Normal file
After Width: | Height: | Size: 14 KiB |
171
plugins/calendar/skins/default/templates/calendar.html
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<title><roundcube:object name="pagetitle" /></title>
|
||||||
|
<roundcube:include file="/includes/links.html" />
|
||||||
|
<script type="text/javascript" src="/functions.js"></script>
|
||||||
|
</head>
|
||||||
|
<body class="calendarmain">
|
||||||
|
<roundcube:include file="/includes/taskbar.html" />
|
||||||
|
<roundcube:include file="/includes/header.html" />
|
||||||
|
|
||||||
|
<div id="main">
|
||||||
|
<div id="sidebar">
|
||||||
|
<div id="datepicker"></div>
|
||||||
|
<div id="calendars">
|
||||||
|
<div class="boxtitle"><roundcube:label name="calendar.calendars" /></div>
|
||||||
|
<div class="boxlistcontent">
|
||||||
|
<roundcube:object name="plugin.calendar_list" id="calendarslist" />
|
||||||
|
</div>
|
||||||
|
<div class="boxfooter">
|
||||||
|
<roundcube:button command="plugin.createcalendar" type="link" title="calendar.createcalendar" class="buttonPas addgroup" classAct="button addgroup" content=" " />
|
||||||
|
<roundcube:button name="calendarmenulink" id="calendarmenulink" type="link" title="calendaractions" class="button groupactions" onclick="return false" content=" " />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="sidebartoggle"></div>
|
||||||
|
<div id="calendar"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="eventshow">
|
||||||
|
<h1 id="event-title">Event Title</h1>
|
||||||
|
<div class="event-section" id="event-location">Location</div>
|
||||||
|
<div class="event-section" id="event-date">From-To</div>
|
||||||
|
<div class="event-section" id="event-description">
|
||||||
|
<h5 class="label"><roundcube:label name="calendar.description" /></h5>
|
||||||
|
<div class="event-text"></div>
|
||||||
|
</div>
|
||||||
|
<div class="event-section" id="event-repeat">
|
||||||
|
<h5 class="label"><roundcube:label name="calendar.repeat" /></h5>
|
||||||
|
<div class="event-text"></div>
|
||||||
|
</div>
|
||||||
|
<div class="event-section" id="event-alarm">
|
||||||
|
<h5 class="label"><roundcube:label name="calendar.alam" /></h5>
|
||||||
|
<div class="event-text"></div>
|
||||||
|
</div>
|
||||||
|
<div class="event-section" id="event-attendees">
|
||||||
|
<h5 class="label"><roundcube:label name="calendar.tabattendees" /></h5>
|
||||||
|
<div class="event-text"></div>
|
||||||
|
</div>
|
||||||
|
<div class="event-line" id="event-calendar">
|
||||||
|
<label><roundcube:label name="calendar.calendar" /></label>
|
||||||
|
<span class="event-text">Default</span>
|
||||||
|
</div>
|
||||||
|
<div class="event-line" id="event-category">
|
||||||
|
<label><roundcube:label name="calendar.category" /></label>
|
||||||
|
<span class="event-text"></span>
|
||||||
|
</div>
|
||||||
|
<div class="event-line" id="event-free-busy">
|
||||||
|
<label><roundcube:label name="calendar.freebusy" /></label>
|
||||||
|
<span class="event-text"></span>
|
||||||
|
</div>
|
||||||
|
<div class="event-line" id="event-priority">
|
||||||
|
<label><roundcube:label name="calendar.priority" /></label>
|
||||||
|
<span class="event-text"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="eventedit">
|
||||||
|
<form id="eventtabs" action="#">
|
||||||
|
<ul>
|
||||||
|
<li><a href="#event-tab-1"><roundcube:label name="calendar.tabsummary" /></a></li>
|
||||||
|
<li id="edit-tab-recurrence"><a href="#event-tab-2"><roundcube:label name="calendar.tabrecurrence" /></a></li>
|
||||||
|
<li id="edit-tab-attendees"><a href="#event-tab-3"><roundcube:label name="calendar.tabattendees" /></a></li>
|
||||||
|
<li id="edit-tab-attachments"><a href="#event-tab-4"><roundcube:label name="calendar.tabattachments" /></a></li>
|
||||||
|
</ul>
|
||||||
|
<!-- basic info -->
|
||||||
|
<div id="event-tab-1">
|
||||||
|
<div class="event-section">
|
||||||
|
<label for="edit-title"><roundcube:label name="calendar.title" /></label>
|
||||||
|
<br />
|
||||||
|
<input type="text" class="text" name="title" id="edit-title" size="40" />
|
||||||
|
</div>
|
||||||
|
<div class="event-section">
|
||||||
|
<label for="edit-location"><roundcube:label name="calendar.location" /></label>
|
||||||
|
<br />
|
||||||
|
<input type="text" class="text" name="location" id="edit-location" size="40" />
|
||||||
|
</div>
|
||||||
|
<div class="event-section">
|
||||||
|
<label for="edit-description"><roundcube:label name="calendar.description" /></label>
|
||||||
|
<br />
|
||||||
|
<textarea name="description" id="edit-description" class="text" rows="5" cols="40"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="event-section">
|
||||||
|
<label style="float:right;padding-right:0.5em"><input type="checkbox" name="allday" id="edit-allday" value="1" /><roundcube:label name="calendar.all-day" /></label>
|
||||||
|
<label for="edit-startdate"><roundcube:label name="calendar.start" /></label>
|
||||||
|
<input type="text" name="startdate" size="10" id="edit-startdate" />
|
||||||
|
<input type="text" name="starttime" size="6" id="edit-starttime" />
|
||||||
|
</div>
|
||||||
|
<div class="event-section">
|
||||||
|
<label for="edit-enddate"><roundcube:label name="calendar.end" /></label>
|
||||||
|
<input type="text" name="enddate" size="10" id="edit-enddate" />
|
||||||
|
<input type="text" name="endtime" size="6" id="edit-endtime" />
|
||||||
|
</div>
|
||||||
|
<div class="event-section">
|
||||||
|
<label for="edit-alarm"><roundcube:label name="calendar.alarms" /></label>
|
||||||
|
<roundcube:object name="plugin.alarm_select" id="edit-alarm" />
|
||||||
|
</div>
|
||||||
|
<div class="event-section" id="calendar-select">
|
||||||
|
<label for="edit-calendar"><roundcube:label name="calendar.calendar" /></label>
|
||||||
|
<roundcube:object name="plugin.calendar_select" id="edit-calendar" />
|
||||||
|
</div>
|
||||||
|
<div class="event-section">
|
||||||
|
<label for="edit-categories"><roundcube:label name="calendar.category" /></label>
|
||||||
|
<roundcube:object name="plugin.category_select" id="edit-categories" />
|
||||||
|
</div>
|
||||||
|
<div class="event-section">
|
||||||
|
<label for="edit-free-busy"><roundcube:label name="calendar.freebusy" /></label>
|
||||||
|
<roundcube:object name="plugin.freebusy_select" id="edit-free-busy" />
|
||||||
|
</div>
|
||||||
|
<div class="event-section">
|
||||||
|
<label for="edit-priority"><roundcube:label name="calendar.priority" /></label>
|
||||||
|
<roundcube:object name="plugin.priority_select" id="edit-priority" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- recurrence settings -->
|
||||||
|
<div id="event-tab-2">
|
||||||
|
<div class="event-section border-after">
|
||||||
|
<roundcube:object name="plugin.recurrence_form" part="frequency" />
|
||||||
|
</div>
|
||||||
|
<div class="recurrence-form border-after" id="recurrence-form-daily">
|
||||||
|
<roundcube:object name="plugin.recurrence_form" part="daily" class="event-section" />
|
||||||
|
</div>
|
||||||
|
<div class="recurrence-form border-after" id="recurrence-form-weekly">
|
||||||
|
<roundcube:object name="plugin.recurrence_form" part="weekly" class="event-section" />
|
||||||
|
</div>
|
||||||
|
<div class="recurrence-form border-after" id="recurrence-form-monthly">
|
||||||
|
<roundcube:object name="plugin.recurrence_form" part="monthly" class="event-section" />
|
||||||
|
</div>
|
||||||
|
<div class="recurrence-form border-after" id="recurrence-form-yearly">
|
||||||
|
<roundcube:object name="plugin.recurrence_form" part="yearly" class="event-section" />
|
||||||
|
</div>
|
||||||
|
<div class="recurrence-form" id="recurrence-form-until">
|
||||||
|
<roundcube:object name="plugin.recurrence_form" part="until" class="event-section" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- attendees list -->
|
||||||
|
<div id="event-tab-3">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- attachments list -->
|
||||||
|
<div id="event-tab-4">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="calendartoolbar">
|
||||||
|
<roundcube:button command="plugin.addevent" type="link" class="buttonPas addevent" classAct="button addevent" classSel="button addeventSel" title="calendar.new_event" content=" " />
|
||||||
|
<roundcube:button command="plugin.print" type="link" class="buttonPas print" classAct="button print" classSel="button printSel" title="calendar.print" content=" " />
|
||||||
|
<roundcube:button command="plugin.export" type="link" class="buttonPas export" classAct="button export" classSel="button exportSel" title="calendar.export" content=" " />
|
||||||
|
<roundcube:container name="toolbar" id="calendartoolbar" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="quicksearchbar">
|
||||||
|
<roundcube:button name="searchmenulink" id="searchmenulink" image="/images/icons/glass.png" />
|
||||||
|
<roundcube:object name="searchform" id="quicksearchbox" />
|
||||||
|
<roundcube:button command="reset-search" id="searchreset" image="/images/icons/reset.gif" title="resetsearch" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<roundcube:object name="plugin.calendar_css" />
|
||||||
|
</body>
|
||||||
|
</html>
|