Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2397570
calendar_driver.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
25 KB
Referenced Files
None
Subscribers
None
calendar_driver.php
View Options
<?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-2014, 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
* '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
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
$categoriesimmutable
=
false
;
public
$alarm_types
=
array
(
'DISPLAY'
);
public
$alarm_absolute
=
true
;
public
$last_error
;
protected
$default_categories
=
array
(
'Personal'
=>
'c0c0c0'
,
'Work'
=>
'ff0000'
,
'Family'
=>
'00ff00'
,
'Holiday'
=>
'ff6600'
,
);
/**
* Get a list of available calendars from this source
*
* @param bool $active Return only active calendars
* @param bool $personal Return only personal calendars
*
* @return array List of calendars
*/
abstract
function
list_calendars
(
$active
=
false
,
$personal
=
false
);
/**
* 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
* 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 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 boolean True on success, Fales on failure
*/
abstract
function
edit_calendar
(
$prop
);
/**
* Set active/subscribed state of a calendar
*
* @param array Hash array with calendar properties
* id: Calendar Identifier
* active: True if calendar is active, false if not
* @return boolean True on success, Fales on failure
*/
abstract
function
subscribe_calendar
(
$prop
);
/**
* Delete the given calendar with all its contents
*
* @param array Hash array with calendar properties
* id: Calendar Identifier
* @return boolean 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 Search string
* @param string Section/source to search
* @return array List of calendars
*/
abstract
function
search_calendars
(
$query
,
$source
);
/**
* Add a single event to the database
*
* @param array 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 Hash array with event properties (see header of this file)
* @return boolean True on success, False on error
*/
abstract
function
edit_event
(
$event
);
/**
* Extended event editing with possible changes to the argument
*
* @param array Hash array with event properties
* @param string New participant status
* @return boolean True on success, False on error
*/
public
function
edit_rsvp
(&
$event
,
$status
)
{
return
$this
->
edit_event
(
$event
);
}
/**
* Move a single event
*
* @param array 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 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 DateTime object with timezone
* end: Event end date/time as DateTime object with 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
* @param boolean Remove event irreversible (mark as deleted otherwise,
* if supported by the backend)
*
* @return boolean True on success, False on error
*/
abstract
function
remove_event
(
$event
,
$force
=
true
);
/**
* Restores a single deleted event (if supported)
*
* @param array Hash array with event properties:
* id: Event identifier
*
* @return boolean True on success, False on error
*/
public
function
restore_event
(
$event
)
{
return
false
;
}
/**
* Return data of a single event
*
* @param mixed UID string or hash array with event properties:
* id: Event identifier
* calendar: Calendar identifier (optional)
* @param boolean If true, only writeable calendars shall be searched
* @param boolean If true, only active calendars shall be searched
* @param boolean If true, only personal calendars shall be searched
*
* @return array Event object as hash array
*/
abstract
function
get_event
(
$event
,
$writeable
=
false
,
$active
=
false
,
$personal
=
false
);
/**
* Get events from source.
*
* @param integer Date range start (unix timestamp)
* @param integer Date range end (unix timestamp)
* @param string Search query (optional)
* @param mixed List of calendar IDs to load events from (either as array or comma-separated string)
* @param boolean Include virtual/recurring events (optional)
* @param integer 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 List of calendar IDs to count events (either as array or comma-separated string)
* @param integer Date range start (unix timestamp)
* @param integer 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 integer Current time (unix timestamp)
* @param mixed 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 identifier
* @param integer 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 object as hash array
* @return boolean True if valid, false if not
*/
public
function
validate
(
$event
)
{
$valid
=
true
;
if
(!
is_object
(
$event
[
'start'
])
||
!
is_a
(
$event
[
'start'
],
'DateTime'
))
$valid
=
false
;
if
(!
is_object
(
$event
[
'end'
])
||
!
is_a
(
$event
[
'end'
],
'DateTime'
))
$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 E-mail address of attendee
* @param integer Requested period start date/time as unix timestamp
* @param integer 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
;
}
/**
* 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 $rev Revisions: "from:to"
*
* @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
,
$rev
)
{
return
false
;
}
/**
* Return full data of a specific revision of an event
*
* @param mixed 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 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 Request action 'form-edit|form-new'
* @param array Calendar properties (e.g. id, color)
* @param array Edit form fields
*
* @return string HTML content of the form
*/
public
function
calendar_form
(
$action
,
$calendar
,
$formfields
)
{
$html
=
''
;
foreach
(
$formfields
as
$field
)
{
$html
.=
html
::
div
(
'form-section'
,
html
::
label
(
$field
[
'id'
],
$field
[
'label'
])
.
$field
[
'value'
]);
}
return
$html
;
}
/**
* 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 integer Event's new start (unix timestamp)
* @param integer Event's new end (unix timestamp)
* @param string Search query (optional)
* @param integer 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
array
();
}
// 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
=
array
();
$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
;
}
$abook
->
set_pagesize
(
10000
);
// check for cached results
$cache_records
=
array
();
$cached
=
$cache
->
get
(
$source
);
// iterate over (cached) contacts
foreach
((
$cached
?:
$abook
->
search
(
'*'
,
''
,
2
,
true
,
true
,
array
(
'birthday'
)))
as
$contact
)
{
if
(
is_array
(
$contact
)
&&
!
empty
(
$contact
[
'birthday'
]))
{
try
{
if
(
is_array
(
$contact
[
'birthday'
]))
$contact
[
'birthday'
]
=
reset
(
$contact
[
'birthday'
]);
$bday
=
$contact
[
'birthday'
]
instanceof
DateTime
?
$contact
[
'birthday'
]
:
new
DateTime
(
$contact
[
'birthday'
],
new
DateTimezone
(
'UTC'
));
$birthyear
=
$bday
->
format
(
'Y'
);
}
catch
(
Exception
$e
)
{
rcube
::
raise_error
(
array
(
'code'
=>
600
,
'type'
=>
'php'
,
'file'
=>
__FILE__
,
'line'
=>
__LINE__
,
'message'
=>
'BIRTHDAY PARSE ERROR: '
.
$e
),
true
,
false
);
continue
;
}
$display_name
=
rcube_addressbook
::
compose_display_name
(
$contact
);
$event_title
=
$rcmail
->
gettext
(
array
(
'name'
=>
'birthdayeventtitle'
,
'vars'
=>
array
(
'name'
=>
$display_name
)),
'calendar'
);
// add stripped record to cache
if
(
empty
(
$cached
))
{
$cache_records
[]
=
array
(
'ID'
=>
$contact
[
'ID'
],
'name'
=>
$display_name
,
'birthday'
=>
$bday
->
format
(
'Y-m-d'
),
);
}
// filter by search term (only name is involved here)
if
(!
empty
(
$search
)
&&
strpos
(
mb_strtolower
(
$event_title
),
$search
)
===
false
)
{
continue
;
}
// quick-and-dirty recurrence computation: just replace the year
$bday
->
setDate
(
$year
,
$bday
->
format
(
'n'
),
$bday
->
format
(
'j'
));
$bday
->
setTime
(
12
,
0
,
0
);
// 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'
));
$year
=
$year2
;
}
// birthday is within requested range
if
(
$bday
<=
$end
&&
$bday
>=
$start
)
{
$age
=
$year
-
$birthyear
;
$event
=
array
(
'id'
=>
rcube_ldap
::
dn_encode
(
'bday:'
.
$source
.
':'
.
$contact
[
'ID'
]
.
':'
.
$year
),
'calendar'
=>
self
::
BIRTHDAY_CALENDAR_ID
,
'title'
=>
$event_title
,
'description'
=>
$rcmail
->
gettext
(
array
(
'name'
=>
'birthdayage'
,
'vars'
=>
array
(
'age'
=>
$age
)),
'calendar'
),
// Add more contact information to description block?
'allday'
=>
true
,
'start'
=>
$bday
,
'alarms'
=>
$alarms
,
);
$event
[
'end'
]
=
clone
$bday
;
$event
[
'end'
]->
add
(
new
DateInterval
(
'PT1H'
));
$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
(
$source
&&
$contact_id
&&
(
$abook
=
$rcmail
->
get_address_book
(
$source
)))
{
$contact
=
$abook
->
get_record
(
$contact_id
,
true
);
if
(
is_array
(
$contact
)
&&
!
empty
(
$contact
[
'birthday'
]))
{
try
{
if
(
is_array
(
$contact
[
'birthday'
]))
$contact
[
'birthday'
]
=
reset
(
$contact
[
'birthday'
]);
$bday
=
$contact
[
'birthday'
]
instanceof
DateTime
?
$contact
[
'birthday'
]
:
new
DateTime
(
$contact
[
'birthday'
],
new
DateTimezone
(
'UTC'
));
$birthyear
=
$bday
->
format
(
'Y'
);
}
catch
(
Exception
$e
)
{
rcube
::
raise_error
(
array
(
'code'
=>
600
,
'type'
=>
'php'
,
'file'
=>
__FILE__
,
'line'
=>
__LINE__
,
'message'
=>
'BIRTHDAY PARSE ERROR: '
.
$e
),
true
,
false
);
return
null
;
}
$display_name
=
rcube_addressbook
::
compose_display_name
(
$contact
);
$event_title
=
$rcmail
->
gettext
(
array
(
'name'
=>
'birthdayeventtitle'
,
'vars'
=>
array
(
'name'
=>
$display_name
)),
'calendar'
);
$event
=
array
(
'id'
=>
rcube_ldap
::
dn_encode
(
'bday:'
.
$source
.
':'
.
$contact
[
'ID'
]
.
':'
.
$year
),
'uid'
=>
rcube_ldap
::
dn_encode
(
'bday:'
.
$source
.
':'
.
$contact
[
'ID'
]
.
':'
.
$birthyear
),
'calendar'
=>
self
::
BIRTHDAY_CALENDAR_ID
,
'title'
=>
$event_title
,
'description'
=>
''
,
'allday'
=>
true
,
'start'
=>
$bday
,
'recurrence'
=>
array
(
'FREQ'
=>
'YEARLY'
,
'INTERVAL'
=>
1
),
'free_busy'
=>
'free'
,
);
$event
[
'end'
]
=
clone
$bday
;
$event
[
'end'
]->
add
(
new
DateInterval
(
'PT1H'
));
return
$event
;
}
}
return
null
;
}
/**
* Store alarm dismissal for birtual birthay events
*
* @param string Event identifier
* @param integer 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
,
array
(
'snooze'
=>
$snooze
,
'notifyat'
=>
$notifyat
));
return
true
;
}
/**
* Handler for user_delete plugin hook
*
* @param array Hash array with hook arguments
* @return array Return arguments for plugin hooks
*/
public
function
user_delete
(
$args
)
{
// TO BE OVERRIDDEN
return
$args
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Mon, Nov 3, 2:13 PM (16 h, 48 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
361042
Default Alt Text
calendar_driver.php (25 KB)
Attached To
Mode
R14 roundcubemail-plugins-kolab
Attached
Detach File
Event Timeline
Log In to Comment