863 lines
31 KiB
PHP
863 lines
31 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Driver interface for the Calendar plugin
|
|
*
|
|
* @version @package_version@
|
|
* @author Lazlo Westerhof <hello@lazlo.me>
|
|
* @author Thomas Bruederli <bruederli@kolabsys.com>
|
|
*
|
|
* Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me>
|
|
* Copyright (C) 2012-2015, Kolab Systems AG <contact@kolabsys.com>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
/**
|
|
* Struct of an internal event object how it is passed from/to the driver classes:
|
|
*
|
|
* $event = array(
|
|
* 'id' => 'Event ID used for editing',
|
|
* 'uid' => 'Unique identifier of this event',
|
|
* 'calendar' => 'Calendar identifier to add event to or where the event is stored',
|
|
* 'start' => DateTime, // Event start date/time as DateTime object
|
|
* 'end' => DateTime, // Event end date/time as DateTime object
|
|
* 'allday' => true|false, // Boolean flag if this is an all-day event
|
|
* 'changed' => DateTime, // Last modification date of event
|
|
* 'title' => 'Event title/summary',
|
|
* 'location' => 'Location string',
|
|
* 'description' => 'Event description',
|
|
* 'url' => 'URL to more information',
|
|
* 'recurrence' => array( // Recurrence definition according to iCalendar (RFC 2445) specification as list of key-value pairs
|
|
* 'FREQ' => 'DAILY|WEEKLY|MONTHLY|YEARLY',
|
|
* 'INTERVAL' => 1...n,
|
|
* 'UNTIL' => DateTime,
|
|
* 'COUNT' => 1..n, // number of times
|
|
* // + more properties (see http://www.kanzaki.com/docs/ical/recur.html)
|
|
* 'EXDATE' => array(), // list of DateTime objects of exception Dates/Times
|
|
* 'EXCEPTIONS' => array(<event>), list of event objects which denote exceptions in the recurrence chain
|
|
* ),
|
|
* 'recurrence_id' => 'ID of the recurrence group', // usually the ID of the starting event
|
|
* '_instance' => 'ID of the recurring instance', // identifies an instance within a recurrence chain
|
|
* 'categories' => 'Event category',
|
|
* 'free_busy' => 'free|busy|outofoffice|tentative', // Show time as
|
|
* 'status' => 'TENTATIVE|CONFIRMED|CANCELLED', // event status according to RFC 2445
|
|
* 'priority' => 0-9, // Event priority (0=undefined, 1=highest, 9=lowest)
|
|
* 'sensitivity' => 'public|private|confidential', // Event sensitivity
|
|
* 'alarms' => '-15M:DISPLAY', // DEPRECATED Reminder settings inspired by valarm definition (e.g. display alert 15 minutes before event)
|
|
* 'valarms' => array( // List of reminders (new format), each represented as a hash array:
|
|
* array(
|
|
* 'trigger' => '-PT90M', // ISO 8601 period string prefixed with '+' or '-', or DateTime object
|
|
* 'action' => 'DISPLAY|EMAIL|AUDIO',
|
|
* 'duration' => 'PT15M', // ISO 8601 period string
|
|
* 'repeat' => 0, // number of repetitions
|
|
* 'description' => '', // text to display for DISPLAY actions
|
|
* 'summary' => '', // message text for EMAIL actions
|
|
* 'attendees' => array(), // list of email addresses to receive alarm messages
|
|
* ),
|
|
* ),
|
|
* 'attachments' => array( // List of attachments
|
|
* 'name' => 'File name',
|
|
* 'mimetype' => 'Content type',
|
|
* 'size' => 1..n, // in bytes
|
|
* 'id' => 'Attachment identifier'
|
|
* ),
|
|
* 'deleted_attachments' => array(), // array of attachment identifiers to delete when event is updated
|
|
* 'attendees' => array( // List of event participants
|
|
* 'name' => 'Participant name',
|
|
* 'email' => 'Participant e-mail address', // used as identifier
|
|
* 'role' => 'ORGANIZER|REQ-PARTICIPANT|OPT-PARTICIPANT|CHAIR',
|
|
* 'status' => 'NEEDS-ACTION|UNKNOWN|ACCEPTED|TENTATIVE|DECLINED'
|
|
* 'rsvp' => true|false,
|
|
* ),
|
|
*
|
|
* '_savemode' => 'all|future|current|new', // How changes on recurring event should be handled
|
|
* '_notify' => true|false, // whether to notify event attendees about changes
|
|
* '_fromcalendar' => 'Calendar identifier where the event was stored before',
|
|
* );
|
|
*/
|
|
|
|
/**
|
|
* Interface definition for calendar driver classes
|
|
*/
|
|
abstract class calendar_driver
|
|
{
|
|
const FILTER_ALL = 0;
|
|
const FILTER_WRITEABLE = 1;
|
|
const FILTER_INSERTABLE = 2;
|
|
const FILTER_ACTIVE = 4;
|
|
const FILTER_PERSONAL = 8;
|
|
const FILTER_PRIVATE = 16;
|
|
const FILTER_CONFIDENTIAL = 32;
|
|
const FILTER_SHARED = 64;
|
|
const BIRTHDAY_CALENDAR_ID = '__bdays__';
|
|
|
|
// features supported by backend
|
|
public $alarms = false;
|
|
public $attendees = false;
|
|
public $freebusy = false;
|
|
public $attachments = false;
|
|
public $undelete = false;
|
|
public $history = false;
|
|
public $alarm_types = ['DISPLAY'];
|
|
public $alarm_absolute = true;
|
|
public $categoriesimmutable = false;
|
|
public $last_error;
|
|
|
|
protected $default_categories = [
|
|
'Personal' => 'c0c0c0',
|
|
'Work' => 'ff0000',
|
|
'Family' => '00ff00',
|
|
'Holiday' => 'ff6600',
|
|
];
|
|
|
|
/**
|
|
* Get a list of available calendars from this source
|
|
*
|
|
* @param int $filter Bitmask defining filter criterias.
|
|
* See FILTER_* constants for possible values.
|
|
*
|
|
* @return array List of calendars
|
|
*/
|
|
abstract function list_calendars($filter = 0);
|
|
|
|
/**
|
|
* Create a new calendar assigned to the current user
|
|
*
|
|
* @param array $prop Hash array with calendar properties
|
|
* name: Calendar name
|
|
* color: The color of the calendar
|
|
* showalarms: True if alarms are enabled
|
|
*
|
|
* @return mixed ID of the calendar on success, False on error
|
|
*/
|
|
abstract function create_calendar($prop);
|
|
|
|
/**
|
|
* Update properties of an existing calendar
|
|
*
|
|
* @param array $prop Hash array with calendar properties
|
|
* id: Calendar Identifier
|
|
* name: Calendar name
|
|
* color: The color of the calendar
|
|
* showalarms: True if alarms are enabled (if supported)
|
|
*
|
|
* @return bool True on success, Fales on failure
|
|
*/
|
|
abstract function edit_calendar($prop);
|
|
|
|
/**
|
|
* Set active/subscribed state of a calendar
|
|
*
|
|
* @param array $prop Hash array with calendar properties
|
|
* id: Calendar Identifier
|
|
* active: True if calendar is active, false if not
|
|
*
|
|
* @return bool True on success, Fales on failure
|
|
*/
|
|
abstract function subscribe_calendar($prop);
|
|
|
|
/**
|
|
* Delete the given calendar with all its contents
|
|
*
|
|
* @param array $prop Hash array with calendar properties
|
|
* id: Calendar Identifier
|
|
*
|
|
* @return bool True on success, Fales on failure
|
|
*/
|
|
abstract function delete_calendar($prop);
|
|
|
|
/**
|
|
* Search for shared or otherwise not listed calendars the user has access
|
|
*
|
|
* @param string $query Search string
|
|
* @param string $source Section/source to search
|
|
*
|
|
* @return array List of calendars
|
|
*/
|
|
abstract function search_calendars($query, $source);
|
|
|
|
/**
|
|
* Add a single event to the database
|
|
*
|
|
* @param array $event Hash array with event properties (see header of this file)
|
|
*
|
|
* @return mixed New event ID on success, False on error
|
|
*/
|
|
abstract function new_event($event);
|
|
|
|
/**
|
|
* Update an event entry with the given data
|
|
*
|
|
* @param array $event Hash array with event properties (see header of this file)
|
|
*
|
|
* @return bool True on success, False on error
|
|
*/
|
|
abstract function edit_event($event);
|
|
|
|
/**
|
|
* Extended event editing with possible changes to the argument
|
|
*
|
|
* @param array &$event Hash array with event properties
|
|
* @param string $status New participant status
|
|
* @param array $attendees List of hash arrays with updated attendees
|
|
*
|
|
* @return bool True on success, False on error
|
|
*/
|
|
public function edit_rsvp(&$event, $status, $attendees)
|
|
{
|
|
return $this->edit_event($event);
|
|
}
|
|
|
|
/**
|
|
* Update the participant status for the given attendee
|
|
*
|
|
* @param array &$event Hash array with event properties
|
|
* @param array $attendees List of hash arrays each represeting an updated attendee
|
|
*
|
|
* @return bool True on success, False on error
|
|
*/
|
|
public function update_attendees(&$event, $attendees)
|
|
{
|
|
return $this->edit_event($event);
|
|
}
|
|
|
|
/**
|
|
* Move a single event
|
|
*
|
|
* @param array $event Hash array with event properties:
|
|
* id: Event identifier
|
|
* start: Event start date/time as DateTime object
|
|
* end: Event end date/time as DateTime object
|
|
* allday: Boolean flag if this is an all-day event
|
|
*
|
|
* @return bool True on success, False on error
|
|
*/
|
|
abstract function move_event($event);
|
|
|
|
/**
|
|
* Resize a single event
|
|
*
|
|
* @param array $event Hash array with event properties:
|
|
* id: Event identifier
|
|
* start: Event start date/time as DateTime object with timezone
|
|
* end: Event end date/time as DateTime object with timezone
|
|
*
|
|
* @return bool True on success, False on error
|
|
*/
|
|
abstract function resize_event($event);
|
|
|
|
/**
|
|
* Remove a single event from the database
|
|
*
|
|
* @param array $event Hash array with event properties:
|
|
* id: Event identifier
|
|
* @param bool $force Remove event irreversible (mark as deleted otherwise,
|
|
* if supported by the backend)
|
|
*
|
|
* @return bool True on success, False on error
|
|
*/
|
|
abstract function remove_event($event, $force = true);
|
|
|
|
/**
|
|
* Restores a single deleted event (if supported)
|
|
*
|
|
* @param array $event Hash array with event properties:
|
|
* id: Event identifier
|
|
*
|
|
* @return bool True on success, False on error
|
|
*/
|
|
public function restore_event($event)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Return data of a single event
|
|
*
|
|
* @param mixed $event UID string or hash array with event properties:
|
|
* id: Event identifier
|
|
* uid: Event UID
|
|
* _instance: Instance identifier in combination with uid (optional)
|
|
* calendar: Calendar identifier (optional)
|
|
* @param int $scope Bitmask defining the scope to search events in.
|
|
* See FILTER_* constants for possible values.
|
|
* @param bool $full If true, recurrence exceptions shall be added
|
|
*
|
|
* @return array Event object as hash array
|
|
*/
|
|
abstract function get_event($event, $scope = 0, $full = false);
|
|
|
|
/**
|
|
* Get events from source.
|
|
*
|
|
* @param int $start Date range start (unix timestamp)
|
|
* @param int $end Date range end (unix timestamp)
|
|
* @param string $query Search query (optional)
|
|
* @param mixed $calendars List of calendar IDs to load events from (either as array or comma-separated string)
|
|
* @param bool $virtual Include virtual/recurring events (optional)
|
|
* @param int $modifiedsince Only list events modified since this time (unix timestamp)
|
|
*
|
|
* @return array A list of event objects (see header of this file for struct of an event)
|
|
*/
|
|
abstract function load_events($start, $end, $query = null, $calendars = null, $virtual = 1, $modifiedsince = null);
|
|
|
|
/**
|
|
* Get number of events in the given calendar
|
|
*
|
|
* @param mixed $calendars List of calendar IDs to count events (either as array or comma-separated string)
|
|
* @param int $start Date range start (unix timestamp)
|
|
* @param int $end Date range end (unix timestamp)
|
|
*
|
|
* @return array Hash array with counts grouped by calendar ID
|
|
*/
|
|
abstract function count_events($calendars, $start, $end = null);
|
|
|
|
/**
|
|
* Get a list of pending alarms to be displayed to the user
|
|
*
|
|
* @param int $time Current time (unix timestamp)
|
|
* @param mixed $calendars List of calendar IDs to show alarms for (either as array or comma-separated string)
|
|
*
|
|
* @return array A list of alarms, each encoded as hash array:
|
|
* id: Event identifier
|
|
* uid: Unique identifier of this event
|
|
* start: Event start date/time as DateTime object
|
|
* end: Event end date/time as DateTime object
|
|
* allday: Boolean flag if this is an all-day event
|
|
* title: Event title/summary
|
|
* location: Location string
|
|
*/
|
|
abstract function pending_alarms($time, $calendars = null);
|
|
|
|
/**
|
|
* (User) feedback after showing an alarm notification
|
|
* This should mark the alarm as 'shown' or snooze it for the given amount of time
|
|
*
|
|
* @param string $event_id Event identifier
|
|
* @param int $snooze Suspend the alarm for this number of seconds
|
|
*/
|
|
abstract function dismiss_alarm($event_id, $snooze = 0);
|
|
|
|
/**
|
|
* Check the given event object for validity
|
|
*
|
|
* @param array $event Event object as hash array
|
|
*
|
|
* @return boolean True if valid, false if not
|
|
*/
|
|
public function validate($event)
|
|
{
|
|
$valid = true;
|
|
|
|
if (empty($event['start']) || !is_object($event['start']) || (!is_a($event['start'], 'DateTime') && !is_a($event['start'], 'DateTimeImmutable'))) {
|
|
$valid = false;
|
|
}
|
|
|
|
if (empty($event['end']) || !is_object($event['end']) || (!is_a($event['end'], 'DateTime') && !is_a($event['end'], 'DateTimeImmutable'))) {
|
|
$valid = false;
|
|
}
|
|
|
|
return $valid;
|
|
}
|
|
|
|
/**
|
|
* Get list of event's attachments.
|
|
* Drivers can return list of attachments as event property.
|
|
* If they will do not do this list_attachments() method will be used.
|
|
*
|
|
* @param array $event Hash array with event properties:
|
|
* id: Event identifier
|
|
* calendar: Calendar identifier
|
|
*
|
|
* @return array List of attachments, each as hash array:
|
|
* id: Attachment identifier
|
|
* name: Attachment name
|
|
* mimetype: MIME content type of the attachment
|
|
* size: Attachment size
|
|
*/
|
|
public function list_attachments($event) { }
|
|
|
|
/**
|
|
* Get attachment properties
|
|
*
|
|
* @param string $id Attachment identifier
|
|
* @param array $event Hash array with event properties:
|
|
* id: Event identifier
|
|
* calendar: Calendar identifier
|
|
*
|
|
* @return array Hash array with attachment properties:
|
|
* id: Attachment identifier
|
|
* name: Attachment name
|
|
* mimetype: MIME content type of the attachment
|
|
* size: Attachment size
|
|
*/
|
|
public function get_attachment($id, $event) { }
|
|
|
|
/**
|
|
* Get attachment body
|
|
*
|
|
* @param string $id Attachment identifier
|
|
* @param array $event Hash array with event properties:
|
|
* id: Event identifier
|
|
* calendar: Calendar identifier
|
|
*
|
|
* @return string Attachment body
|
|
*/
|
|
public function get_attachment_body($id, $event) { }
|
|
|
|
/**
|
|
* Build a struct representing the given message reference
|
|
*
|
|
* @param object|string $uri_or_headers rcube_message_header instance holding the message headers
|
|
* or an URI from a stored link referencing a mail message.
|
|
* @param string $folder IMAP folder the message resides in
|
|
*
|
|
* @return array An struct referencing the given IMAP message
|
|
*/
|
|
public function get_message_reference($uri_or_headers, $folder = null)
|
|
{
|
|
// to be implemented by the derived classes
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* List availabale categories
|
|
* The default implementation reads them from config/user prefs
|
|
*/
|
|
public function list_categories()
|
|
{
|
|
$rcmail = rcube::get_instance();
|
|
return $rcmail->config->get('calendar_categories', $this->default_categories);
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*
|
|
* @param string $email E-mail address of attendee
|
|
* @param int $start Requested period start date/time as unix timestamp
|
|
* @param int $end Requested period end date/time as unix timestamp
|
|
*
|
|
* @return array List of busy timeslots within the requested range
|
|
*/
|
|
public function get_freebusy_list($email, $start, $end)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Create instances of a recurring event
|
|
*
|
|
* @param array $event Hash array with event properties
|
|
* @param DateTime $start Start date of the recurrence window
|
|
* @param DateTime $end End date of the recurrence window
|
|
*
|
|
* @return array List of recurring event instances
|
|
*/
|
|
public function get_recurring_events($event, $start, $end = null)
|
|
{
|
|
$events = [];
|
|
|
|
if (!empty($event['recurrence'])) {
|
|
// include library class
|
|
require_once(dirname(__FILE__) . '/../lib/calendar_recurrence.php');
|
|
|
|
$rcmail = rcmail::get_instance();
|
|
$recurrence = new calendar_recurrence($rcmail->plugins->get_plugin('calendar'), $event);
|
|
$recurrence_id_format = libcalendaring::recurrence_id_format($event);
|
|
|
|
// determine a reasonable end date if none given
|
|
if (!$end) {
|
|
switch ($event['recurrence']['FREQ']) {
|
|
case 'YEARLY': $intvl = 'P100Y'; break;
|
|
case 'MONTHLY': $intvl = 'P20Y'; break;
|
|
default: $intvl = 'P10Y'; break;
|
|
}
|
|
|
|
$end = clone $event['start'];
|
|
$end->add(new DateInterval($intvl));
|
|
}
|
|
|
|
$i = 0;
|
|
while ($next_event = $recurrence->next_instance()) {
|
|
// add to output if in range
|
|
if (($next_event['start'] <= $end && $next_event['end'] >= $start)) {
|
|
$next_event['_instance'] = $next_event['start']->format($recurrence_id_format);
|
|
$next_event['id'] = $next_event['uid'] . '-' . $exception['_instance'];
|
|
$next_event['recurrence_id'] = $event['uid'];
|
|
$events[] = $next_event;
|
|
}
|
|
else if ($next_event['start'] > $end) { // stop loop if out of range
|
|
break;
|
|
}
|
|
|
|
// avoid endless recursion loops
|
|
if (++$i > 1000) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $events;
|
|
}
|
|
|
|
/**
|
|
* Provide a list of revisions for the given event
|
|
*
|
|
* @param array $event Hash array with event properties:
|
|
* id: Event identifier
|
|
* calendar: Calendar identifier
|
|
*
|
|
* @return array List of changes, each as a hash array:
|
|
* rev: Revision number
|
|
* type: Type of the change (create, update, move, delete)
|
|
* date: Change date
|
|
* user: The user who executed the change
|
|
* ip: Client IP
|
|
* destination: Destination calendar for 'move' type
|
|
*/
|
|
public function get_event_changelog($event)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get a list of property changes beteen two revisions of an event
|
|
*
|
|
* @param array $event Hash array with event properties:
|
|
* id: Event identifier
|
|
* calendar: Calendar identifier
|
|
* @param mixed $rev1 Old Revision
|
|
* @param mixed $rev2 New Revision
|
|
*
|
|
* @return array List of property changes, each as a hash array:
|
|
* property: Revision number
|
|
* old: Old property value
|
|
* new: Updated property value
|
|
*/
|
|
public function get_event_diff($event, $rev1, $rev2)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Return full data of a specific revision of an event
|
|
*
|
|
* @param mixed $event UID string or hash array with event properties:
|
|
* id: Event identifier
|
|
* calendar: Calendar identifier
|
|
* @param mixed $rev Revision number
|
|
*
|
|
* @return array Event object as hash array
|
|
* @see self::get_event()
|
|
*/
|
|
public function get_event_revison($event, $rev)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Command the backend to restore a certain revision of an event.
|
|
* This shall replace the current event with an older version.
|
|
*
|
|
* @param mixed $event UID string or hash array with event properties:
|
|
* id: Event identifier
|
|
* calendar: Calendar identifier
|
|
* @param mixed $rev Revision number
|
|
*
|
|
* @return boolean True on success, False on failure
|
|
*/
|
|
public function restore_event_revision($event, $rev)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Callback function to produce driver-specific calendar create/edit form
|
|
*
|
|
* @param string $action Request action 'form-edit|form-new'
|
|
* @param array $calendar Calendar properties (e.g. id, color)
|
|
* @param array $formfields Edit form fields
|
|
*
|
|
* @return string HTML content of the form
|
|
*/
|
|
public function calendar_form($action, $calendar, $formfields)
|
|
{
|
|
$table = new html_table(['cols' => 2, 'class' => 'propform']);
|
|
|
|
foreach ($formfields as $col => $colprop) {
|
|
$label = !empty($colprop['label']) ? $colprop['label'] : $rcmail->gettext("$domain.$col");
|
|
|
|
$table->add('title', html::label($colprop['id'], rcube::Q($label)));
|
|
$table->add(null, $colprop['value']);
|
|
}
|
|
|
|
return $table->show();
|
|
}
|
|
|
|
/**
|
|
* Compose a list of birthday events from the contact records in the user's address books.
|
|
*
|
|
* This is a default implementation using Roundcube's address book API.
|
|
* It can be overriden with a more optimized version by the individual drivers.
|
|
*
|
|
* @param int $start Event's new start (unix timestamp)
|
|
* @param int $end Event's new end (unix timestamp)
|
|
* @param string $search Search query (optional)
|
|
* @param int $modifiedsince Only list events modified since this time (unix timestamp)
|
|
*
|
|
* @return array A list of event records
|
|
*/
|
|
public function load_birthday_events($start, $end, $search = null, $modifiedsince = null)
|
|
{
|
|
// ignore update requests for simplicity reasons
|
|
if (!empty($modifiedsince)) {
|
|
return [];
|
|
}
|
|
|
|
// convert to DateTime for comparisons
|
|
$start = new DateTime('@'.$start);
|
|
$end = new DateTime('@'.$end);
|
|
// extract the current year
|
|
$year = $start->format('Y');
|
|
$year2 = $end->format('Y');
|
|
|
|
$events = [];
|
|
$search = mb_strtolower($search);
|
|
$rcmail = rcmail::get_instance();
|
|
$cache = $rcmail->get_cache('calendar.birthdays', 'db', 3600);
|
|
$cache->expunge();
|
|
|
|
$alarm_type = $rcmail->config->get('calendar_birthdays_alarm_type', '');
|
|
$alarm_offset = $rcmail->config->get('calendar_birthdays_alarm_offset', '-1D');
|
|
$alarms = $alarm_type ? $alarm_offset . ':' . $alarm_type : null;
|
|
|
|
// let the user select the address books to consider in prefs
|
|
$selected_sources = $rcmail->config->get('calendar_birthday_adressbooks');
|
|
$sources = $selected_sources ?: array_keys($rcmail->get_address_sources(false, true));
|
|
|
|
foreach ($sources as $source) {
|
|
$abook = $rcmail->get_address_book($source);
|
|
|
|
// skip LDAP address books unless selected by the user
|
|
if (!$abook || ($abook instanceof rcube_ldap && empty($selected_sources))) {
|
|
continue;
|
|
}
|
|
|
|
// skip collected recipients/senders addressbooks
|
|
if (is_a($abook, 'rcube_addresses')) {
|
|
continue;
|
|
}
|
|
|
|
$abook->set_pagesize(10000);
|
|
|
|
// check for cached results
|
|
$cache_records = [];
|
|
$cached = $cache->get($source);
|
|
|
|
// iterate over (cached) contacts
|
|
foreach (($cached ?: $abook->search('*', '', 2, true, true, ['birthday'])) as $contact) {
|
|
$event = self::parse_contact($contact, $source);
|
|
|
|
if (empty($event)) {
|
|
continue;
|
|
}
|
|
|
|
// add stripped record to cache
|
|
if (empty($cached)) {
|
|
$cache_records[] = [
|
|
'ID' => $contact['ID'],
|
|
'name' => $event['_displayname'],
|
|
'birthday' => $event['start']->format('Y-m-d'),
|
|
];
|
|
}
|
|
|
|
// filter by search term (only name is involved here)
|
|
if (!empty($search) && strpos(mb_strtolower($event['title']), $search) === false) {
|
|
continue;
|
|
}
|
|
|
|
$bday = clone $event['start'];
|
|
$byear = $bday->format('Y');
|
|
|
|
// quick-and-dirty recurrence computation: just replace the year
|
|
$bday->setDate($year, $bday->format('n'), $bday->format('j'));
|
|
$bday->setTime(12, 0, 0);
|
|
$this_year = $year;
|
|
|
|
// date range reaches over multiple years: use end year if not in range
|
|
if (($bday > $end || $bday < $start) && $year2 != $year) {
|
|
$bday->setDate($year2, $bday->format('n'), $bday->format('j'));
|
|
$this_year = $year2;
|
|
}
|
|
|
|
// birthday is within requested range
|
|
if ($bday <= $end && $bday >= $start) {
|
|
unset($event['_displayname']);
|
|
$event['alarms'] = $alarms;
|
|
|
|
// if this is not the first occurence modify event details
|
|
// but not when this is "all birthdays feed" request
|
|
if ($year2 - $year < 10 && ($age = ($this_year - $byear))) {
|
|
$label = ['name' => 'birthdayage', 'vars' => ['age' => $age]];
|
|
|
|
$event['description'] = $rcmail->gettext($label, 'calendar');
|
|
$event['start'] = $bday;
|
|
$event['end'] = clone $bday;
|
|
|
|
unset($event['recurrence']);
|
|
}
|
|
|
|
// add the main instance
|
|
$events[] = $event;
|
|
}
|
|
}
|
|
|
|
// store collected contacts in cache
|
|
if (empty($cached)) {
|
|
$cache->write($source, $cache_records);
|
|
}
|
|
}
|
|
|
|
return $events;
|
|
}
|
|
|
|
/**
|
|
* Get a single birthday calendar event
|
|
*/
|
|
public function get_birthday_event($id)
|
|
{
|
|
// decode $id
|
|
list(, $source, $contact_id, $year) = explode(':', rcube_ldap::dn_decode($id));
|
|
|
|
$rcmail = rcmail::get_instance();
|
|
|
|
if (strlen($source) && $contact_id && ($abook = $rcmail->get_address_book($source))) {
|
|
if ($contact = $abook->get_record($contact_id, true)) {
|
|
return self::parse_contact($contact, $source);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse contact and create an event for its birthday
|
|
*
|
|
* @param array $contact Contact data
|
|
* @param string $source Addressbook source ID
|
|
*
|
|
* @return array|null Birthday event data
|
|
*/
|
|
public static function parse_contact($contact, $source)
|
|
{
|
|
if (!is_array($contact)) {
|
|
return;
|
|
}
|
|
|
|
if (!empty($contact['birthday']) && is_array($contact['birthday'])) {
|
|
$contact['birthday'] = reset($contact['birthday']);
|
|
}
|
|
|
|
if (empty($contact['birthday'])) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$bday = $contact['birthday'];
|
|
if (!$bday instanceof DateTime) {
|
|
$bday = new DateTime($bday, new DateTimezone('UTC'));
|
|
}
|
|
$bday->_dateonly = true;
|
|
}
|
|
catch (Exception $e) {
|
|
rcube::raise_error([
|
|
'code' => 600,
|
|
'file' => __FILE__,
|
|
'line' => __LINE__,
|
|
'message' => 'BIRTHDAY PARSE ERROR: ' . $e->getMessage()
|
|
],
|
|
true, false
|
|
);
|
|
return;
|
|
}
|
|
|
|
$rcmail = rcmail::get_instance();
|
|
$birthyear = $bday->format('Y');
|
|
$display_name = rcube_addressbook::compose_display_name($contact);
|
|
$label = ['name' => 'birthdayeventtitle', 'vars' => ['name' => $display_name]];
|
|
$event_title = $rcmail->gettext($label, 'calendar');
|
|
$uid = rcube_ldap::dn_encode('bday:' . $source . ':' . $contact['ID'] . ':' . $birthyear);
|
|
|
|
return [
|
|
'id' => $uid,
|
|
'uid' => $uid,
|
|
'calendar' => self::BIRTHDAY_CALENDAR_ID,
|
|
'title' => $event_title,
|
|
'description' => '',
|
|
'allday' => true,
|
|
'start' => $bday,
|
|
'end' => clone $bday,
|
|
'recurrence' => ['FREQ' => 'YEARLY', 'INTERVAL' => 1],
|
|
'free_busy' => 'free',
|
|
'_displayname' => $display_name,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Store alarm dismissal for birtual birthay events
|
|
*
|
|
* @param string $event_id Event identifier
|
|
* @param int $snooze Suspend the alarm for this number of seconds
|
|
*/
|
|
public function dismiss_birthday_alarm($event_id, $snooze = 0)
|
|
{
|
|
$rcmail = rcmail::get_instance();
|
|
$cache = $rcmail->get_cache('calendar.birthdayalarms', 'db', 86400 * 30);
|
|
$cache->remove($event_id);
|
|
|
|
// compute new notification time or disable if not snoozed
|
|
$notifyat = $snooze > 0 ? time() + $snooze : null;
|
|
$cache->set($event_id, ['snooze' => $snooze, 'notifyat' => $notifyat]);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Handler for user_delete plugin hook
|
|
*
|
|
* @param array $args Hash array with hook arguments
|
|
*
|
|
* @return array Return arguments for plugin hooks
|
|
*/
|
|
public function user_delete($args)
|
|
{
|
|
// TO BE OVERRIDDEN
|
|
return $args;
|
|
}
|
|
}
|