Page MenuHomePhorge

No OneTemporary

Size
234 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/kolab_sync_backend.php b/lib/kolab_sync_backend.php
index 6fc65a1..b985aef 100644
--- a/lib/kolab_sync_backend.php
+++ b/lib/kolab_sync_backend.php
@@ -1,808 +1,811 @@
<?php
/**
+--------------------------------------------------------------------------+
| Kolab Sync (ActiveSync for Kolab) |
| |
| Copyright (C) 2011-2012, 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/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
class kolab_sync_backend
{
/**
* Singleton instace of kolab_sync_backend
*
* @var kolab_sync_backend
*/
static protected $instance;
protected $storage;
protected $folder_meta;
protected $folder_uids;
protected $root_meta;
static protected $types = array(
1 => '',
2 => 'mail.inbox',
3 => 'mail.drafts',
4 => 'mail.wastebasket',
5 => 'mail.sentitems',
6 => 'mail.outbox',
7 => 'task.default',
8 => 'event.default',
9 => 'contact.default',
10 => 'note.default',
11 => 'journal.default',
12 => 'mail',
13 => 'event',
14 => 'contact',
15 => 'task',
16 => 'journal',
17 => 'note',
);
static protected $classes = array(
Syncroton_Data_Factory::CLASS_CALENDAR => 'event',
Syncroton_Data_Factory::CLASS_CONTACTS => 'contact',
Syncroton_Data_Factory::CLASS_EMAIL => 'mail',
Syncroton_Data_Factory::CLASS_TASKS => 'task',
);
const ROOT_MAILBOX = 'INBOX';
// const ROOT_MAILBOX = '';
const ASYNC_KEY = '/private/vendor/kolab/activesync';
const UID_KEY = '/shared/vendor/cmu/cyrus-imapd/uniqueid';
/**
* This implements the 'singleton' design pattern
*
* @return kolab_sync_backend The one and only instance
*/
static function get_instance()
{
if (!self::$instance) {
self::$instance = new kolab_sync_backend;
self::$instance->startup(); // init AFTER object was linked with self::$instance
}
return self::$instance;
}
/**
* Class initialization
*/
public function startup()
{
$this->storage = rcube::get_instance()->get_storage();
// @TODO: reset cache? if we do this for every request the cache would be useless
// There's no session here
//$this->storage->clear_cache('mailboxes.', true);
// set additional header used by libkolab
$this->storage->set_options(array(
// @TODO: there can be Roundcube plugins defining additional headers,
// we maybe would need to add them here
'fetch_headers' => 'X-KOLAB-TYPE X-KOLAB-MIME-VERSION',
'skip_deleted' => true,
'threading' => false,
));
// Disable paging
$this->storage->set_pagesize(999999);
}
/**
* List known devices
*
* @return array Device list as hash array
*/
public function devices_list()
{
if ($this->root_meta === null) {
// @TODO: consider server annotation instead of INBOX
if ($meta = $this->storage->get_metadata(self::ROOT_MAILBOX, self::ASYNC_KEY)) {
$this->root_meta = $this->unserialize_metadata($meta[self::ROOT_MAILBOX][self::ASYNC_KEY]);
}
else {
$this->root_meta = array();
}
}
if (!empty($this->root_meta['DEVICE']) && is_array($this->root_meta['DEVICE'])) {
return $this->root_meta['DEVICE'];
}
return array();
}
/**
* Get list of folders available for sync
*
* @param string $deviceid Device identifier
* @param string $type Folder type
*
* @return array|bool List of mailbox folders, False on backend failure
*/
public function folders_list($deviceid, $type)
{
// get all folders of specified type
$folders = (array) kolab_storage::list_folders('', '*', $type, false, $typedata);
// get folders activesync config
$folderdata = $this->folder_meta();
if (!is_array($folders) || !is_array($folderdata)) {
return false;
}
$folders_list = array();
// check if folders are "subscribed" for activesync
foreach ($folderdata as $folder => $meta) {
if (empty($meta['FOLDER']) || empty($meta['FOLDER'][$deviceid])
|| empty($meta['FOLDER'][$deviceid]['S'])
) {
continue;
}
if (!empty($type) && !in_array($folder, $folders)) {
continue;
}
// Activesync folder identifier (serverId)
$folder_type = $typedata[$folder];
$folder_id = self::folder_id($folder, $folder_type);
$folders_list[$folder_id] = $this->folder_data($folder, $folder_type);
}
return $folders_list;
}
/**
* Getter for folder metadata
*
* @return array|bool Hash array with meta data for each folder, False on backend failure
*/
public function folder_meta()
{
if (!isset($this->folder_meta)) {
$this->folder_meta = array();
// get folders activesync config
$folderdata = $this->storage->get_metadata("*", self::ASYNC_KEY);
if (!is_array($folderdata)) {
return false;
}
foreach ($folderdata as $folder => $meta) {
if ($asyncdata = $meta[self::ASYNC_KEY]) {
if ($metadata = $this->unserialize_metadata($asyncdata)) {
$this->folder_meta[$folder] = $metadata;
}
}
}
}
return $this->folder_meta;
}
/**
* Creates folder and subscribes to the device
*
* @param string $name Folder name (UTF7-IMAP)
* @param int $type Folder (ActiveSync) type
* @param string $deviceid Device identifier
*
* @return bool True on success, False on failure
*/
public function folder_create($name, $type, $deviceid)
{
if ($this->storage->folder_exists($name)) {
$created = true;
}
else {
$type = self::type_activesync2kolab($type);
$created = kolab_storage::folder_create($name, $type, true);
}
if ($created) {
// Set ActiveSync subscription flag
$this->folder_set($name, $deviceid, 1);
return true;
}
return false;
}
/**
* Renames a folder
*
* @param string $old_name Old folder name (UTF7-IMAP)
* @param string $new_name New folder name (UTF7-IMAP)
* @param int $type Folder (ActiveSync) type
* @param string $deviceid Device identifier
*
* @return bool True on success, False on failure
*/
public function folder_rename($old_name, $new_name, $type, $deviceid)
{
$moved = kolab_storage::folder_rename($old_name, $new_name);
if ($moved) {
// UnSet ActiveSync subscription flag
$this->folder_set($old_name, $deviceid, 0);
// Set ActiveSync subscription flag
$this->folder_set($new_name, $deviceid, 1);
return true;
}
return false;
}
/**
* Deletes folder
*
* @param string $name Folder name (UTF7-IMAP)
* @param string $deviceid Device identifier
*
*/
public function folder_delete($name, $deviceid)
{
unset($this->folder_meta[$name]);
return kolab_storage::folder_delete($name);
}
/**
* Sets ActiveSync subscription flag on a folder
*
* @param string $name Folder name (UTF7-IMAP)
* @param string $deviceid Device identifier
* @param int $flag Flag value (0|1|2)
*/
public function folder_set($name, $deviceid, $flag)
{
if (empty($deviceid)) {
return false;
}
// get folders activesync config
$metadata = $this->folder_meta();
if (!is_array($metadata)) {
return false;
}
$metadata = $metadata[$name];
if ($flag) {
if (empty($metadata)) {
$metadata = array();
}
if (empty($metadata['FOLDER'])) {
$metadata['FOLDER'] = array();
}
if (empty($metadata['FOLDER'][$deviceid])) {
$metadata['FOLDER'][$deviceid] = array();
}
// Z-Push uses:
// 1 - synchronize, no alarms
// 2 - synchronize with alarms
$metadata['FOLDER'][$deviceid]['S'] = $flag;
}
if (!$flag) {
unset($metadata['FOLDER'][$deviceid]['S']);
if (empty($metadata['FOLDER'][$deviceid])) {
unset($metadata['FOLDER'][$deviceid]);
}
if (empty($metadata['FOLDER'])) {
unset($metadata['FOLDER']);
}
if (empty($metadata)) {
$metadata = null;
}
}
// Return if nothing's been changed
if (!self::data_array_diff($this->folder_meta[$name], $metadata)) {
return true;
}
$this->folder_meta[$name] = $metadata;
return $this->storage->set_metadata($name, array(
self::ASYNC_KEY => $this->serialize_metadata($metadata)));
}
public function device_get($id)
{
$devices_list = $this->devices_list();
$result = $devices_list[$id];
return $result;
}
/**
* Registers new device on server
*
* @param array $device Device data
* @param string $id Device ID
*
* @return bool True on success, False on failure
*/
public function device_create($device, $id)
{
// Fill local cache
$this->devices_list();
// Some devices create dummy devices with name "validate" (#1109)
// This device entry is used in two initial requests, but later
// the device registers a real name. We can remove this dummy entry
// on new device creation
$this->device_delete('validate');
// Old Kolab_ZPush device parameters
// MODE: -1 | 0 | 1 (not set | flatmode | foldermode)
// TYPE: device type string
// ALIAS: user-friendly device name
// Syncroton (kolab_sync_backend_device) uses
// ID: internal identifier in syncroton database
// TYPE: device type string
// ALIAS: user-friendly device name
$metadata = $this->root_meta;
$metadata['DEVICE'][$id] = $device;
$metadata = array(self::ASYNC_KEY => $this->serialize_metadata($metadata));
$result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata);
if ($result) {
// Update local cache
$this->root_meta['DEVICE'][$id] = $device;
// Subscribe to default folders
- $foldertypes = $this->storage->get_metadata('*', array(kolab_storage::CTYPE_KEY, kolab_storage::CTYPE_KEY_PRIVATE));
- $types = array(
- 'mail.drafts',
- 'mail.wastebasket',
- 'mail.sentitems',
- 'mail.outbox',
- 'event.default',
- 'contact.default',
- 'task.default',
- 'event',
- 'contact',
- 'task'
- );
-
- $foldertypes = array_map(array('kolab_storage', 'folder_select_metadata'), $foldertypes);
- $foldertypes = array_intersect($foldertypes, $types);
-
- // get default folders
- foreach ($foldertypes as $folder => $type) {
- // only personal folders
- if ($this->storage->folder_namespace($folder) == 'personal') {
- $this->folder_set($folder, $id, 1);
+ $foldertypes = kolab_storage::folders_typedata();
+
+ if (!empty($foldertypes)) {
+ $types = array(
+ 'mail.drafts',
+ 'mail.wastebasket',
+ 'mail.sentitems',
+ 'mail.outbox',
+ 'event.default',
+ 'contact.default',
+ 'task.default',
+ 'event',
+ 'contact',
+ 'task'
+ );
+
+ $foldertypes = array_intersect($foldertypes, $types);
+
+ // get default folders
+ foreach ($foldertypes as $folder => $type) {
+ // only personal folders
+ if ($this->storage->folder_namespace($folder) == 'personal') {
+ $this->folder_set($folder, $id, 1);
+ }
}
}
+
// INBOX always exists
$this->folder_set('INBOX', $id, 1);
}
return $result;
}
/**
* Device update.
*
* @param array $device Device data
* @param string $id Device ID
*
* @return bool True on success, False on failure
*/
public function device_update($device, $id)
{
$devices_list = $this->devices_list();
$old_device = $devices_list[$id];
if (!$old_device) {
return false;
}
// Do nothing if nothing is changed
if (!self::data_array_diff($old_device, $device)) {
return true;
}
$device = array_merge($old_device, $device);
$metadata = $this->root_meta;
$metadata['DEVICE'][$id] = $device;
$metadata = array(self::ASYNC_KEY => $this->serialize_metadata($metadata));
$result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata);
if ($result) {
// Update local cache
$this->root_meta['DEVICE'][$id] = $device;
}
return $result;
}
/**
* Device delete.
*
* @param string $id Device ID
*
* @return bool True on success, False on failure
*/
public function device_delete($id)
{
$device = $this->device_get($id);
if (!$device) {
return false;
}
unset($this->root_meta['DEVICE'][$id], $this->root_meta['FOLDER'][$id]);
if (empty($this->root_meta['DEVICE'])) {
unset($this->root_meta['DEVICE']);
}
if (empty($this->root_meta['FOLDER'])) {
unset($this->root_meta['FOLDER']);
}
$metadata = $this->serialize_metadata($this->root_meta);
$metadata = array(self::ASYNC_KEY => $metadata);
// update meta data
$result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata);
if ($result) {
// remove device annotation for every folder
foreach ($this->folder_meta() as $folder => $meta) {
// skip root folder (already handled above)
if ($folder == self::ROOT_MAILBOX)
continue;
if (!empty($meta['FOLDER']) && isset($meta['FOLDER'][$id])) {
unset($meta['FOLDER'][$id]);
if (empty($meta['FOLDER'])) {
unset($this->folder_meta[$folder]['FOLDER']);
unset($meta['FOLDER']);
}
if (empty($meta)) {
unset($this->folder_meta[$folder]);
$meta = null;
}
$metadata = array(self::ASYNC_KEY => $this->serialize_metadata($meta));
$res = $this->storage->set_metadata($folder, $metadata);
if ($res && $meta) {
$this->folder_meta[$folder] = $meta;
}
}
}
}
return $result;
}
/**
* Helper method to decode saved IMAP metadata
*/
private function unserialize_metadata($str)
{
if (!empty($str)) {
// Support old Z-Push annotation format
if ($str[0] != '{') {
$str = base64_decode($str);
}
$data = json_decode($str, true);
return $data;
}
return null;
}
/**
* Helper method to encode IMAP metadata for saving
*/
private function serialize_metadata($data)
{
if (!empty($data) && is_array($data)) {
$data = json_encode($data);
// $data = base64_encode($data);
return $data;
}
return null;
}
/**
* Returns Kolab folder type for specified ActiveSync type ID
*/
public static function type_activesync2kolab($type)
{
if (!empty(self::$types[$type])) {
return self::$types[$type];
}
return '';
}
/**
* Returns ActiveSync folder type for specified Kolab type
*/
public static function type_kolab2activesync($type)
{
if ($key = array_search($type, self::$types)) {
return $key;
}
return key(self::$types);
}
/**
* Returns Kolab folder type for specified ActiveSync class name
*/
public static function class_activesync2kolab($class)
{
if (!empty(self::$classes[$class])) {
return self::$classes[$class];
}
return '';
}
private function folder_data($folder, $type)
{
// Folder name parameters
$delim = $this->storage->get_hierarchy_delimiter();
$items = explode($delim, $folder);
$name = array_pop($items);
// Folder UID
$folder_id = $this->folder_id($folder, $type);
// Folder type
$type = self::type_kolab2activesync($type);
// fix type, if there's no type annotation it's detected as UNKNOWN
// we'll use 'mail' (12) or 'mail.inbox' (2)
if ($type == 1) {
$type = $folder == 'INBOX' ? 2 : 12;
}
// Syncroton folder data array
return array(
'serverId' => $folder_id,
'parentId' => count($items) ? self::folder_id(implode($delim, $items)) : 0,
'displayName' => rcube_charset::convert($name, 'UTF7-IMAP', kolab_sync::CHARSET),
'type' => $type,
);
}
/**
* Builds folder ID based on folder name
*/
public function folder_id($name, $type = null)
{
// ActiveSync expects folder identifiers to be max.64 characters
// So we can't use just folder name
if ($name === '' || !is_string($name)) {
return null;
}
if (isset($this->folder_uids[$name])) {
return $this->folder_uids[$name];
}
/*
@TODO: For now uniqueid annotation doesn't work, we will create UIDs by ourselves.
There's one inconvenience of this solution: folder name/type change
would be handled in ActiveSync as delete + create.
// get folders unique identifier
$folderdata = $this->storage->get_metadata($name, self::UID_KEY);
if ($folderdata && !empty($folderdata[$name])) {
$uid = $folderdata[$name][self::UID_KEY];
return $this->folder_uids[$name] = $uid;
}
*/
// Add type to folder UID hash, so type change can be detected by Syncroton
$uid = $name . '!!' . ($type !== null ? $type : kolab_storage::folder_type($name));
$uid = md5($uid);
return $this->folder_uids[$name] = $uid;
}
/**
* Returns IMAP folder name
*
* @param string $id Folder identifier
* @param string $deviceid Device dentifier
*
* @return string Folder name (UTF7-IMAP)
*/
public function folder_id2name($id, $deviceid)
{
// check in cache first
if (!empty($this->folder_uids)) {
if (($name = array_search($id, $this->folder_uids)) !== false) {
return $name;
}
}
/*
@TODO: see folder_id()
// get folders unique identifier
$folderdata = $this->storage->get_metadata('*', self::UID_KEY);
foreach ((array)$folderdata as $folder => $data) {
if (!empty($data[self::UID_KEY])) {
$uid = $data[self::UID_KEY];
$this->folder_uids[$folder] = $uid;
if ($uid == $id) {
$name = $folder;
}
}
}
*/
// get all folders of specified type
$folderdata = $this->folder_meta();
if (!is_array($folderdata)) {
return null;
}
// check if folders are "subscribed" for activesync
foreach ($folderdata as $folder => $meta) {
if (empty($meta['FOLDER']) || empty($meta['FOLDER'][$deviceid])
|| empty($meta['FOLDER'][$deviceid]['S'])
) {
continue;
}
$uid = self::folder_id($folder);
$this->folder_uids[$folder] = $uid;
if ($uid == $id) {
$name = $folder;
}
}
return $name;
}
/**
*/
public function modseq_set($deviceid, $folderid, $synctime, $data)
{
$synctime = $synctime->format('Ymdhis');
$rcube = rcube::get_instance();
$db = $rcube->get_dbh();
$this->modseq[$deviceid][$folderid][$synctime] = $data;
$data = json_encode($data);
$db->query("UPDATE syncroton_modseq"
." SET data = ?"
." WHERE device_id = ? AND folder_id = ? AND synctime = ?",
$data, $deviceid, $folderid, $synctime);
if (!$db->affected_rows()) {
$db->query("INSERT INTO syncroton_modseq (device_id, folder_id, synctime, data)"
." VALUES (?, ?, ?, ?)",
$deviceid, $folderid, $synctime, $data);
}
}
public function modseq_get($deviceid, $folderid, $synctime)
{
$synctime = $synctime->format('Ymdhis');
if (!isset($this->modseq[$deviceid]) || !isset($this->modseq[$deviceid][$folderid])
|| !isset($this->modseq[$deviceid][$synctime])
) {
$rcube = rcube::get_instance();
$db = $rcube->get_dbh();
$db->limitquery("SELECT data, synctime FROM syncroton_modseq"
." WHERE device_id = ? AND folder_id = ? AND synctime <= ?"
." ORDER BY synctime DESC",
0, 2, $deviceid, $folderid, $synctime);
if ($row = $db->fetch_assoc()) {
$current = $row['synctime'];
$this->modseq[$deviceid][$folderid][$synctime] = json_decode($row['data']);
// Cleanup: remove old records (older than 12 hours from the last one)
if (($row = $db->fetch_assoc()) && $row['synctime'] < $current - 86400) {
$db->query("DELETE FROM syncroton_modseq"
." WHERE device_id = ? AND folder_id = ? AND synctime < ?",
$deviceid, $folderid, $current - 86400);
}
}
}
return @$this->modseq[$deviceid][$folderid][$synctime];
}
/**
* Compares two arrays
*
* @param array $array1
* @param array $array2
*
* @return bool True if arrays differs, False otherwise
*/
private static function data_array_diff($array1, $array2)
{
if (!is_array($array1) || !is_array($array2)) {
return $array1 != $array2;
}
if (count($array1) != count($array2)) {
return true;
}
foreach ($array1 as $key => $val) {
if (!array_key_exists($key, $array2)) {
return true;
}
if ($val !== $array2[$key]) {
return true;
}
}
return false;
}
}
diff --git a/lib/plugins/kolab_folders/kolab_folders.php b/lib/plugins/kolab_folders/kolab_folders.php
index a45c108..5b1d1d3 100644
--- a/lib/plugins/kolab_folders/kolab_folders.php
+++ b/lib/plugins/kolab_folders/kolab_folders.php
@@ -1,486 +1,561 @@
<?php
/**
* Type-aware folder management/listing for Kolab
*
* @version @package_version@
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* Copyright (C) 2011, 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/>.
*/
class kolab_folders extends rcube_plugin
{
public $task = '?(?!login).*';
public $types = array('mail', 'event', 'journal', 'task', 'note', 'contact', 'configuration', 'file', 'freebusy');
public $mail_types = array('inbox', 'drafts', 'sentitems', 'outbox', 'wastebasket', 'junkemail');
private $rc;
private static $instance;
/**
* Plugin initialization.
*/
function init()
{
self::$instance = $this;
$this->rc = rcube::get_instance();
// load required plugin
$this->require_plugin('libkolab');
// Folder listing hooks
$this->add_hook('storage_folders', array($this, 'mailboxes_list'));
// Folder manager hooks
$this->add_hook('folder_form', array($this, 'folder_form'));
$this->add_hook('folder_update', array($this, 'folder_save'));
$this->add_hook('folder_create', array($this, 'folder_save'));
$this->add_hook('folder_delete', array($this, 'folder_save'));
$this->add_hook('folder_rename', array($this, 'folder_save'));
$this->add_hook('folders_list', array($this, 'folders_list'));
+
+ // Special folders setting
+ $this->add_hook('preferences_save', array($this, 'prefs_save'));
}
/**
* Handler for mailboxes_list hook. Enables type-aware lists filtering.
*/
function mailboxes_list($args)
{
// infinite loop prevention
if ($this->is_processing) {
return $args;
}
if (!$this->metadata_support()) {
return $args;
}
$this->is_processing = true;
// get folders
$folders = kolab_storage::list_folders($args['root'], $args['name'], $args['filter'], $args['mode'] == 'LSUB', $folderdata);
$this->is_processing = false;
if (!is_array($folders)) {
return $args;
}
// Create default folders
if ($args['root'] == '' && $args['name'] = '*') {
$this->create_default_folders($folders, $args['filter'], $folderdata);
}
$args['folders'] = $folders;
return $args;
}
/**
* Handler for folders_list hook. Add css classes to folder rows.
*/
function folders_list($args)
{
if (!$this->metadata_support()) {
return $args;
}
- $table = $args['table'];
- $storage = $this->rc->get_storage();
-
// get folders types
- $folderdata = $storage->get_metadata('*', kolab_storage::CTYPE_KEY);
+ $folderdata = kolab_storage::folders_typedata();
if (!is_array($folderdata)) {
return $args;
}
+ $table = $args['table'];
+
// Add type-based style for table rows
// See kolab_folders::folder_class_name()
for ($i=1, $cnt=$table->size(); $i<=$cnt; $i++) {
$attrib = $table->get_row_attribs($i);
$folder = $attrib['foldername']; // UTF7-IMAP
- $type = !empty($folderdata[$folder]) ? $folderdata[$folder][kolab_storage::CTYPE_KEY] : null;
+ $type = $folderdata[$folder];
- if (!$type)
+ if (!$type) {
$type = 'mail';
+ }
$class_name = self::folder_class_name($type);
$attrib['class'] = trim($attrib['class'] . ' ' . $class_name);
$table->set_row_attribs($attrib, $i);
}
return $args;
}
/**
* Handler for folder info/edit form (folder_form hook).
* Adds folder type selector.
*/
function folder_form($args)
{
if (!$this->metadata_support()) {
return $args;
}
// load translations
$this->add_texts('localization/', false);
// INBOX folder is of type mail.inbox and this cannot be changed
if ($args['name'] == 'INBOX') {
$args['form']['props']['fieldsets']['settings']['content']['foldertype'] = array(
'label' => $this->gettext('folderctype'),
'value' => sprintf('%s (%s)', $this->gettext('foldertypemail'), $this->gettext('inbox')),
);
return $args;
}
if ($args['options']['is_root']) {
return $args;
}
$mbox = strlen($args['name']) ? $args['name'] : $args['parent_name'];
if (isset($_POST['_ctype'])) {
$new_ctype = trim(get_input_value('_ctype', RCUBE_INPUT_POST));
$new_subtype = trim(get_input_value('_subtype', RCUBE_INPUT_POST));
}
// Get type of the folder or the parent
if (strlen($mbox)) {
list($ctype, $subtype) = $this->get_folder_type($mbox);
if (strlen($args['parent_name']) && $subtype == 'default')
$subtype = ''; // there can be only one
}
if (!$ctype) {
$ctype = 'mail';
}
$storage = $this->rc->get_storage();
// Don't allow changing type of shared folder, according to ACL
if (strlen($mbox)) {
$options = $storage->folder_info($mbox);
- if ($options['namespace'] != 'personal' && !in_array('a', $options['rights'])) {
+ if ($options['namespace'] != 'personal' && !in_array('a', (array)$options['rights'])) {
if (in_array($ctype, $this->types)) {
$value = $this->gettext('foldertype'.$ctype);
}
else {
$value = $ctype;
}
if ($subtype) {
$value .= ' ('. ($subtype == 'default' ? $this->gettext('default') : $subtype) .')';
}
$args['form']['props']['fieldsets']['settings']['content']['foldertype'] = array(
'label' => $this->gettext('folderctype'),
'value' => $value,
);
return $args;
}
}
// Add javascript script to the client
$this->include_script('kolab_folders.js');
// build type SELECT fields
$type_select = new html_select(array('name' => '_ctype', 'id' => '_ctype'));
$sub_select = new html_select(array('name' => '_subtype', 'id' => '_subtype'));
foreach ($this->types as $type) {
$type_select->add($this->gettext('foldertype'.$type), $type);
}
// add non-supported type
if (!in_array($ctype, $this->types)) {
$type_select->add($ctype, $ctype);
}
$sub_select->add('', '');
$sub_select->add($this->gettext('default'), 'default');
foreach ($this->mail_types as $type) {
$sub_select->add($this->gettext($type), $type);
}
$args['form']['props']['fieldsets']['settings']['content']['foldertype'] = array(
'label' => $this->gettext('folderctype'),
'value' => $type_select->show(isset($new_ctype) ? $new_ctype : $ctype)
. $sub_select->show(isset($new_subtype) ? $new_subtype : $subtype),
);
return $args;
}
/**
* Handler for folder update/create action (folder_update/folder_create hook).
*/
function folder_save($args)
{
// Folder actions from folders list
if (empty($args['record'])) {
return $args;
}
// Folder create/update with form
$ctype = trim(get_input_value('_ctype', RCUBE_INPUT_POST));
$subtype = trim(get_input_value('_subtype', RCUBE_INPUT_POST));
$mbox = $args['record']['name'];
$old_mbox = $args['record']['oldname'];
$subscribe = $args['record']['subscribe'];
if (empty($ctype)) {
return $args;
}
// load translations
$this->add_texts('localization/', false);
// Skip folder creation/rename in core
// @TODO: Maybe we should provide folder_create_after and folder_update_after hooks?
// Using create_mailbox/rename_mailbox here looks bad
$args['abort'] = true;
// There can be only one default folder of specified type
if ($subtype == 'default') {
$default = $this->get_default_folder($ctype);
if ($default !== null && $old_mbox != $default) {
$args['result'] = false;
$args['message'] = $this->gettext('defaultfolderexists');
return $args;
}
}
// Subtype sanity-checks
else if ($subtype && ($ctype != 'mail' || !in_array($subtype, $this->mail_types))) {
$subtype = '';
}
$ctype .= $subtype ? '.'.$subtype : '';
$storage = $this->rc->get_storage();
// Create folder
if (!strlen($old_mbox)) {
// By default don't subscribe to non-mail folders
if ($subscribe)
$subscribe = (bool) preg_match('/^mail/', $ctype);
$result = $storage->create_folder($mbox, $subscribe);
// Set folder type
if ($result) {
$this->set_folder_type($mbox, $ctype);
}
}
// Rename folder
else {
if ($old_mbox != $mbox) {
$result = $storage->rename_folder($old_mbox, $mbox);
}
else {
$result = true;
}
if ($result) {
list($oldtype, $oldsubtype) = $this->get_folder_type($mbox);
$oldtype .= $oldsubtype ? '.'.$oldsubtype : '';
if ($ctype != $oldtype) {
$this->set_folder_type($mbox, $ctype);
}
}
}
$args['record']['class'] = self::folder_class_name($ctype);
$args['record']['subscribe'] = $subscribe;
$args['result'] = $result;
return $args;
}
+ /**
+ * Handler for user preferences save (preferences_save hook)
+ *
+ * @param array $args Hash array with hook parameters
+ *
+ * @return array Hash array with modified hook parameters
+ */
+ public function prefs_save($args)
+ {
+ if ($args['section'] != 'folders') {
+ return $args;
+ }
+
+ // Load configuration
+ $this->load_config();
+
+ // Check that configuration is not disabled
+ $dont_override = (array) $this->rc->config->get('dont_override', array());
+
+ // special handling for 'default_folders'
+ if (in_array('default_folders', $dont_override)) {
+ return $args;
+ }
+
+ // map config option name to kolab folder type annotation
+ $opts = array(
+ 'drafts_mbox' => 'mail.drafts',
+ 'sent_mbox' => 'mail.sentitems',
+ 'junk_mbox' => 'mail.junkemail',
+ 'trash_mbox' => 'mail.wastebasket',
+ );
+
+ // check if any of special folders has been changed
+ foreach ($opts as $opt_name => $type) {
+ $new = $args['prefs'][$opt_name];
+ $old = $this->rc->config->get($opt_name);
+ if ($new === $old) {
+ unset($opts[$opt_name]);
+ }
+ }
+
+ if (empty($opts)) {
+ return $args;
+ }
+
+ $folderdata = kolab_storage::folders_typedata();
+
+ if (!is_array($folderdata)) {
+ return $args;
+ }
+
+ foreach ($opts as $opt_name => $type) {
+ $foldername = $args['prefs'][$opt_name];
+ if (strlen($foldername)) {
+
+ // get all folders of specified type
+ $folders = array_intersect($folderdata, array($type));
+
+ // folder already annotated with specified type
+ if (!empty($folders[$foldername])) {
+ continue;
+ }
+
+ // set type to the new folder
+ $this->set_folder_type($foldername, $type);
+
+ // unset old folder(s) type annotation
+ list($maintype, $subtype) = explode('.', $type);
+ foreach (array_keys($folders) as $folder) {
+ $this->set_folder_type($folder, $maintype);
+ }
+ }
+ }
+
+ return $args;
+ }
+
/**
* Checks if IMAP server supports any of METADATA, ANNOTATEMORE, ANNOTATEMORE2
*
* @return boolean
*/
function metadata_support()
{
$storage = $this->rc->get_storage();
return $storage->get_capability('METADATA') ||
$storage->get_capability('ANNOTATEMORE') ||
$storage->get_capability('ANNOTATEMORE2');
}
/**
* Checks if IMAP server supports any of METADATA, ANNOTATEMORE, ANNOTATEMORE2
*
* @param string $folder Folder name
*
* @return array Folder content-type
*/
function get_folder_type($folder)
{
return explode('.', (string)kolab_storage::folder_type($folder));
}
/**
* Sets folder content-type.
*
* @param string $folder Folder name
* @param string $type Content type
*
* @return boolean True on success
*/
function set_folder_type($folder, $type = 'mail')
{
return kolab_storage::set_folder_type($folder, $type);
}
/**
* Returns the name of default folder
*
* @param string $type Folder type
*
* @return string Folder name
*/
function get_default_folder($type)
{
- $storage = $this->rc->get_storage();
- $folderdata = $storage->get_metadata('*', array(kolab_storage::CTYPE_KEY_PRIVATE, kolab_storage::CTYPE_KEY));
+ $folderdata = kolab_storage::folders_typedata();
if (!is_array($folderdata)) {
return null;
}
// get all folders of specified type
- $folderdata = array_map(array('kolab_storage', 'folder_select_metadata'), $folderdata);
$folderdata = array_intersect($folderdata, array($type.'.default'));
return key($folderdata);
}
/**
* Returns CSS class name for specified folder type
*
* @param string $type Folder type
*
* @return string Class name
*/
static function folder_class_name($type)
{
list($ctype, $subtype) = explode('.', $type);
$class[] = 'type-' . ($ctype ? $ctype : 'mail');
if ($subtype)
$class[] = 'subtype-' . $subtype;
return implode(' ', $class);
}
/**
* Creates default folders if they doesn't exist
*/
private function create_default_folders(&$folders, $filter, $folderdata = null)
{
$storage = $this->rc->get_storage();
$namespace = $storage->get_namespace();
$defaults = array();
- $need_update = false;
$prefix = '';
// Find personal namespace prefix
if (is_array($namespace['personal']) && count($namespace['personal']) == 1) {
$prefix = $namespace['personal'][0][0];
}
$this->load_config();
// get configured defaults
foreach ($this->types as $type) {
$subtypes = $type == 'mail' ? $this->mail_types : array('default');
foreach ($subtypes as $subtype) {
$opt_name = 'kolab_folders_' . $type . '_' . $subtype;
if ($folder = $this->rc->config->get($opt_name)) {
// convert configuration value to UTF7-IMAP charset
$folder = rcube_charset::convert($folder, RCUBE_CHARSET, 'UTF7-IMAP');
// and namespace prefix if needed
if ($prefix && strpos($folder, $prefix) === false && $folder != 'INBOX') {
$folder = $prefix . $folder;
}
$defaults[$type . '.' . $subtype] = $folder;
}
}
}
if (empty($defaults)) {
return;
}
- if (!is_array($folderdata)) {
- $folderdata = $storage->get_metadata('*', array(kolab_storage::CTYPE_KEY_PRIVATE, kolab_storage::CTYPE_KEY));
-
- if (!is_array($folderdata)) {
- return;
- }
+ if ($folderdata === null) {
+ $folderdata = kolab_storage::folders_typedata();
+ }
- $folderdata = array_map(array('kolab_storage', 'folder_select_metadata'), $folderdata);
+ if (!is_array($folderdata)) {
+ return;
}
// find default folders
foreach ($defaults as $type => $foldername) {
// get all folders of specified type
$_folders = array_intersect($folderdata, array($type));
// default folder found
if (!empty($_folders)) {
continue;
}
list($type1, $type2) = explode('.', $type);
$exists = !empty($folderdata[$foldername]) || $foldername == 'INBOX';
// create folder
if (!$exists && !$storage->folder_exists($foldername)) {
$storage->create_folder($foldername, $type1 == 'mail');
}
// set type
$result = $this->set_folder_type($foldername, $type);
// add new folder to the result
if ($result && (!$filter || $filter == $type1)) {
$folders[] = $foldername;
}
}
}
/**
* Static getter for default folder of the given type
*
* @param string $type Folder type
* @return string Folder name
*/
public static function default_folder($type)
{
return self::$instance->get_default_folder($type);
}
}
diff --git a/lib/plugins/libkolab/lib/Horde_Date.php b/lib/plugins/libkolab/lib/Horde_Date.php
deleted file mode 100644
index 9197f84..0000000
--- a/lib/plugins/libkolab/lib/Horde_Date.php
+++ /dev/null
@@ -1,1304 +0,0 @@
-<?php
-
-/**
- * This is a concatenated copy of the following files:
- * Horde/Date/Utils.php, Horde/Date/Recurrence.php
- * Pull the latest version of these files from the PEAR channel of the Horde
- * project at http://pear.horde.org by installing the Horde_Date package.
- */
-
-
-/**
- * Horde Date wrapper/logic class, including some calculation
- * functions.
- *
- * @category Horde
- * @package Date
- *
- * @TODO in format():
- * http://php.net/intldateformatter
- *
- * @TODO on timezones:
- * http://trac.agavi.org/ticket/1008
- * http://trac.agavi.org/changeset/3659
- *
- * @TODO on switching to PHP::DateTime:
- * The only thing ever stored in the database *IS* Unix timestamps. Doing
- * anything other than that is unmanageable, yet some frameworks use 'server
- * based' times in their systems, simply because they do not bother with
- * daylight saving and only 'serve' one timezone!
- *
- * The second you have to manage 'real' time across timezones then daylight
- * saving becomes essential, BUT only on the display side! Since the browser
- * only provides a time offset, this is useless and to be honest should simply
- * be ignored ( until it is upgraded to provide the correct information ;)
- * ). So we need a 'display' function that takes a simple numeric epoch, and a
- * separate timezone id into which the epoch is to be 'converted'. My W3C
- * mapping works simply because ADOdb then converts that to it's own simple
- * offset abbreviation - in my case GMT or BST. As long as DateTime passes the
- * full 64 bit number the date range from 100AD is also preserved ( and
- * further back if 2 digit years are disabled ). If I want to display the
- * 'real' timezone with this 'time' then I just add it in place of ADOdb's
- * 'timezone'. I am tempted to simply adjust the ADOdb class to take a
- * timezone in place of the simple GMT switch it currently uses.
- *
- * The return path is just the reverse and simply needs to take the client
- * display offset off prior to storage of the UTC epoch. SO we use
- * DateTimeZone to get an offset value for the clients timezone and simply add
- * or subtract this from a timezone agnostic display on the client end when
- * entering new times.
- *
- *
- * It's not really feasible to store dates in specific timezone, as most
- * national/local timezones support DST - and that is a pain to support, as
- * eg. sorting breaks when some timestamps get repeated. That's why it's
- * usually better to store datetimes as either UTC datetime or plain unix
- * timestamp. I usually go with the former - using database datetime type.
- */
-
-/**
- * @category Horde
- * @package Date
- */
-class Horde_Date
-{
- const DATE_SUNDAY = 0;
- const DATE_MONDAY = 1;
- const DATE_TUESDAY = 2;
- const DATE_WEDNESDAY = 3;
- const DATE_THURSDAY = 4;
- const DATE_FRIDAY = 5;
- const DATE_SATURDAY = 6;
-
- const MASK_SUNDAY = 1;
- const MASK_MONDAY = 2;
- const MASK_TUESDAY = 4;
- const MASK_WEDNESDAY = 8;
- const MASK_THURSDAY = 16;
- const MASK_FRIDAY = 32;
- const MASK_SATURDAY = 64;
- const MASK_WEEKDAYS = 62;
- const MASK_WEEKEND = 65;
- const MASK_ALLDAYS = 127;
-
- const MASK_SECOND = 1;
- const MASK_MINUTE = 2;
- const MASK_HOUR = 4;
- const MASK_DAY = 8;
- const MASK_MONTH = 16;
- const MASK_YEAR = 32;
- const MASK_ALLPARTS = 63;
-
- const DATE_DEFAULT = 'Y-m-d H:i:s';
- const DATE_JSON = 'Y-m-d\TH:i:s';
-
- /**
- * Year
- *
- * @var integer
- */
- protected $_year;
-
- /**
- * Month
- *
- * @var integer
- */
- protected $_month;
-
- /**
- * Day
- *
- * @var integer
- */
- protected $_mday;
-
- /**
- * Hour
- *
- * @var integer
- */
- protected $_hour = 0;
-
- /**
- * Minute
- *
- * @var integer
- */
- protected $_min = 0;
-
- /**
- * Second
- *
- * @var integer
- */
- protected $_sec = 0;
-
- /**
- * String representation of the date's timezone.
- *
- * @var string
- */
- protected $_timezone;
-
- /**
- * Default format for __toString()
- *
- * @var string
- */
- protected $_defaultFormat = self::DATE_DEFAULT;
-
- /**
- * Default specs that are always supported.
- * @var string
- */
- protected static $_defaultSpecs = '%CdDeHImMnRStTyY';
-
- /**
- * Internally supported strftime() specifiers.
- * @var string
- */
- protected static $_supportedSpecs = '';
-
- /**
- * Map of required correction masks.
- *
- * @see __set()
- *
- * @var array
- */
- protected static $_corrections = array(
- 'year' => self::MASK_YEAR,
- 'month' => self::MASK_MONTH,
- 'mday' => self::MASK_DAY,
- 'hour' => self::MASK_HOUR,
- 'min' => self::MASK_MINUTE,
- 'sec' => self::MASK_SECOND,
- );
-
- protected $_formatCache = array();
-
- /**
- * Builds a new date object. If $date contains date parts, use them to
- * initialize the object.
- *
- * Recognized formats:
- * - arrays with keys 'year', 'month', 'mday', 'day'
- * 'hour', 'min', 'minute', 'sec'
- * - objects with properties 'year', 'month', 'mday', 'hour', 'min', 'sec'
- * - yyyy-mm-dd hh:mm:ss
- * - yyyymmddhhmmss
- * - yyyymmddThhmmssZ
- * - yyyymmdd (might conflict with unix timestamps between 31 Oct 1966 and
- * 03 Mar 1973)
- * - unix timestamps
- * - anything parsed by strtotime()/DateTime.
- *
- * @throws Horde_Date_Exception
- */
- public function __construct($date = null, $timezone = null)
- {
- if (!self::$_supportedSpecs) {
- self::$_supportedSpecs = self::$_defaultSpecs;
- if (function_exists('nl_langinfo')) {
- self::$_supportedSpecs .= 'bBpxX';
- }
- }
-
- if (func_num_args() > 2) {
- // Handle args in order: year month day hour min sec tz
- $this->_initializeFromArgs(func_get_args());
- return;
- }
-
- $this->_initializeTimezone($timezone);
-
- if (is_null($date)) {
- return;
- }
-
- if (is_string($date)) {
- $date = trim($date, '"');
- }
-
- if (is_object($date)) {
- $this->_initializeFromObject($date);
- } elseif (is_array($date)) {
- $this->_initializeFromArray($date);
- } elseif (preg_match('/^(\d{4})-?(\d{2})-?(\d{2})T? ?(\d{2}):?(\d{2}):?(\d{2})(?:\.\d+)?(Z?)$/', $date, $parts)) {
- $this->_year = (int)$parts[1];
- $this->_month = (int)$parts[2];
- $this->_mday = (int)$parts[3];
- $this->_hour = (int)$parts[4];
- $this->_min = (int)$parts[5];
- $this->_sec = (int)$parts[6];
- if ($parts[7]) {
- $this->_initializeTimezone('UTC');
- }
- } elseif (preg_match('/^(\d{4})-?(\d{2})-?(\d{2})$/', $date, $parts) &&
- $parts[2] > 0 && $parts[2] <= 12 &&
- $parts[3] > 0 && $parts[3] <= 31) {
- $this->_year = (int)$parts[1];
- $this->_month = (int)$parts[2];
- $this->_mday = (int)$parts[3];
- $this->_hour = $this->_min = $this->_sec = 0;
- } elseif ((string)(int)$date == $date) {
- // Try as a timestamp.
- $parts = @getdate($date);
- if ($parts) {
- $this->_year = $parts['year'];
- $this->_month = $parts['mon'];
- $this->_mday = $parts['mday'];
- $this->_hour = $parts['hours'];
- $this->_min = $parts['minutes'];
- $this->_sec = $parts['seconds'];
- }
- } else {
- // Use date_create() so we can catch errors with PHP 5.2. Use
- // "new DateTime() once we require 5.3.
- $parsed = date_create($date);
- if (!$parsed) {
- throw new Horde_Date_Exception(sprintf(Horde_Date_Translation::t("Failed to parse time string (%s)"), $date));
- }
- $parsed->setTimezone(new DateTimeZone(date_default_timezone_get()));
- $this->_year = (int)$parsed->format('Y');
- $this->_month = (int)$parsed->format('m');
- $this->_mday = (int)$parsed->format('d');
- $this->_hour = (int)$parsed->format('H');
- $this->_min = (int)$parsed->format('i');
- $this->_sec = (int)$parsed->format('s');
- $this->_initializeTimezone(date_default_timezone_get());
- }
- }
-
- /**
- * Returns a simple string representation of the date object
- *
- * @return string This object converted to a string.
- */
- public function __toString()
- {
- try {
- return $this->format($this->_defaultFormat);
- } catch (Exception $e) {
- return '';
- }
- }
-
- /**
- * Returns a DateTime object representing this object.
- *
- * @return DateTime
- */
- public function toDateTime()
- {
- $date = new DateTime(null, new DateTimeZone($this->_timezone));
- $date->setDate($this->_year, $this->_month, $this->_mday);
- $date->setTime($this->_hour, $this->_min, $this->_sec);
- return $date;
- }
-
- /**
- * Converts a date in the proleptic Gregorian calendar to the no of days
- * since 24th November, 4714 B.C.
- *
- * Returns the no of days since Monday, 24th November, 4714 B.C. in the
- * proleptic Gregorian calendar (which is 24th November, -4713 using
- * 'Astronomical' year numbering, and 1st January, 4713 B.C. in the
- * proleptic Julian calendar). This is also the first day of the 'Julian
- * Period' proposed by Joseph Scaliger in 1583, and the number of days
- * since this date is known as the 'Julian Day'. (It is not directly
- * to do with the Julian calendar, although this is where the name
- * is derived from.)
- *
- * The algorithm is valid for all years (positive and negative), and
- * also for years preceding 4714 B.C.
- *
- * Algorithm is from PEAR::Date_Calc
- *
- * @author Monte Ohrt <monte@ispi.net>
- * @author Pierre-Alain Joye <pajoye@php.net>
- * @author Daniel Convissor <danielc@php.net>
- * @author C.A. Woodcock <c01234@netcomuk.co.uk>
- *
- * @return integer The number of days since 24th November, 4714 B.C.
- */
- public function toDays()
- {
- if (function_exists('GregorianToJD')) {
- return gregoriantojd($this->_month, $this->_mday, $this->_year);
- }
-
- $day = $this->_mday;
- $month = $this->_month;
- $year = $this->_year;
-
- if ($month > 2) {
- // March = 0, April = 1, ..., December = 9,
- // January = 10, February = 11
- $month -= 3;
- } else {
- $month += 9;
- --$year;
- }
-
- $hb_negativeyear = $year < 0;
- $century = intval($year / 100);
- $year = $year % 100;
-
- if ($hb_negativeyear) {
- // Subtract 1 because year 0 is a leap year;
- // And N.B. that we must treat the leap years as occurring
- // one year earlier than they do, because for the purposes
- // of calculation, the year starts on 1st March:
- //
- return intval((14609700 * $century + ($year == 0 ? 1 : 0)) / 400) +
- intval((1461 * $year + 1) / 4) +
- intval((153 * $month + 2) / 5) +
- $day + 1721118;
- } else {
- return intval(146097 * $century / 4) +
- intval(1461 * $year / 4) +
- intval((153 * $month + 2) / 5) +
- $day + 1721119;
- }
- }
-
- /**
- * Converts number of days since 24th November, 4714 B.C. (in the proleptic
- * Gregorian calendar, which is year -4713 using 'Astronomical' year
- * numbering) to Gregorian calendar date.
- *
- * Returned date belongs to the proleptic Gregorian calendar, using
- * 'Astronomical' year numbering.
- *
- * The algorithm is valid for all years (positive and negative), and
- * also for years preceding 4714 B.C. (i.e. for negative 'Julian Days'),
- * and so the only limitation is platform-dependent (for 32-bit systems
- * the maximum year would be something like about 1,465,190 A.D.).
- *
- * N.B. Monday, 24th November, 4714 B.C. is Julian Day '0'.
- *
- * Algorithm is from PEAR::Date_Calc
- *
- * @author Monte Ohrt <monte@ispi.net>
- * @author Pierre-Alain Joye <pajoye@php.net>
- * @author Daniel Convissor <danielc@php.net>
- * @author C.A. Woodcock <c01234@netcomuk.co.uk>
- *
- * @param int $days the number of days since 24th November, 4714 B.C.
- * @param string $format the string indicating how to format the output
- *
- * @return Horde_Date A Horde_Date object representing the date.
- */
- public static function fromDays($days)
- {
- if (function_exists('JDToGregorian')) {
- list($month, $day, $year) = explode('/', JDToGregorian($days));
- } else {
- $days = intval($days);
-
- $days -= 1721119;
- $century = floor((4 * $days - 1) / 146097);
- $days = floor(4 * $days - 1 - 146097 * $century);
- $day = floor($days / 4);
-
- $year = floor((4 * $day + 3) / 1461);
- $day = floor(4 * $day + 3 - 1461 * $year);
- $day = floor(($day + 4) / 4);
-
- $month = floor((5 * $day - 3) / 153);
- $day = floor(5 * $day - 3 - 153 * $month);
- $day = floor(($day + 5) / 5);
-
- $year = $century * 100 + $year;
- if ($month < 10) {
- $month +=3;
- } else {
- $month -=9;
- ++$year;
- }
- }
-
- return new Horde_Date($year, $month, $day);
- }
-
- /**
- * Getter for the date and time properties.
- *
- * @param string $name One of 'year', 'month', 'mday', 'hour', 'min' or
- * 'sec'.
- *
- * @return integer The property value, or null if not set.
- */
- public function __get($name)
- {
- if ($name == 'day') {
- $name = 'mday';
- }
-
- return $this->{'_' . $name};
- }
-
- /**
- * Setter for the date and time properties.
- *
- * @param string $name One of 'year', 'month', 'mday', 'hour', 'min' or
- * 'sec'.
- * @param integer $value The property value.
- */
- public function __set($name, $value)
- {
- if ($name == 'timezone') {
- $this->_initializeTimezone($value);
- return;
- }
- if ($name == 'day') {
- $name = 'mday';
- }
-
- if ($name != 'year' && $name != 'month' && $name != 'mday' &&
- $name != 'hour' && $name != 'min' && $name != 'sec') {
- throw new InvalidArgumentException('Undefined property ' . $name);
- }
-
- $down = $value < $this->{'_' . $name};
- $this->{'_' . $name} = $value;
- $this->_correct(self::$_corrections[$name], $down);
- $this->_formatCache = array();
- }
-
- /**
- * Returns whether a date or time property exists.
- *
- * @param string $name One of 'year', 'month', 'mday', 'hour', 'min' or
- * 'sec'.
- *
- * @return boolen True if the property exists and is set.
- */
- public function __isset($name)
- {
- if ($name == 'day') {
- $name = 'mday';
- }
- return ($name == 'year' || $name == 'month' || $name == 'mday' ||
- $name == 'hour' || $name == 'min' || $name == 'sec') &&
- isset($this->{'_' . $name});
- }
-
- /**
- * Adds a number of seconds or units to this date, returning a new Date
- * object.
- */
- public function add($factor)
- {
- $d = clone($this);
- if (is_array($factor) || is_object($factor)) {
- foreach ($factor as $property => $value) {
- $d->$property += $value;
- }
- } else {
- $d->sec += $factor;
- }
-
- return $d;
- }
-
- /**
- * Subtracts a number of seconds or units from this date, returning a new
- * Horde_Date object.
- */
- public function sub($factor)
- {
- if (is_array($factor)) {
- foreach ($factor as &$value) {
- $value *= -1;
- }
- } else {
- $factor *= -1;
- }
-
- return $this->add($factor);
- }
-
- /**
- * Converts this object to a different timezone.
- *
- * @param string $timezone The new timezone.
- *
- * @return Horde_Date This object.
- */
- public function setTimezone($timezone)
- {
- $date = $this->toDateTime();
- $date->setTimezone(new DateTimeZone($timezone));
- $this->_timezone = $timezone;
- $this->_year = (int)$date->format('Y');
- $this->_month = (int)$date->format('m');
- $this->_mday = (int)$date->format('d');
- $this->_hour = (int)$date->format('H');
- $this->_min = (int)$date->format('i');
- $this->_sec = (int)$date->format('s');
- $this->_formatCache = array();
- return $this;
- }
-
- /**
- * Sets the default date format used in __toString()
- *
- * @param string $format
- */
- public function setDefaultFormat($format)
- {
- $this->_defaultFormat = $format;
- }
-
- /**
- * Returns the day of the week (0 = Sunday, 6 = Saturday) of this date.
- *
- * @return integer The day of the week.
- */
- public function dayOfWeek()
- {
- if ($this->_month > 2) {
- $month = $this->_month - 2;
- $year = $this->_year;
- } else {
- $month = $this->_month + 10;
- $year = $this->_year - 1;
- }
-
- $day = (floor((13 * $month - 1) / 5) +
- $this->_mday + ($year % 100) +
- floor(($year % 100) / 4) +
- floor(($year / 100) / 4) - 2 *
- floor($year / 100) + 77);
-
- return (int)($day - 7 * floor($day / 7));
- }
-
- /**
- * Returns the day number of the year (1 to 365/366).
- *
- * @return integer The day of the year.
- */
- public function dayOfYear()
- {
- return $this->format('z') + 1;
- }
-
- /**
- * Returns the week of the month.
- *
- * @return integer The week number.
- */
- public function weekOfMonth()
- {
- return ceil($this->_mday / 7);
- }
-
- /**
- * Returns the week of the year, first Monday is first day of first week.
- *
- * @return integer The week number.
- */
- public function weekOfYear()
- {
- return $this->format('W');
- }
-
- /**
- * Returns the number of weeks in the given year (52 or 53).
- *
- * @param integer $year The year to count the number of weeks in.
- *
- * @return integer $numWeeks The number of weeks in $year.
- */
- public static function weeksInYear($year)
- {
- // Find the last Thursday of the year.
- $date = new Horde_Date($year . '-12-31');
- while ($date->dayOfWeek() != self::DATE_THURSDAY) {
- --$date->mday;
- }
- return $date->weekOfYear();
- }
-
- /**
- * Sets the date of this object to the $nth weekday of $weekday.
- *
- * @param integer $weekday The day of the week (0 = Sunday, etc).
- * @param integer $nth The $nth $weekday to set to (defaults to 1).
- */
- public function setNthWeekday($weekday, $nth = 1)
- {
- if ($weekday < self::DATE_SUNDAY || $weekday > self::DATE_SATURDAY) {
- return;
- }
-
- if ($nth < 0) { // last $weekday of month
- $this->_mday = $lastday = Horde_Date_Utils::daysInMonth($this->_month, $this->_year);
- $last = $this->dayOfWeek();
- $this->_mday += ($weekday - $last);
- if ($this->_mday > $lastday)
- $this->_mday -= 7;
- }
- else {
- $this->_mday = 1;
- $first = $this->dayOfWeek();
- if ($weekday < $first) {
- $this->_mday = 8 + $weekday - $first;
- } else {
- $this->_mday = $weekday - $first + 1;
- }
- $diff = 7 * $nth - 7;
- $this->_mday += $diff;
- $this->_correct(self::MASK_DAY, $diff < 0);
- }
- }
-
- /**
- * Is the date currently represented by this object a valid date?
- *
- * @return boolean Validity, counting leap years, etc.
- */
- public function isValid()
- {
- return ($this->_year >= 0 && $this->_year <= 9999);
- }
-
- /**
- * Compares this date to another date object to see which one is
- * greater (later). Assumes that the dates are in the same
- * timezone.
- *
- * @param mixed $other The date to compare to.
- *
- * @return integer == 0 if they are on the same date
- * >= 1 if $this is greater (later)
- * <= -1 if $other is greater (later)
- */
- public function compareDate($other)
- {
- if (!($other instanceof Horde_Date)) {
- $other = new Horde_Date($other);
- }
-
- if ($this->_year != $other->year) {
- return $this->_year - $other->year;
- }
- if ($this->_month != $other->month) {
- return $this->_month - $other->month;
- }
-
- return $this->_mday - $other->mday;
- }
-
- /**
- * Returns whether this date is after the other.
- *
- * @param mixed $other The date to compare to.
- *
- * @return boolean True if this date is after the other.
- */
- public function after($other)
- {
- return $this->compareDate($other) > 0;
- }
-
- /**
- * Returns whether this date is before the other.
- *
- * @param mixed $other The date to compare to.
- *
- * @return boolean True if this date is before the other.
- */
- public function before($other)
- {
- return $this->compareDate($other) < 0;
- }
-
- /**
- * Returns whether this date is the same like the other.
- *
- * @param mixed $other The date to compare to.
- *
- * @return boolean True if this date is the same like the other.
- */
- public function equals($other)
- {
- return $this->compareDate($other) == 0;
- }
-
- /**
- * Compares this to another date object by time, to see which one
- * is greater (later). Assumes that the dates are in the same
- * timezone.
- *
- * @param mixed $other The date to compare to.
- *
- * @return integer == 0 if they are at the same time
- * >= 1 if $this is greater (later)
- * <= -1 if $other is greater (later)
- */
- public function compareTime($other)
- {
- if (!($other instanceof Horde_Date)) {
- $other = new Horde_Date($other);
- }
-
- if ($this->_hour != $other->hour) {
- return $this->_hour - $other->hour;
- }
- if ($this->_min != $other->min) {
- return $this->_min - $other->min;
- }
-
- return $this->_sec - $other->sec;
- }
-
- /**
- * Compares this to another date object, including times, to see
- * which one is greater (later). Assumes that the dates are in the
- * same timezone.
- *
- * @param mixed $other The date to compare to.
- *
- * @return integer == 0 if they are equal
- * >= 1 if $this is greater (later)
- * <= -1 if $other is greater (later)
- */
- public function compareDateTime($other)
- {
- if (!($other instanceof Horde_Date)) {
- $other = new Horde_Date($other);
- }
-
- if ($diff = $this->compareDate($other)) {
- return $diff;
- }
-
- return $this->compareTime($other);
- }
-
- /**
- * Returns number of days between this date and another.
- *
- * @param Horde_Date $other The other day to diff with.
- *
- * @return integer The absolute number of days between the two dates.
- */
- public function diff($other)
- {
- return abs($this->toDays() - $other->toDays());
- }
-
- /**
- * Returns the time offset for local time zone.
- *
- * @param boolean $colon Place a colon between hours and minutes?
- *
- * @return string Timezone offset as a string in the format +HH:MM.
- */
- public function tzOffset($colon = true)
- {
- return $colon ? $this->format('P') : $this->format('O');
- }
-
- /**
- * Returns the unix timestamp representation of this date.
- *
- * @return integer A unix timestamp.
- */
- public function timestamp()
- {
- if ($this->_year >= 1970 && $this->_year < 2038) {
- return mktime($this->_hour, $this->_min, $this->_sec,
- $this->_month, $this->_mday, $this->_year);
- }
- return $this->format('U');
- }
-
- /**
- * Returns the unix timestamp representation of this date, 12:00am.
- *
- * @return integer A unix timestamp.
- */
- public function datestamp()
- {
- if ($this->_year >= 1970 && $this->_year < 2038) {
- return mktime(0, 0, 0, $this->_month, $this->_mday, $this->_year);
- }
- $date = new DateTime($this->format('Y-m-d'));
- return $date->format('U');
- }
-
- /**
- * Formats date and time to be passed around as a short url parameter.
- *
- * @return string Date and time.
- */
- public function dateString()
- {
- return sprintf('%04d%02d%02d', $this->_year, $this->_month, $this->_mday);
- }
-
- /**
- * Formats date and time to the ISO format used by JSON.
- *
- * @return string Date and time.
- */
- public function toJson()
- {
- return $this->format(self::DATE_JSON);
- }
-
- /**
- * Formats date and time to the RFC 2445 iCalendar DATE-TIME format.
- *
- * @param boolean $floating Whether to return a floating date-time
- * (without time zone information).
- *
- * @return string Date and time.
- */
- public function toiCalendar($floating = false)
- {
- if ($floating) {
- return $this->format('Ymd\THis');
- }
- $dateTime = $this->toDateTime();
- $dateTime->setTimezone(new DateTimeZone('UTC'));
- return $dateTime->format('Ymd\THis\Z');
- }
-
- /**
- * Formats time using the specifiers available in date() or in the DateTime
- * class' format() method.
- *
- * To format in languages other than English, use strftime() instead.
- *
- * @param string $format
- *
- * @return string Formatted time.
- */
- public function format($format)
- {
- if (!isset($this->_formatCache[$format])) {
- $this->_formatCache[$format] = $this->toDateTime()->format($format);
- }
- return $this->_formatCache[$format];
- }
-
- /**
- * Formats date and time using strftime() format.
- *
- * @return string strftime() formatted date and time.
- */
- public function strftime($format)
- {
- if (preg_match('/%[^' . self::$_supportedSpecs . ']/', $format)) {
- return strftime($format, $this->timestamp());
- } else {
- return $this->_strftime($format);
- }
- }
-
- /**
- * Formats date and time using a limited set of the strftime() format.
- *
- * @return string strftime() formatted date and time.
- */
- protected function _strftime($format)
- {
- return preg_replace(
- array('/%b/e',
- '/%B/e',
- '/%C/e',
- '/%d/e',
- '/%D/e',
- '/%e/e',
- '/%H/e',
- '/%I/e',
- '/%m/e',
- '/%M/e',
- '/%n/',
- '/%p/e',
- '/%R/e',
- '/%S/e',
- '/%t/',
- '/%T/e',
- '/%x/e',
- '/%X/e',
- '/%y/e',
- '/%Y/',
- '/%%/'),
- array('$this->_strftime(Horde_Nls::getLangInfo(constant(\'ABMON_\' . (int)$this->_month)))',
- '$this->_strftime(Horde_Nls::getLangInfo(constant(\'MON_\' . (int)$this->_month)))',
- '(int)($this->_year / 100)',
- 'sprintf(\'%02d\', $this->_mday)',
- '$this->_strftime(\'%m/%d/%y\')',
- 'sprintf(\'%2d\', $this->_mday)',
- 'sprintf(\'%02d\', $this->_hour)',
- 'sprintf(\'%02d\', $this->_hour == 0 ? 12 : ($this->_hour > 12 ? $this->_hour - 12 : $this->_hour))',
- 'sprintf(\'%02d\', $this->_month)',
- 'sprintf(\'%02d\', $this->_min)',
- "\n",
- '$this->_strftime(Horde_Nls::getLangInfo($this->_hour < 12 ? AM_STR : PM_STR))',
- '$this->_strftime(\'%H:%M\')',
- 'sprintf(\'%02d\', $this->_sec)',
- "\t",
- '$this->_strftime(\'%H:%M:%S\')',
- '$this->_strftime(Horde_Nls::getLangInfo(D_FMT))',
- '$this->_strftime(Horde_Nls::getLangInfo(T_FMT))',
- 'substr(sprintf(\'%04d\', $this->_year), -2)',
- (int)$this->_year,
- '%'),
- $format);
- }
-
- /**
- * Corrects any over- or underflows in any of the date's members.
- *
- * @param integer $mask We may not want to correct some overflows.
- * @param integer $down Whether to correct the date up or down.
- */
- protected function _correct($mask = self::MASK_ALLPARTS, $down = false)
- {
- if ($mask & self::MASK_SECOND) {
- if ($this->_sec < 0 || $this->_sec > 59) {
- $mask |= self::MASK_MINUTE;
-
- $this->_min += (int)($this->_sec / 60);
- $this->_sec %= 60;
- if ($this->_sec < 0) {
- $this->_min--;
- $this->_sec += 60;
- }
- }
- }
-
- if ($mask & self::MASK_MINUTE) {
- if ($this->_min < 0 || $this->_min > 59) {
- $mask |= self::MASK_HOUR;
-
- $this->_hour += (int)($this->_min / 60);
- $this->_min %= 60;
- if ($this->_min < 0) {
- $this->_hour--;
- $this->_min += 60;
- }
- }
- }
-
- if ($mask & self::MASK_HOUR) {
- if ($this->_hour < 0 || $this->_hour > 23) {
- $mask |= self::MASK_DAY;
-
- $this->_mday += (int)($this->_hour / 24);
- $this->_hour %= 24;
- if ($this->_hour < 0) {
- $this->_mday--;
- $this->_hour += 24;
- }
- }
- }
-
- if ($mask & self::MASK_MONTH) {
- $this->_correctMonth($down);
- /* When correcting the month, always correct the day too. Months
- * have different numbers of days. */
- $mask |= self::MASK_DAY;
- }
-
- if ($mask & self::MASK_DAY) {
- while ($this->_mday > 28 &&
- $this->_mday > Horde_Date_Utils::daysInMonth($this->_month, $this->_year)) {
- if ($down) {
- $this->_mday -= Horde_Date_Utils::daysInMonth($this->_month + 1, $this->_year) - Horde_Date_Utils::daysInMonth($this->_month, $this->_year);
- } else {
- $this->_mday -= Horde_Date_Utils::daysInMonth($this->_month, $this->_year);
- $this->_month++;
- }
- $this->_correctMonth($down);
- }
- while ($this->_mday < 1) {
- --$this->_month;
- $this->_correctMonth($down);
- $this->_mday += Horde_Date_Utils::daysInMonth($this->_month, $this->_year);
- }
- }
- }
-
- /**
- * Corrects the current month.
- *
- * This cannot be done in _correct() because that would also trigger a
- * correction of the day, which would result in an infinite loop.
- *
- * @param integer $down Whether to correct the date up or down.
- */
- protected function _correctMonth($down = false)
- {
- $this->_year += (int)($this->_month / 12);
- $this->_month %= 12;
- if ($this->_month < 1) {
- $this->_year--;
- $this->_month += 12;
- }
- }
-
- /**
- * Handles args in order: year month day hour min sec tz
- */
- protected function _initializeFromArgs($args)
- {
- $tz = (isset($args[6])) ? array_pop($args) : null;
- $this->_initializeTimezone($tz);
-
- $args = array_slice($args, 0, 6);
- $keys = array('year' => 1, 'month' => 1, 'mday' => 1, 'hour' => 0, 'min' => 0, 'sec' => 0);
- $date = array_combine(array_slice(array_keys($keys), 0, count($args)), $args);
- $date = array_merge($keys, $date);
-
- $this->_initializeFromArray($date);
- }
-
- protected function _initializeFromArray($date)
- {
- if (isset($date['year']) && is_string($date['year']) && strlen($date['year']) == 2) {
- if ($date['year'] > 70) {
- $date['year'] += 1900;
- } else {
- $date['year'] += 2000;
- }
- }
-
- foreach ($date as $key => $val) {
- if (in_array($key, array('year', 'month', 'mday', 'hour', 'min', 'sec'))) {
- $this->{'_'. $key} = (int)$val;
- }
- }
-
- // If $date['day'] is present and numeric we may have been passed
- // a Horde_Form_datetime array.
- if (isset($date['day']) &&
- (string)(int)$date['day'] == $date['day']) {
- $this->_mday = (int)$date['day'];
- }
- // 'minute' key also from Horde_Form_datetime
- if (isset($date['minute']) &&
- (string)(int)$date['minute'] == $date['minute']) {
- $this->_min = (int)$date['minute'];
- }
-
- $this->_correct();
- }
-
- protected function _initializeFromObject($date)
- {
- if ($date instanceof DateTime) {
- $this->_year = (int)$date->format('Y');
- $this->_month = (int)$date->format('m');
- $this->_mday = (int)$date->format('d');
- $this->_hour = (int)$date->format('H');
- $this->_min = (int)$date->format('i');
- $this->_sec = (int)$date->format('s');
- $this->_initializeTimezone($date->getTimezone()->getName());
- } else {
- $is_horde_date = $date instanceof Horde_Date;
- foreach (array('year', 'month', 'mday', 'hour', 'min', 'sec') as $key) {
- if ($is_horde_date || isset($date->$key)) {
- $this->{'_' . $key} = (int)$date->$key;
- }
- }
- if (!$is_horde_date) {
- $this->_correct();
- } else {
- $this->_initializeTimezone($date->timezone);
- }
- }
- }
-
- protected function _initializeTimezone($timezone)
- {
- if (empty($timezone)) {
- $timezone = date_default_timezone_get();
- }
- $this->_timezone = $timezone;
- }
-
-}
-
-/**
- * @category Horde
- * @package Date
- */
-
-/**
- * Horde Date wrapper/logic class, including some calculation
- * functions.
- *
- * @category Horde
- * @package Date
- */
-class Horde_Date_Utils
-{
- /**
- * Returns whether a year is a leap year.
- *
- * @param integer $year The year.
- *
- * @return boolean True if the year is a leap year.
- */
- public static function isLeapYear($year)
- {
- if (strlen($year) != 4 || preg_match('/\D/', $year)) {
- return false;
- }
-
- return (($year % 4 == 0 && $year % 100 != 0) || $year % 400 == 0);
- }
-
- /**
- * Returns the date of the year that corresponds to the first day of the
- * given week.
- *
- * @param integer $week The week of the year to find the first day of.
- * @param integer $year The year to calculate for.
- *
- * @return Horde_Date The date of the first day of the given week.
- */
- public static function firstDayOfWeek($week, $year)
- {
- return new Horde_Date(sprintf('%04dW%02d', $year, $week));
- }
-
- /**
- * Returns the number of days in the specified month.
- *
- * @param integer $month The month
- * @param integer $year The year.
- *
- * @return integer The number of days in the month.
- */
- public static function daysInMonth($month, $year)
- {
- static $cache = array();
- if (!isset($cache[$year][$month])) {
- $date = new DateTime(sprintf('%04d-%02d-01', $year, $month));
- $cache[$year][$month] = $date->format('t');
- }
- return $cache[$year][$month];
- }
-
- /**
- * Returns a relative, natural language representation of a timestamp
- *
- * @todo Wider range of values ... maybe future time as well?
- * @todo Support minimum resolution parameter.
- *
- * @param mixed $time The time. Any format accepted by Horde_Date.
- * @param string $date_format Format to display date if timestamp is
- * more then 1 day old.
- * @param string $time_format Format to display time if timestamp is 1
- * day old.
- *
- * @return string The relative time (i.e. 2 minutes ago)
- */
- public static function relativeDateTime($time, $date_format = '%x',
- $time_format = '%X')
- {
- $date = new Horde_Date($time);
-
- $delta = time() - $date->timestamp();
- if ($delta < 60) {
- return sprintf(Horde_Date_Translation::ngettext("%d second ago", "%d seconds ago", $delta), $delta);
- }
-
- $delta = round($delta / 60);
- if ($delta < 60) {
- return sprintf(Horde_Date_Translation::ngettext("%d minute ago", "%d minutes ago", $delta), $delta);
- }
-
- $delta = round($delta / 60);
- if ($delta < 24) {
- return sprintf(Horde_Date_Translation::ngettext("%d hour ago", "%d hours ago", $delta), $delta);
- }
-
- if ($delta > 24 && $delta < 48) {
- $date = new Horde_Date($time);
- return sprintf(Horde_Date_Translation::t("yesterday at %s"), $date->strftime($time_format));
- }
-
- $delta = round($delta / 24);
- if ($delta < 7) {
- return sprintf(Horde_Date_Translation::t("%d days ago"), $delta);
- }
-
- if (round($delta / 7) < 5) {
- $delta = round($delta / 7);
- return sprintf(Horde_Date_Translation::ngettext("%d week ago", "%d weeks ago", $delta), $delta);
- }
-
- // Default to the user specified date format.
- return $date->strftime($date_format);
- }
-
- /**
- * Tries to convert strftime() formatters to date() formatters.
- *
- * Unsupported formatters will be removed.
- *
- * @param string $format A strftime() formatting string.
- *
- * @return string A date() formatting string.
- */
- public static function strftime2date($format)
- {
- $replace = array(
- '/%a/' => 'D',
- '/%A/' => 'l',
- '/%d/' => 'd',
- '/%e/' => 'j',
- '/%j/' => 'z',
- '/%u/' => 'N',
- '/%w/' => 'w',
- '/%U/' => '',
- '/%V/' => 'W',
- '/%W/' => '',
- '/%b/' => 'M',
- '/%B/' => 'F',
- '/%h/' => 'M',
- '/%m/' => 'm',
- '/%C/' => '',
- '/%g/' => '',
- '/%G/' => 'o',
- '/%y/' => 'y',
- '/%Y/' => 'Y',
- '/%H/' => 'H',
- '/%I/' => 'h',
- '/%i/' => 'g',
- '/%M/' => 'i',
- '/%p/' => 'A',
- '/%P/' => 'a',
- '/%r/' => 'h:i:s A',
- '/%R/' => 'H:i',
- '/%S/' => 's',
- '/%T/' => 'H:i:s',
- '/%X/e' => 'Horde_Date_Utils::strftime2date(Horde_Nls::getLangInfo(T_FMT))',
- '/%z/' => 'O',
- '/%Z/' => '',
- '/%c/' => '',
- '/%D/' => 'm/d/y',
- '/%F/' => 'Y-m-d',
- '/%s/' => 'U',
- '/%x/e' => 'Horde_Date_Utils::strftime2date(Horde_Nls::getLangInfo(D_FMT))',
- '/%n/' => "\n",
- '/%t/' => "\t",
- '/%%/' => '%'
- );
-
- return preg_replace(array_keys($replace), array_values($replace), $format);
- }
-
-}
diff --git a/lib/plugins/libkolab/lib/Horde_Date_Recurrence.php b/lib/plugins/libkolab/lib/Horde_Date_Recurrence.php
deleted file mode 100644
index 81f0857..0000000
--- a/lib/plugins/libkolab/lib/Horde_Date_Recurrence.php
+++ /dev/null
@@ -1,1673 +0,0 @@
-<?php
-
-/**
- * This is a modified copy of Horde/Date/Recurrence.php
- * Pull the latest version of this file from the PEAR channel of the Horde
- * project at http://pear.horde.org by installing the Horde_Date package.
- */
-
-if (!class_exists('Horde_Date'))
- require_once(dirname(__FILE__) . '/Horde_Date.php');
-
-// minimal required implementation of Horde_Date_Translation to avoid a huge dependency nightmare
-class Horde_Date_Translation
-{
- function t($arg) { return $arg; }
- function ngettext($sing, $plur, $num) { return ($num > 1 ? $plur : $sing); }
-}
-
-
-/**
- * This file contains the Horde_Date_Recurrence class and according constants.
- *
- * Copyright 2007-2012 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.horde.org/licenses/lgpl21.
- *
- * @category Horde
- * @package Date
- */
-
-/**
- * The Horde_Date_Recurrence class implements algorithms for calculating
- * recurrences of events, including several recurrence types, intervals,
- * exceptions, and conversion from and to vCalendar and iCalendar recurrence
- * rules.
- *
- * All methods expecting dates as parameters accept all values that the
- * Horde_Date constructor accepts, i.e. a timestamp, another Horde_Date
- * object, an ISO time string or a hash.
- *
- * @author Jan Schneider <jan@horde.org>
- * @category Horde
- * @package Date
- */
-class Horde_Date_Recurrence
-{
- /** No Recurrence **/
- const RECUR_NONE = 0;
-
- /** Recurs daily. */
- const RECUR_DAILY = 1;
-
- /** Recurs weekly. */
- const RECUR_WEEKLY = 2;
-
- /** Recurs monthly on the same date. */
- const RECUR_MONTHLY_DATE = 3;
-
- /** Recurs monthly on the same week day. */
- const RECUR_MONTHLY_WEEKDAY = 4;
-
- /** Recurs yearly on the same date. */
- const RECUR_YEARLY_DATE = 5;
-
- /** Recurs yearly on the same day of the year. */
- const RECUR_YEARLY_DAY = 6;
-
- /** Recurs yearly on the same week day. */
- const RECUR_YEARLY_WEEKDAY = 7;
-
- /**
- * The start time of the event.
- *
- * @var Horde_Date
- */
- public $start;
-
- /**
- * The end date of the recurrence interval.
- *
- * @var Horde_Date
- */
- public $recurEnd = null;
-
- /**
- * The number of recurrences.
- *
- * @var integer
- */
- public $recurCount = null;
-
- /**
- * The type of recurrence this event follows. RECUR_* constant.
- *
- * @var integer
- */
- public $recurType = self::RECUR_NONE;
-
- /**
- * The length of time between recurrences. The time unit depends on the
- * recurrence type.
- *
- * @var integer
- */
- public $recurInterval = 1;
-
- /**
- * Any additional recurrence data.
- *
- * @var integer
- */
- public $recurData = null;
-
- /**
- * BYDAY recurrence number
- *
- * @var integer
- */
- public $recurNthDay = null;
-
- /**
- * BYMONTH recurrence data
- *
- * @var array
- */
- public $recurMonths = array();
-
- /**
- * All the exceptions from recurrence for this event.
- *
- * @var array
- */
- public $exceptions = array();
-
- /**
- * All the dates this recurrence has been marked as completed.
- *
- * @var array
- */
- public $completions = array();
-
- /**
- * Constructor.
- *
- * @param Horde_Date $start Start of the recurring event.
- */
- public function __construct($start)
- {
- $this->start = new Horde_Date($start);
- }
-
- /**
- * Resets the class properties.
- */
- public function reset()
- {
- $this->recurEnd = null;
- $this->recurCount = null;
- $this->recurType = self::RECUR_NONE;
- $this->recurInterval = 1;
- $this->recurData = null;
- $this->exceptions = array();
- $this->completions = array();
- }
-
- /**
- * Checks if this event recurs on a given day of the week.
- *
- * @param integer $dayMask A mask consisting of Horde_Date::MASK_*
- * constants specifying the day(s) to check.
- *
- * @return boolean True if this event recurs on the given day(s).
- */
- public function recurOnDay($dayMask)
- {
- return ($this->recurData & $dayMask);
- }
-
- /**
- * Specifies the days this event recurs on.
- *
- * @param integer $dayMask A mask consisting of Horde_Date::MASK_*
- * constants specifying the day(s) to recur on.
- */
- public function setRecurOnDay($dayMask)
- {
- $this->recurData = $dayMask;
- }
-
- /**
- *
- * @param integer $nthDay The nth weekday of month to repeat events on
- */
- public function setRecurNthWeekday($nth)
- {
- $this->recurNthDay = (int)$nth;
- }
-
- /**
- *
- * @return integer The nth weekday of month to repeat events.
- */
- public function getRecurNthWeekday()
- {
- return isset($this->recurNthDay) ? $this->recurNthDay : ceil($this->start->mday / 7);
- }
-
- /**
- * Specifies the months for yearly (weekday) recurrence
- *
- * @param array $months List of months (integers) this event recurs on.
- */
- function setRecurByMonth($months)
- {
- $this->recurMonths = (array)$months;
- }
-
- /**
- * Returns a list of months this yearly event recurs on
- *
- * @return array List of months (integers) this event recurs on.
- */
- function getRecurByMonth()
- {
- return $this->recurMonths;
- }
-
- /**
- * Returns the days this event recurs on.
- *
- * @return integer A mask consisting of Horde_Date::MASK_* constants
- * specifying the day(s) this event recurs on.
- */
- public function getRecurOnDays()
- {
- return $this->recurData;
- }
-
- /**
- * Returns whether this event has a specific recurrence type.
- *
- * @param integer $recurrence RECUR_* constant of the
- * recurrence type to check for.
- *
- * @return boolean True if the event has the specified recurrence type.
- */
- public function hasRecurType($recurrence)
- {
- return ($recurrence == $this->recurType);
- }
-
- /**
- * Sets a recurrence type for this event.
- *
- * @param integer $recurrence A RECUR_* constant.
- */
- public function setRecurType($recurrence)
- {
- $this->recurType = $recurrence;
- }
-
- /**
- * Returns recurrence type of this event.
- *
- * @return integer A RECUR_* constant.
- */
- public function getRecurType()
- {
- return $this->recurType;
- }
-
- /**
- * Returns a description of this event's recurring type.
- *
- * @return string Human readable recurring type.
- */
- public function getRecurName()
- {
- switch ($this->getRecurType()) {
- case self::RECUR_NONE: return Horde_Date_Translation::t("No recurrence");
- case self::RECUR_DAILY: return Horde_Date_Translation::t("Daily");
- case self::RECUR_WEEKLY: return Horde_Date_Translation::t("Weekly");
- case self::RECUR_MONTHLY_DATE:
- case self::RECUR_MONTHLY_WEEKDAY: return Horde_Date_Translation::t("Monthly");
- case self::RECUR_YEARLY_DATE:
- case self::RECUR_YEARLY_DAY:
- case self::RECUR_YEARLY_WEEKDAY: return Horde_Date_Translation::t("Yearly");
- }
- }
-
- /**
- * Sets the length of time between recurrences of this event.
- *
- * @param integer $interval The time between recurrences.
- */
- public function setRecurInterval($interval)
- {
- if ($interval > 0) {
- $this->recurInterval = $interval;
- }
- }
-
- /**
- * Retrieves the length of time between recurrences of this event.
- *
- * @return integer The number of seconds between recurrences.
- */
- public function getRecurInterval()
- {
- return $this->recurInterval;
- }
-
- /**
- * Sets the number of recurrences of this event.
- *
- * @param integer $count The number of recurrences.
- */
- public function setRecurCount($count)
- {
- if ($count > 0) {
- $this->recurCount = (int)$count;
- // Recurrence counts and end dates are mutually exclusive.
- $this->recurEnd = null;
- } else {
- $this->recurCount = null;
- }
- }
-
- /**
- * Retrieves the number of recurrences of this event.
- *
- * @return integer The number recurrences.
- */
- public function getRecurCount()
- {
- return $this->recurCount;
- }
-
- /**
- * Returns whether this event has a recurrence with a fixed count.
- *
- * @return boolean True if this recurrence has a fixed count.
- */
- public function hasRecurCount()
- {
- return isset($this->recurCount);
- }
-
- /**
- * Sets the start date of the recurrence interval.
- *
- * @param Horde_Date $start The recurrence start.
- */
- public function setRecurStart($start)
- {
- $this->start = clone $start;
- }
-
- /**
- * Retrieves the start date of the recurrence interval.
- *
- * @return Horde_Date The recurrence start.
- */
- public function getRecurStart()
- {
- return $this->start;
- }
-
- /**
- * Sets the end date of the recurrence interval.
- *
- * @param Horde_Date $end The recurrence end.
- */
- public function setRecurEnd($end)
- {
- if (!empty($end)) {
- // Recurrence counts and end dates are mutually exclusive.
- $this->recurCount = null;
- $this->recurEnd = clone $end;
- } else {
- $this->recurEnd = $end;
- }
- }
-
- /**
- * Retrieves the end date of the recurrence interval.
- *
- * @return Horde_Date The recurrence end.
- */
- public function getRecurEnd()
- {
- return $this->recurEnd;
- }
-
- /**
- * Returns whether this event has a recurrence end.
- *
- * @return boolean True if this recurrence ends.
- */
- public function hasRecurEnd()
- {
- return isset($this->recurEnd) && isset($this->recurEnd->year) &&
- $this->recurEnd->year != 9999;
- }
-
- /**
- * Finds the next recurrence of this event that's after $afterDate.
- *
- * @param Horde_Date|string $after Return events after this date.
- *
- * @return Horde_Date|boolean The date of the next recurrence or false
- * if the event does not recur after
- * $afterDate.
- */
- public function nextRecurrence($after)
- {
- if (!($after instanceof Horde_Date)) {
- $after = new Horde_Date($after);
- } else {
- $after = clone($after);
- }
-
- // Make sure $after and $this->start are in the same TZ
- $after->setTimezone($this->start->timezone);
- if ($this->start->compareDateTime($after) >= 0) {
- return clone $this->start;
- }
-
- if ($this->recurInterval == 0) {
- return false;
- }
-
- switch ($this->getRecurType()) {
- case self::RECUR_DAILY:
- $diff = $this->start->diff($after);
- $recur = ceil($diff / $this->recurInterval);
- if ($this->recurCount && $recur >= $this->recurCount) {
- return false;
- }
-
- $recur *= $this->recurInterval;
- $next = $this->start->add(array('day' => $recur));
- if ((!$this->hasRecurEnd() ||
- $next->compareDateTime($this->recurEnd) <= 0) &&
- $next->compareDateTime($after) >= 0) {
- return $next;
- }
- break;
-
- case self::RECUR_WEEKLY:
- if (empty($this->recurData)) {
- return false;
- }
-
- $start_week = Horde_Date_Utils::firstDayOfWeek($this->start->format('W'),
- $this->start->year);
- $start_week->timezone = $this->start->timezone;
- $start_week->hour = $this->start->hour;
- $start_week->min = $this->start->min;
- $start_week->sec = $this->start->sec;
-
- // Make sure we are not at the ISO-8601 first week of year while
- // still in month 12...OR in the ISO-8601 last week of year while
- // in month 1 and adjust the year accordingly.
- $week = $after->format('W');
- if ($week == 1 && $after->month == 12) {
- $theYear = $after->year + 1;
- } elseif ($week >= 52 && $after->month == 1) {
- $theYear = $after->year - 1;
- } else {
- $theYear = $after->year;
- }
-
- $after_week = Horde_Date_Utils::firstDayOfWeek($week, $theYear);
- $after_week->timezone = $this->start->timezone;
- $after_week_end = clone $after_week;
- $after_week_end->mday += 7;
-
- $diff = $start_week->diff($after_week);
- $interval = $this->recurInterval * 7;
- $repeats = floor($diff / $interval);
- if ($diff % $interval < 7) {
- $recur = $diff;
- } else {
- /**
- * If the after_week is not in the first week interval the
- * search needs to skip ahead a complete interval. The way it is
- * calculated here means that an event that occurs every second
- * week on Monday and Wednesday with the event actually starting
- * on Tuesday or Wednesday will only have one incidence in the
- * first week.
- */
- $recur = $interval * ($repeats + 1);
- }
-
- if ($this->hasRecurCount()) {
- $recurrences = 0;
- /**
- * Correct the number of recurrences by the number of events
- * that lay between the start of the start week and the
- * recurrence start.
- */
- $next = clone $start_week;
- while ($next->compareDateTime($this->start) < 0) {
- if ($this->recurOnDay((int)pow(2, $next->dayOfWeek()))) {
- $recurrences--;
- }
- ++$next->mday;
- }
- if ($repeats > 0) {
- $weekdays = $this->recurData;
- $total_recurrences_per_week = 0;
- while ($weekdays > 0) {
- if ($weekdays % 2) {
- $total_recurrences_per_week++;
- }
- $weekdays = ($weekdays - ($weekdays % 2)) / 2;
- }
- $recurrences += $total_recurrences_per_week * $repeats;
- }
- }
-
- $next = clone $start_week;
- $next->mday += $recur;
- while ($next->compareDateTime($after) < 0 &&
- $next->compareDateTime($after_week_end) < 0) {
- if ($this->hasRecurCount()
- && $next->compareDateTime($after) < 0
- && $this->recurOnDay((int)pow(2, $next->dayOfWeek()))) {
- $recurrences++;
- }
- ++$next->mday;
- }
- if ($this->hasRecurCount() &&
- $recurrences >= $this->recurCount) {
- return false;
- }
- if (!$this->hasRecurEnd() ||
- $next->compareDateTime($this->recurEnd) <= 0) {
- if ($next->compareDateTime($after_week_end) >= 0) {
- return $this->nextRecurrence($after_week_end);
- }
- while (!$this->recurOnDay((int)pow(2, $next->dayOfWeek())) &&
- $next->compareDateTime($after_week_end) < 0) {
- ++$next->mday;
- }
- if (!$this->hasRecurEnd() ||
- $next->compareDateTime($this->recurEnd) <= 0) {
- if ($next->compareDateTime($after_week_end) >= 0) {
- return $this->nextRecurrence($after_week_end);
- } else {
- return $next;
- }
- }
- }
- break;
-
- case self::RECUR_MONTHLY_DATE:
- $start = clone $this->start;
- if ($after->compareDateTime($start) < 0) {
- $after = clone $start;
- } else {
- $after = clone $after;
- }
-
- // If we're starting past this month's recurrence of the event,
- // look in the next month on the day the event recurs.
- if ($after->mday > $start->mday) {
- ++$after->month;
- $after->mday = $start->mday;
- }
-
- // Adjust $start to be the first match.
- $offset = ($after->month - $start->month) + ($after->year - $start->year) * 12;
- $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval;
-
- if ($this->recurCount &&
- ($offset / $this->recurInterval) >= $this->recurCount) {
- return false;
- }
- $start->month += $offset;
- $count = $offset / $this->recurInterval;
-
- do {
- if ($this->recurCount &&
- $count++ >= $this->recurCount) {
- return false;
- }
-
- // Bail if we've gone past the end of recurrence.
- if ($this->hasRecurEnd() &&
- $this->recurEnd->compareDateTime($start) < 0) {
- return false;
- }
- if ($start->isValid()) {
- return $start;
- }
-
- // If the interval is 12, and the date isn't valid, then we
- // need to see if February 29th is an option. If not, then the
- // event will _never_ recur, and we need to stop checking to
- // avoid an infinite loop.
- if ($this->recurInterval == 12 && ($start->month != 2 || $start->mday > 29)) {
- return false;
- }
-
- // Add the recurrence interval.
- $start->month += $this->recurInterval;
- } while (true);
-
- break;
-
- case self::RECUR_MONTHLY_WEEKDAY:
- // Start with the start date of the event.
- $estart = clone $this->start;
-
- // What day of the week, and week of the month, do we recur on?
- if (isset($this->recurNthDay)) {
- $nth = $this->recurNthDay;
- $weekday = log($this->recurData, 2);
- } else {
- $nth = ceil($this->start->mday / 7);
- $weekday = $estart->dayOfWeek();
- }
-
- // Adjust $estart to be the first candidate.
- $offset = ($after->month - $estart->month) + ($after->year - $estart->year) * 12;
- $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval;
-
- // Adjust our working date until it's after $after.
- $estart->month += $offset - $this->recurInterval;
-
- $count = $offset / $this->recurInterval;
- do {
- if ($this->recurCount &&
- $count++ >= $this->recurCount) {
- return false;
- }
-
- $estart->month += $this->recurInterval;
-
- $next = clone $estart;
- $next->setNthWeekday($weekday, $nth);
-
- if ($next->compareDateTime($after) < 0) {
- // We haven't made it past $after yet, try again.
- continue;
- }
- if ($this->hasRecurEnd() &&
- $next->compareDateTime($this->recurEnd) > 0) {
- // We've gone past the end of recurrence; we can give up
- // now.
- return false;
- }
-
- // We have a candidate to return.
- break;
- } while (true);
-
- return $next;
-
- case self::RECUR_YEARLY_DATE:
- // Start with the start date of the event.
- $estart = clone $this->start;
- $after = clone $after;
-
- if ($after->month > $estart->month ||
- ($after->month == $estart->month && $after->mday > $estart->mday)) {
- ++$after->year;
- $after->month = $estart->month;
- $after->mday = $estart->mday;
- }
-
- // Seperate case here for February 29th
- if ($estart->month == 2 && $estart->mday == 29) {
- while (!Horde_Date_Utils::isLeapYear($after->year)) {
- ++$after->year;
- }
- }
-
- // Adjust $estart to be the first candidate.
- $offset = $after->year - $estart->year;
- if ($offset > 0) {
- $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval;
- $estart->year += $offset;
- }
-
- // We've gone past the end of recurrence; give up.
- if ($this->recurCount &&
- $offset >= $this->recurCount) {
- return false;
- }
- if ($this->hasRecurEnd() &&
- $this->recurEnd->compareDateTime($estart) < 0) {
- return false;
- }
-
- return $estart;
-
- case self::RECUR_YEARLY_DAY:
- // Check count first.
- $dayofyear = $this->start->dayOfYear();
- $count = ($after->year - $this->start->year) / $this->recurInterval + 1;
- if ($this->recurCount &&
- ($count > $this->recurCount ||
- ($count == $this->recurCount &&
- $after->dayOfYear() > $dayofyear))) {
- return false;
- }
-
- // Start with a rough interval.
- $estart = clone $this->start;
- $estart->year += floor($count - 1) * $this->recurInterval;
-
- // Now add the difference to the required day of year.
- $estart->mday += $dayofyear - $estart->dayOfYear();
-
- // Add an interval if the estimation was wrong.
- if ($estart->compareDate($after) < 0) {
- $estart->year += $this->recurInterval;
- $estart->mday += $dayofyear - $estart->dayOfYear();
- }
-
- // We've gone past the end of recurrence; give up.
- if ($this->hasRecurEnd() &&
- $this->recurEnd->compareDateTime($estart) < 0) {
- return false;
- }
-
- return $estart;
-
- case self::RECUR_YEARLY_WEEKDAY:
- // Start with the start date of the event.
- $estart = clone $this->start;
-
- // What day of the week, and week of the month, do we recur on?
- if (isset($this->recurNthDay)) {
- $nth = $this->recurNthDay;
- $weekday = log($this->recurData, 2);
- } else {
- $nth = ceil($this->start->mday / 7);
- $weekday = $estart->dayOfWeek();
- }
-
- // Adjust $estart to be the first candidate.
- $offset = floor(($after->year - $estart->year + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval;
-
- // Adjust our working date until it's after $after.
- $estart->year += $offset - $this->recurInterval;
-
- $count = $offset / $this->recurInterval;
- do {
- if ($this->recurCount &&
- $count++ >= $this->recurCount) {
- return false;
- }
-
- $estart->year += $this->recurInterval;
-
- $next = clone $estart;
- $next->setNthWeekday($weekday, $nth);
-
- if ($next->compareDateTime($after) < 0) {
- // We haven't made it past $after yet, try again.
- continue;
- }
- if ($this->hasRecurEnd() &&
- $next->compareDateTime($this->recurEnd) > 0) {
- // We've gone past the end of recurrence; we can give up
- // now.
- return false;
- }
-
- // We have a candidate to return.
- break;
- } while (true);
-
- return $next;
- }
-
- // We didn't find anything, the recurType was bad, or something else
- // went wrong - return false.
- return false;
- }
-
- /**
- * Returns whether this event has any date that matches the recurrence
- * rules and is not an exception.
- *
- * @return boolean True if an active recurrence exists.
- */
- public function hasActiveRecurrence()
- {
- if (!$this->hasRecurEnd()) {
- return true;
- }
-
- $next = $this->nextRecurrence(new Horde_Date($this->start));
- while (is_object($next)) {
- if (!$this->hasException($next->year, $next->month, $next->mday) &&
- !$this->hasCompletion($next->year, $next->month, $next->mday)) {
- return true;
- }
-
- $next = $this->nextRecurrence($next->add(array('day' => 1)));
- }
-
- return false;
- }
-
- /**
- * Returns the next active recurrence.
- *
- * @param Horde_Date $afterDate Return events after this date.
- *
- * @return Horde_Date|boolean The date of the next active
- * recurrence or false if the event
- * has no active recurrence after
- * $afterDate.
- */
- public function nextActiveRecurrence($afterDate)
- {
- $next = $this->nextRecurrence($afterDate);
- while (is_object($next)) {
- if (!$this->hasException($next->year, $next->month, $next->mday) &&
- !$this->hasCompletion($next->year, $next->month, $next->mday)) {
- return $next;
- }
- $next->mday++;
- $next = $this->nextRecurrence($next);
- }
-
- return false;
- }
-
- /**
- * Adds an exception to a recurring event.
- *
- * @param integer $year The year of the execption.
- * @param integer $month The month of the execption.
- * @param integer $mday The day of the month of the exception.
- */
- public function addException($year, $month, $mday)
- {
- $this->exceptions[] = sprintf('%04d%02d%02d', $year, $month, $mday);
- }
-
- /**
- * Deletes an exception from a recurring event.
- *
- * @param integer $year The year of the execption.
- * @param integer $month The month of the execption.
- * @param integer $mday The day of the month of the exception.
- */
- public function deleteException($year, $month, $mday)
- {
- $key = array_search(sprintf('%04d%02d%02d', $year, $month, $mday), $this->exceptions);
- if ($key !== false) {
- unset($this->exceptions[$key]);
- }
- }
-
- /**
- * Checks if an exception exists for a given reccurence of an event.
- *
- * @param integer $year The year of the reucrance.
- * @param integer $month The month of the reucrance.
- * @param integer $mday The day of the month of the reucrance.
- *
- * @return boolean True if an exception exists for the given date.
- */
- public function hasException($year, $month, $mday)
- {
- return in_array(sprintf('%04d%02d%02d', $year, $month, $mday),
- $this->getExceptions());
- }
-
- /**
- * Retrieves all the exceptions for this event.
- *
- * @return array Array containing the dates of all the exceptions in
- * YYYYMMDD form.
- */
- public function getExceptions()
- {
- return $this->exceptions;
- }
-
- /**
- * Adds a completion to a recurring event.
- *
- * @param integer $year The year of the execption.
- * @param integer $month The month of the execption.
- * @param integer $mday The day of the month of the completion.
- */
- public function addCompletion($year, $month, $mday)
- {
- $this->completions[] = sprintf('%04d%02d%02d', $year, $month, $mday);
- }
-
- /**
- * Deletes a completion from a recurring event.
- *
- * @param integer $year The year of the execption.
- * @param integer $month The month of the execption.
- * @param integer $mday The day of the month of the completion.
- */
- public function deleteCompletion($year, $month, $mday)
- {
- $key = array_search(sprintf('%04d%02d%02d', $year, $month, $mday), $this->completions);
- if ($key !== false) {
- unset($this->completions[$key]);
- }
- }
-
- /**
- * Checks if a completion exists for a given reccurence of an event.
- *
- * @param integer $year The year of the reucrance.
- * @param integer $month The month of the recurrance.
- * @param integer $mday The day of the month of the recurrance.
- *
- * @return boolean True if a completion exists for the given date.
- */
- public function hasCompletion($year, $month, $mday)
- {
- return in_array(sprintf('%04d%02d%02d', $year, $month, $mday),
- $this->getCompletions());
- }
-
- /**
- * Retrieves all the completions for this event.
- *
- * @return array Array containing the dates of all the completions in
- * YYYYMMDD form.
- */
- public function getCompletions()
- {
- return $this->completions;
- }
-
- /**
- * Parses a vCalendar 1.0 recurrence rule.
- *
- * @link http://www.imc.org/pdi/vcal-10.txt
- * @link http://www.shuchow.com/vCalAddendum.html
- *
- * @param string $rrule A vCalendar 1.0 conform RRULE value.
- */
- public function fromRRule10($rrule)
- {
- $this->reset();
-
- if (!$rrule) {
- return;
- }
-
- if (!preg_match('/([A-Z]+)(\d+)?(.*)/', $rrule, $matches)) {
- // No recurrence data - event does not recur.
- $this->setRecurType(self::RECUR_NONE);
- }
-
- // Always default the recurInterval to 1.
- $this->setRecurInterval(!empty($matches[2]) ? $matches[2] : 1);
-
- $remainder = trim($matches[3]);
-
- switch ($matches[1]) {
- case 'D':
- $this->setRecurType(self::RECUR_DAILY);
- break;
-
- case 'W':
- $this->setRecurType(self::RECUR_WEEKLY);
- if (!empty($remainder)) {
- $mask = 0;
- while (preg_match('/^ ?[A-Z]{2} ?/', $remainder, $matches)) {
- $day = trim($matches[0]);
- $remainder = substr($remainder, strlen($matches[0]));
- $mask |= $maskdays[$day];
- }
- $this->setRecurOnDay($mask);
- } else {
- // Recur on the day of the week of the original recurrence.
- $maskdays = array(
- Horde_Date::DATE_SUNDAY => Horde_Date::MASK_SUNDAY,
- Horde_Date::DATE_MONDAY => Horde_Date::MASK_MONDAY,
- Horde_Date::DATE_TUESDAY => Horde_Date::MASK_TUESDAY,
- Horde_Date::DATE_WEDNESDAY => Horde_Date::MASK_WEDNESDAY,
- Horde_Date::DATE_THURSDAY => Horde_Date::MASK_THURSDAY,
- Horde_Date::DATE_FRIDAY => Horde_Date::MASK_FRIDAY,
- Horde_Date::DATE_SATURDAY => Horde_Date::MASK_SATURDAY,
- );
- $this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]);
- }
- break;
-
- case 'MP':
- $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY);
- break;
-
- case 'MD':
- $this->setRecurType(self::RECUR_MONTHLY_DATE);
- break;
-
- case 'YM':
- $this->setRecurType(self::RECUR_YEARLY_DATE);
- break;
-
- case 'YD':
- $this->setRecurType(self::RECUR_YEARLY_DAY);
- break;
- }
-
- // We don't support modifiers at the moment, strip them.
- while ($remainder && !preg_match('/^(#\d+|\d{8})($| |T\d{6})/', $remainder)) {
- $remainder = substr($remainder, 1);
- }
- if (!empty($remainder)) {
- if (strpos($remainder, '#') === 0) {
- $this->setRecurCount(substr($remainder, 1));
- } else {
- list($year, $month, $mday) = sscanf($remainder, '%04d%02d%02d');
- $this->setRecurEnd(new Horde_Date(array('year' => $year,
- 'month' => $month,
- 'mday' => $mday,
- 'hour' => 23,
- 'min' => 59,
- 'sec' => 59)));
- }
- }
- }
-
- /**
- * Creates a vCalendar 1.0 recurrence rule.
- *
- * @link http://www.imc.org/pdi/vcal-10.txt
- * @link http://www.shuchow.com/vCalAddendum.html
- *
- * @param Horde_Icalendar $calendar A Horde_Icalendar object instance.
- *
- * @return string A vCalendar 1.0 conform RRULE value.
- */
- public function toRRule10($calendar)
- {
- switch ($this->recurType) {
- case self::RECUR_NONE:
- return '';
-
- case self::RECUR_DAILY:
- $rrule = 'D' . $this->recurInterval;
- break;
-
- case self::RECUR_WEEKLY:
- $rrule = 'W' . $this->recurInterval;
- $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
-
- for ($i = 0; $i <= 7; ++$i) {
- if ($this->recurOnDay(pow(2, $i))) {
- $rrule .= ' ' . $vcaldays[$i];
- }
- }
- break;
-
- case self::RECUR_MONTHLY_DATE:
- $rrule = 'MD' . $this->recurInterval . ' ' . trim($this->start->mday);
- break;
-
- case self::RECUR_MONTHLY_WEEKDAY:
- $nth_weekday = (int)($this->start->mday / 7);
- if (($this->start->mday % 7) > 0) {
- $nth_weekday++;
- }
-
- $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
- $rrule = 'MP' . $this->recurInterval . ' ' . $nth_weekday . '+ ' . $vcaldays[$this->start->dayOfWeek()];
-
- break;
-
- case self::RECUR_YEARLY_DATE:
- $rrule = 'YM' . $this->recurInterval . ' ' . trim($this->start->month);
- break;
-
- case self::RECUR_YEARLY_DAY:
- $rrule = 'YD' . $this->recurInterval . ' ' . $this->start->dayOfYear();
- break;
-
- default:
- return '';
- }
-
- if ($this->hasRecurEnd()) {
- $recurEnd = clone $this->recurEnd;
- return $rrule . ' ' . $calendar->_exportDateTime($recurEnd);
- }
-
- return $rrule . ' #' . (int)$this->getRecurCount();
- }
-
- /**
- * Parses an iCalendar 2.0 recurrence rule.
- *
- * @link http://rfc.net/rfc2445.html#s4.3.10
- * @link http://rfc.net/rfc2445.html#s4.8.5
- * @link http://www.shuchow.com/vCalAddendum.html
- *
- * @param string $rrule An iCalendar 2.0 conform RRULE value.
- */
- public function fromRRule20($rrule)
- {
- $this->reset();
-
- // Parse the recurrence rule into keys and values.
- $rdata = array();
- $parts = explode(';', $rrule);
- foreach ($parts as $part) {
- list($key, $value) = explode('=', $part, 2);
- $rdata[strtoupper($key)] = $value;
- }
-
- if (isset($rdata['FREQ'])) {
- // Always default the recurInterval to 1.
- $this->setRecurInterval(isset($rdata['INTERVAL']) ? $rdata['INTERVAL'] : 1);
-
- $maskdays = array(
- 'SU' => Horde_Date::MASK_SUNDAY,
- 'MO' => Horde_Date::MASK_MONDAY,
- 'TU' => Horde_Date::MASK_TUESDAY,
- 'WE' => Horde_Date::MASK_WEDNESDAY,
- 'TH' => Horde_Date::MASK_THURSDAY,
- 'FR' => Horde_Date::MASK_FRIDAY,
- 'SA' => Horde_Date::MASK_SATURDAY,
- );
-
- switch (strtoupper($rdata['FREQ'])) {
- case 'DAILY':
- $this->setRecurType(self::RECUR_DAILY);
- break;
-
- case 'WEEKLY':
- $this->setRecurType(self::RECUR_WEEKLY);
- if (isset($rdata['BYDAY'])) {
- $days = explode(',', $rdata['BYDAY']);
- $mask = 0;
- foreach ($days as $day) {
- $mask |= $maskdays[$day];
- }
- $this->setRecurOnDay($mask);
- } else {
- // Recur on the day of the week of the original
- // recurrence.
- $maskdays = array(
- Horde_Date::DATE_SUNDAY => Horde_Date::MASK_SUNDAY,
- Horde_Date::DATE_MONDAY => Horde_Date::MASK_MONDAY,
- Horde_Date::DATE_TUESDAY => Horde_Date::MASK_TUESDAY,
- Horde_Date::DATE_WEDNESDAY => Horde_Date::MASK_WEDNESDAY,
- Horde_Date::DATE_THURSDAY => Horde_Date::MASK_THURSDAY,
- Horde_Date::DATE_FRIDAY => Horde_Date::MASK_FRIDAY,
- Horde_Date::DATE_SATURDAY => Horde_Date::MASK_SATURDAY);
- $this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]);
- }
- break;
-
- case 'MONTHLY':
- if (isset($rdata['BYDAY'])) {
- $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY);
- if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) {
- $this->setRecurOnDay($maskdays[$m[2]]);
- $this->setRecurNthWeekday($m[1]);
- }
- } else {
- $this->setRecurType(self::RECUR_MONTHLY_DATE);
- }
- break;
-
- case 'YEARLY':
- if (isset($rdata['BYYEARDAY'])) {
- $this->setRecurType(self::RECUR_YEARLY_DAY);
- } elseif (isset($rdata['BYDAY'])) {
- $this->setRecurType(self::RECUR_YEARLY_WEEKDAY);
- if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) {
- $this->setRecurOnDay($maskdays[$m[2]]);
- $this->setRecurNthWeekday($m[1]);
- }
- if ($rdata['BYMONTH']) {
- $months = explode(',', $rdata['BYMONTH']);
- $this->setRecurByMonth($months);
- }
- } else {
- $this->setRecurType(self::RECUR_YEARLY_DATE);
- }
- break;
- }
-
- if (isset($rdata['UNTIL'])) {
- list($year, $month, $mday) = sscanf($rdata['UNTIL'],
- '%04d%02d%02d');
- $this->setRecurEnd(new Horde_Date(array('year' => $year,
- 'month' => $month,
- 'mday' => $mday,
- 'hour' => 23,
- 'min' => 59,
- 'sec' => 59)));
- }
- if (isset($rdata['COUNT'])) {
- $this->setRecurCount($rdata['COUNT']);
- }
- } else {
- // No recurrence data - event does not recur.
- $this->setRecurType(self::RECUR_NONE);
- }
- }
-
- /**
- * Creates an iCalendar 2.0 recurrence rule.
- *
- * @link http://rfc.net/rfc2445.html#s4.3.10
- * @link http://rfc.net/rfc2445.html#s4.8.5
- * @link http://www.shuchow.com/vCalAddendum.html
- *
- * @param Horde_Icalendar $calendar A Horde_Icalendar object instance.
- *
- * @return string An iCalendar 2.0 conform RRULE value.
- */
- public function toRRule20($calendar)
- {
- switch ($this->recurType) {
- case self::RECUR_NONE:
- return '';
-
- case self::RECUR_DAILY:
- $rrule = 'FREQ=DAILY;INTERVAL=' . $this->recurInterval;
- break;
-
- case self::RECUR_WEEKLY:
- $rrule = 'FREQ=WEEKLY;INTERVAL=' . $this->recurInterval . ';BYDAY=';
- $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
-
- for ($i = $flag = 0; $i <= 7; ++$i) {
- if ($this->recurOnDay(pow(2, $i))) {
- if ($flag) {
- $rrule .= ',';
- }
- $rrule .= $vcaldays[$i];
- $flag = true;
- }
- }
- break;
-
- case self::RECUR_MONTHLY_DATE:
- $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval;
- break;
-
- case self::RECUR_MONTHLY_WEEKDAY:
- if (isset($this->recurNthDay)) {
- $nth_weekday = $this->recurNthDay;
- $day_of_week = log($this->recurData, 2);
- } else {
- $day_of_week = $this->start->dayOfWeek();
- $nth_weekday = (int)($this->start->mday / 7);
- if (($this->start->mday % 7) > 0) {
- $nth_weekday++;
- }
- }
- $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
- $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval
- . ';BYDAY=' . $nth_weekday . $vcaldays[$day_of_week];
- break;
-
- case self::RECUR_YEARLY_DATE:
- $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval;
- break;
-
- case self::RECUR_YEARLY_DAY:
- $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval
- . ';BYYEARDAY=' . $this->start->dayOfYear();
- break;
-
- case self::RECUR_YEARLY_WEEKDAY:
- if (isset($this->recurNthDay)) {
- $nth_weekday = $this->recurNthDay;
- $day_of_week = log($this->recurData, 2);
- } else {
- $day_of_week = $this->start->dayOfWeek();
- $nth_weekday = (int)($this->start->mday / 7);
- if (($this->start->mday % 7) > 0) {
- $nth_weekday++;
- }
- }
- $months = !empty($this->recurMonths) ? join(',', $this->recurMonths) : $this->start->month;
- $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
- $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval
- . ';BYDAY='
- . $nth_weekday
- . $vcaldays[$day_of_week]
- . ';BYMONTH=' . $this->start->month;
- break;
- }
-
- if ($this->hasRecurEnd()) {
- $recurEnd = clone $this->recurEnd;
- $rrule .= ';UNTIL=' . $calendar->_exportDateTime($recurEnd);
- }
- if ($count = $this->getRecurCount()) {
- $rrule .= ';COUNT=' . $count;
- }
- return $rrule;
- }
-
- /**
- * Parses the recurrence data from a hash.
- *
- * @param array $hash The hash to convert.
- *
- * @return boolean True if the hash seemed valid, false otherwise.
- */
- public function fromHash($hash)
- {
- $this->reset();
-
- if (!isset($hash['interval']) || !isset($hash['cycle'])) {
- $this->setRecurType(self::RECUR_NONE);
- return false;
- }
-
- $this->setRecurInterval((int)$hash['interval']);
-
- $month2number = array(
- 'january' => 1,
- 'february' => 2,
- 'march' => 3,
- 'april' => 4,
- 'may' => 5,
- 'june' => 6,
- 'july' => 7,
- 'august' => 8,
- 'september' => 9,
- 'october' => 10,
- 'november' => 11,
- 'december' => 12,
- );
-
- $parse_day = false;
- $set_daymask = false;
- $update_month = false;
- $update_daynumber = false;
- $update_weekday = false;
- $nth_weekday = -1;
-
- switch ($hash['cycle']) {
- case 'daily':
- $this->setRecurType(self::RECUR_DAILY);
- break;
-
- case 'weekly':
- $this->setRecurType(self::RECUR_WEEKLY);
- $parse_day = true;
- $set_daymask = true;
- break;
-
- case 'monthly':
- if (!isset($hash['daynumber'])) {
- $this->setRecurType(self::RECUR_NONE);
- return false;
- }
-
- switch ($hash['type']) {
- case 'daynumber':
- $this->setRecurType(self::RECUR_MONTHLY_DATE);
- $update_daynumber = true;
- break;
-
- case 'weekday':
- $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY);
- $this->setRecurNthWeekday($hash['daynumber']);
- $parse_day = true;
- $set_daymask = true;
- break;
- }
- break;
-
- case 'yearly':
- if (!isset($hash['type'])) {
- $this->setRecurType(self::RECUR_NONE);
- return false;
- }
-
- switch ($hash['type']) {
- case 'monthday':
- $this->setRecurType(self::RECUR_YEARLY_DATE);
- $update_month = true;
- $update_daynumber = true;
- break;
-
- case 'yearday':
- if (!isset($hash['month'])) {
- $this->setRecurType(self::RECUR_NONE);
- return false;
- }
-
- $this->setRecurType(self::RECUR_YEARLY_DAY);
- // Start counting days in January.
- $hash['month'] = 'january';
- $update_month = true;
- $update_daynumber = true;
- break;
-
- case 'weekday':
- if (!isset($hash['daynumber'])) {
- $this->setRecurType(self::RECUR_NONE);
- return false;
- }
-
- $this->setRecurType(self::RECUR_YEARLY_WEEKDAY);
- $this->setRecurNthWeekday($hash['daynumber']);
- $parse_day = true;
- $set_daymask = true;
-
- if ($hash['month'] && isset($month2number[$hash['month']])) {
- $this->setRecurByMonth($month2number[$hash['month']]);
- }
- break;
- }
- }
-
- if (isset($hash['range-type']) && isset($hash['range'])) {
- switch ($hash['range-type']) {
- case 'number':
- $this->setRecurCount((int)$hash['range']);
- break;
-
- case 'date':
- $recur_end = new Horde_Date($hash['range']);
- $recur_end->hour = 23;
- $recur_end->min = 59;
- $recur_end->sec = 59;
- $this->setRecurEnd($recur_end);
- break;
- }
- }
-
- // Need to parse <day>?
- $last_found_day = -1;
- if ($parse_day) {
- if (!isset($hash['day'])) {
- $this->setRecurType(self::RECUR_NONE);
- return false;
- }
-
- $mask = 0;
- $bits = array(
- 'monday' => Horde_Date::MASK_MONDAY,
- 'tuesday' => Horde_Date::MASK_TUESDAY,
- 'wednesday' => Horde_Date::MASK_WEDNESDAY,
- 'thursday' => Horde_Date::MASK_THURSDAY,
- 'friday' => Horde_Date::MASK_FRIDAY,
- 'saturday' => Horde_Date::MASK_SATURDAY,
- 'sunday' => Horde_Date::MASK_SUNDAY,
- );
- $days = array(
- 'monday' => Horde_Date::DATE_MONDAY,
- 'tuesday' => Horde_Date::DATE_TUESDAY,
- 'wednesday' => Horde_Date::DATE_WEDNESDAY,
- 'thursday' => Horde_Date::DATE_THURSDAY,
- 'friday' => Horde_Date::DATE_FRIDAY,
- 'saturday' => Horde_Date::DATE_SATURDAY,
- 'sunday' => Horde_Date::DATE_SUNDAY,
- );
-
- foreach ($hash['day'] as $day) {
- // Validity check.
- if (empty($day) || !isset($bits[$day])) {
- continue;
- }
-
- $mask |= $bits[$day];
- $last_found_day = $days[$day];
- }
-
- if ($set_daymask) {
- $this->setRecurOnDay($mask);
- }
- }
-
- if ($update_month || $update_daynumber || $update_weekday) {
- if ($update_month) {
- if (isset($month2number[$hash['month']])) {
- $this->start->month = $month2number[$hash['month']];
- }
- }
-
- if ($update_daynumber) {
- if (!isset($hash['daynumber'])) {
- $this->setRecurType(self::RECUR_NONE);
- return false;
- }
-
- $this->start->mday = $hash['daynumber'];
- }
-
- if ($update_weekday) {
- $this->setNthWeekday($nth_weekday);
- }
- }
-
- // Exceptions.
- if (isset($hash['exceptions'])) {
- $this->exceptions = $hash['exceptions'];
- }
-
- if (isset($hash['completions'])) {
- $this->completions = $hash['completions'];
- }
-
- return true;
- }
-
- /**
- * Export this object into a hash.
- *
- * @return array The recurrence hash.
- */
- public function toHash()
- {
- if ($this->getRecurType() == self::RECUR_NONE) {
- return array();
- }
-
- $day2number = array(
- 0 => 'sunday',
- 1 => 'monday',
- 2 => 'tuesday',
- 3 => 'wednesday',
- 4 => 'thursday',
- 5 => 'friday',
- 6 => 'saturday'
- );
- $month2number = array(
- 1 => 'january',
- 2 => 'february',
- 3 => 'march',
- 4 => 'april',
- 5 => 'may',
- 6 => 'june',
- 7 => 'july',
- 8 => 'august',
- 9 => 'september',
- 10 => 'october',
- 11 => 'november',
- 12 => 'december'
- );
-
- $hash = array('interval' => $this->getRecurInterval());
- $start = $this->getRecurStart();
-
- switch ($this->getRecurType()) {
- case self::RECUR_DAILY:
- $hash['cycle'] = 'daily';
- break;
-
- case self::RECUR_WEEKLY:
- $hash['cycle'] = 'weekly';
- $bits = array(
- 'monday' => Horde_Date::MASK_MONDAY,
- 'tuesday' => Horde_Date::MASK_TUESDAY,
- 'wednesday' => Horde_Date::MASK_WEDNESDAY,
- 'thursday' => Horde_Date::MASK_THURSDAY,
- 'friday' => Horde_Date::MASK_FRIDAY,
- 'saturday' => Horde_Date::MASK_SATURDAY,
- 'sunday' => Horde_Date::MASK_SUNDAY,
- );
- $days = array();
- foreach ($bits as $name => $bit) {
- if ($this->recurOnDay($bit)) {
- $days[] = $name;
- }
- }
- $hash['day'] = $days;
- break;
-
- case self::RECUR_MONTHLY_DATE:
- $hash['cycle'] = 'monthly';
- $hash['type'] = 'daynumber';
- $hash['daynumber'] = $start->mday;
- break;
-
- case self::RECUR_MONTHLY_WEEKDAY:
- $hash['cycle'] = 'monthly';
- $hash['type'] = 'weekday';
- $hash['daynumber'] = $start->weekOfMonth();
- $hash['day'] = array ($day2number[$start->dayOfWeek()]);
- break;
-
- case self::RECUR_YEARLY_DATE:
- $hash['cycle'] = 'yearly';
- $hash['type'] = 'monthday';
- $hash['daynumber'] = $start->mday;
- $hash['month'] = $month2number[$start->month];
- break;
-
- case self::RECUR_YEARLY_DAY:
- $hash['cycle'] = 'yearly';
- $hash['type'] = 'yearday';
- $hash['daynumber'] = $start->dayOfYear();
- break;
-
- case self::RECUR_YEARLY_WEEKDAY:
- $hash['cycle'] = 'yearly';
- $hash['type'] = 'weekday';
- $hash['daynumber'] = $start->weekOfMonth();
- $hash['day'] = array ($day2number[$start->dayOfWeek()]);
- $hash['month'] = $month2number[$start->month];
- }
-
- if ($this->hasRecurCount()) {
- $hash['range-type'] = 'number';
- $hash['range'] = $this->getRecurCount();
- } elseif ($this->hasRecurEnd()) {
- $date = $this->getRecurEnd();
- $hash['range-type'] = 'date';
- $hash['range'] = $date->datestamp();
- } else {
- $hash['range-type'] = 'none';
- $hash['range'] = '';
- }
-
- // Recurrence exceptions
- $hash['exceptions'] = $this->exceptions;
- $hash['completions'] = $this->completions;
-
- return $hash;
- }
-
- /**
- * Returns a simple object suitable for json transport representing this
- * object.
- *
- * Possible properties are:
- * - t: type
- * - i: interval
- * - e: end date
- * - c: count
- * - d: data
- * - co: completions
- * - ex: exceptions
- *
- * @return object A simple object.
- */
- public function toJson()
- {
- $json = new stdClass;
- $json->t = $this->recurType;
- $json->i = $this->recurInterval;
- if ($this->hasRecurEnd()) {
- $json->e = $this->recurEnd->toJson();
- }
- if ($this->recurCount) {
- $json->c = $this->recurCount;
- }
- if ($this->recurData) {
- $json->d = $this->recurData;
- }
- if ($this->completions) {
- $json->co = $this->completions;
- }
- if ($this->exceptions) {
- $json->ex = $this->exceptions;
- }
- return $json;
- }
-
-}
diff --git a/lib/plugins/libkolab/lib/Horde_Kolab_Format_XML_configuration.php b/lib/plugins/libkolab/lib/Horde_Kolab_Format_XML_configuration.php
deleted file mode 100644
index c80fbd3..0000000
--- a/lib/plugins/libkolab/lib/Horde_Kolab_Format_XML_configuration.php
+++ /dev/null
@@ -1,76 +0,0 @@
-<?php
-
-/**
- * Kolab XML handler for configuration (KEP:9).
- *
- * @author Aleksander Machniak <machniak@kolabsys.com>
- *
- * Copyright (C) 2011, 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/>.
- */
-class Horde_Kolab_Format_XML_configuration extends Horde_Kolab_Format_XML {
- /**
- * Specific data fields for the configuration object
- *
- * @var Kolab
- */
- var $_fields_specific;
-
- var $_root_version = 2.1;
-
- /**
- * Constructor
- */
- function Horde_Kolab_Format_XML_configuration($params = array())
- {
- $this->_root_name = 'configuration';
-
- // Specific configuration fields, in kolab format specification order
- $this->_fields_specific = array(
- 'application' => array (
- 'type' => HORDE_KOLAB_XML_TYPE_STRING,
- 'value' => HORDE_KOLAB_XML_VALUE_MAYBE_MISSING,
- ),
- 'type' => array(
- 'type' => HORDE_KOLAB_XML_TYPE_STRING,
- 'value' => HORDE_KOLAB_XML_VALUE_NOT_EMPTY,
- ),
- );
-
- // Dictionary fields
- if (!empty($params['subtype']) && preg_match('/^dictionary.*/', $params['subtype'])) {
- $this->_fields_specific = array_merge($this->_fields_specific, array(
- 'language' => array (
- 'type' => HORDE_KOLAB_XML_TYPE_STRING,
- 'value' => HORDE_KOLAB_XML_VALUE_NOT_EMPTY,
- ),
- 'e' => array(
- 'type' => HORDE_KOLAB_XML_TYPE_MULTIPLE,
- 'value' => HORDE_KOLAB_XML_VALUE_NOT_EMPTY,
- 'array' => array(
- 'type' => HORDE_KOLAB_XML_TYPE_STRING,
- 'value' => HORDE_KOLAB_XML_VALUE_NOT_EMPTY,
- ),
- ),
- ));
- }
-
- parent::Horde_Kolab_Format_XML($params);
-
- unset($this->_fields_basic['body']);
- unset($this->_fields_basic['categories']);
- unset($this->_fields_basic['sensitivity']);
- }
-}
diff --git a/lib/plugins/libkolab/lib/kolab_format_configuration.php b/lib/plugins/libkolab/lib/kolab_format_configuration.php
index 104d90f..5a8d3ff 100644
--- a/lib/plugins/libkolab/lib/kolab_format_configuration.php
+++ b/lib/plugins/libkolab/lib/kolab_format_configuration.php
@@ -1,144 +1,139 @@
<?php
/**
* Kolab Configuration data model class
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, 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/>.
*/
class kolab_format_configuration extends kolab_format
{
public $CTYPE = 'application/x-vnd.kolab.configuration';
public $CTYPEv2 = 'application/x-vnd.kolab.configuration';
protected $objclass = 'Configuration';
protected $read_func = 'readConfiguration';
protected $write_func = 'writeConfiguration';
private $type_map = array(
'dictionary' => Configuration::TypeDictionary,
'category' => Configuration::TypeCategoryColor,
);
/**
* Set properties to the kolabformat object
*
* @param array Object data as hash array
*/
public function set(&$object)
{
- $this->init();
+ // set common object properties
+ parent::set($object);
// read type-specific properties
switch ($object['type']) {
case 'dictionary':
$dict = new Dictionary($object['language']);
$dict->setEntries(self::array2vector($object['e']));
$this->obj = new Configuration($dict);
break;
case 'category':
// TODO: implement this
$categories = new vectorcategorycolor;
$this->obj = new Configuration($categories);
break;
default:
return false;
}
- // set some automatic values if missing
- if (!empty($object['uid']))
- $this->obj->setUid($object['uid']);
- if (!empty($object['created']))
- $this->obj->setCreated(self::get_datetime($object['created']));
-
// adjust content-type string
$this->CTYPE = $this->CTYPEv2 = 'application/x-vnd.kolab.configuration.' . $object['type'];
// cache this data
$this->data = $object;
unset($this->data['_formatobj']);
}
/**
*
*/
public function is_valid()
{
return $this->data || (is_object($this->obj) && $this->obj->isValid());
}
/**
* Convert the Configuration object into a hash array data structure
*
* @param array Additional data for merge
*
* @return array Config object data as hash array
*/
public function to_array($data = array())
{
// return cached result
if (!empty($this->data))
return $this->data;
// read common object props into local data object
$object = parent::to_array($data);
$type_map = array_flip($this->type_map);
$object['type'] = $type_map[$this->obj->type()];
// read type-specific properties
switch ($object['type']) {
case 'dictionary':
$dict = $this->obj->dictionary();
$object['language'] = $dict->language();
$object['e'] = self::vector2array($dict->entries());
break;
case 'category':
// TODO: implement this
break;
}
// adjust content-type string
if ($object['type'])
$this->CTYPE = $this->CTYPEv2 = 'application/x-vnd.kolab.configuration.' . $object['type'];
$this->data = $object;
return $this->data;
}
/**
* Callback for kolab_storage_cache to get object specific tags to cache
*
* @return array List of tags to save in cache
*/
public function get_tags()
{
$tags = array();
if ($this->data['type'] == 'dictionary')
$tags = array($this->data['language']);
return $tags;
}
}
diff --git a/lib/plugins/libkolab/lib/kolab_format_xcal.php b/lib/plugins/libkolab/lib/kolab_format_xcal.php
index 24f8237..ff10a10 100644
--- a/lib/plugins/libkolab/lib/kolab_format_xcal.php
+++ b/lib/plugins/libkolab/lib/kolab_format_xcal.php
@@ -1,409 +1,411 @@
<?php
/**
* Xcal based Kolab format class wrapping libkolabxml bindings
*
* Base class for xcal-based Kolab groupware objects such as event, todo, journal
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, 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/>.
*/
abstract class kolab_format_xcal extends kolab_format
{
public $CTYPE = 'application/calendar+xml';
public static $fulltext_cols = array('title', 'description', 'location', 'attendees:name', 'attendees:email', 'categories');
protected $sensitivity_map = array(
'public' => kolabformat::ClassPublic,
'private' => kolabformat::ClassPrivate,
'confidential' => kolabformat::ClassConfidential,
);
protected $role_map = array(
'REQ-PARTICIPANT' => kolabformat::Required,
'OPT-PARTICIPANT' => kolabformat::Optional,
'NON-PARTICIPANT' => kolabformat::NonParticipant,
'CHAIR' => kolabformat::Chair,
);
protected $cutype_map = array(
'INDIVIDUAL' => kolabformat::CutypeIndividual,
'GROUP' => kolabformat::CutypeGroup,
'ROOM' => kolabformat::CutypeRoom,
'RESOURCE' => kolabformat::CutypeResource,
'UNKNOWN' => kolabformat::CutypeUnknown,
);
protected $rrule_type_map = array(
'MINUTELY' => RecurrenceRule::Minutely,
'HOURLY' => RecurrenceRule::Hourly,
'DAILY' => RecurrenceRule::Daily,
'WEEKLY' => RecurrenceRule::Weekly,
'MONTHLY' => RecurrenceRule::Monthly,
'YEARLY' => RecurrenceRule::Yearly,
);
protected $weekday_map = array(
'MO' => kolabformat::Monday,
'TU' => kolabformat::Tuesday,
'WE' => kolabformat::Wednesday,
'TH' => kolabformat::Thursday,
'FR' => kolabformat::Friday,
'SA' => kolabformat::Saturday,
'SU' => kolabformat::Sunday,
);
protected $alarm_type_map = array(
'DISPLAY' => Alarm::DisplayAlarm,
'EMAIL' => Alarm::EMailAlarm,
'AUDIO' => Alarm::AudioAlarm,
);
private $status_map = array(
'NEEDS-ACTION' => kolabformat::StatusNeedsAction,
'IN-PROCESS' => kolabformat::StatusInProcess,
'COMPLETED' => kolabformat::StatusCompleted,
'CANCELLED' => kolabformat::StatusCancelled,
);
protected $part_status_map = array(
'UNKNOWN' => kolabformat::PartNeedsAction,
'NEEDS-ACTION' => kolabformat::PartNeedsAction,
'TENTATIVE' => kolabformat::PartTentative,
'ACCEPTED' => kolabformat::PartAccepted,
'DECLINED' => kolabformat::PartDeclined,
'DELEGATED' => kolabformat::PartDelegated,
);
/**
* Convert common xcard properties into a hash array data structure
*
* @param array Additional data for merge
*
* @return array Object data as hash array
*/
public function to_array($data = array())
{
// read common object props
$object = parent::to_array($data);
$status_map = array_flip($this->status_map);
$sensitivity_map = array_flip($this->sensitivity_map);
$object += array(
'sequence' => intval($this->obj->sequence()),
'title' => $this->obj->summary(),
'location' => $this->obj->location(),
'description' => $this->obj->description(),
'url' => $this->obj->url(),
'status' => $status_map[$this->obj->status()],
'sensitivity' => $sensitivity_map[$this->obj->classification()],
'priority' => $this->obj->priority(),
'categories' => self::vector2array($this->obj->categories()),
'start' => self::php_datetime($this->obj->start()),
);
// read organizer and attendees
if (($organizer = $this->obj->organizer()) && ($organizer->email() || $organizer->name())) {
$object['organizer'] = array(
'email' => $organizer->email(),
'name' => $organizer->name(),
);
}
$role_map = array_flip($this->role_map);
$cutype_map = array_flip($this->cutype_map);
$part_status_map = array_flip($this->part_status_map);
$attvec = $this->obj->attendees();
for ($i=0; $i < $attvec->size(); $i++) {
$attendee = $attvec->get($i);
$cr = $attendee->contact();
if ($cr->email() != $object['organizer']['email']) {
$object['attendees'][] = array(
'role' => $role_map[$attendee->role()],
'cutype' => $cutype_map[$attendee->cutype()],
'status' => $part_status_map[$attendee->partStat()],
'rsvp' => $attendee->rsvp(),
'email' => $cr->email(),
'name' => $cr->name(),
);
}
}
// read recurrence rule
if (($rr = $this->obj->recurrenceRule()) && $rr->isValid()) {
$rrule_type_map = array_flip($this->rrule_type_map);
$object['recurrence'] = array('FREQ' => $rrule_type_map[$rr->frequency()]);
if ($intvl = $rr->interval())
$object['recurrence']['INTERVAL'] = $intvl;
if (($count = $rr->count()) && $count > 0) {
$object['recurrence']['COUNT'] = $count;
}
else if ($until = self::php_datetime($rr->end())) {
$until->setTime($object['start']->format('G'), $object['start']->format('i'), 0);
$object['recurrence']['UNTIL'] = $until;
}
if (($byday = $rr->byday()) && $byday->size()) {
$weekday_map = array_flip($this->weekday_map);
$weekdays = array();
for ($i=0; $i < $byday->size(); $i++) {
$daypos = $byday->get($i);
$prefix = $daypos->occurence();
$weekdays[] = ($prefix ? $prefix : '') . $weekday_map[$daypos->weekday()];
}
$object['recurrence']['BYDAY'] = join(',', $weekdays);
}
if (($bymday = $rr->bymonthday()) && $bymday->size()) {
$object['recurrence']['BYMONTHDAY'] = join(',', self::vector2array($bymday));
}
if (($bymonth = $rr->bymonth()) && $bymonth->size()) {
$object['recurrence']['BYMONTH'] = join(',', self::vector2array($bymonth));
}
if ($exdates = $this->obj->exceptionDates()) {
for ($i=0; $i < $exdates->size(); $i++) {
if ($exdate = self::php_datetime($exdates->get($i)))
$object['recurrence']['EXDATE'][] = $exdate;
}
}
}
// read alarm
$valarms = $this->obj->alarms();
$alarm_types = array_flip($this->alarm_type_map);
for ($i=0; $i < $valarms->size(); $i++) {
$alarm = $valarms->get($i);
$type = $alarm_types[$alarm->type()];
if ($type == 'DISPLAY' || $type == 'EMAIL') { // only DISPLAY and EMAIL alarms are supported
if ($start = self::php_datetime($alarm->start())) {
$object['alarms'] = '@' . $start->format('U');
}
else if ($offset = $alarm->relativeStart()) {
$value = $alarm->relativeTo() == kolabformat::End ? '+' : '-';
if ($w = $offset->weeks()) $value .= $w . 'W';
else if ($d = $offset->days()) $value .= $d . 'D';
else if ($h = $offset->hours()) $value .= $h . 'H';
else if ($m = $offset->minutes()) $value .= $m . 'M';
else if ($s = $offset->seconds()) $value .= $s . 'S';
else continue;
$object['alarms'] = $value;
}
$object['alarms'] .= ':' . $type;
break;
}
}
return $object;
}
/**
* Set common xcal properties to the kolabformat object
*
* @param array Event data as hash array
*/
public function set(&$object)
{
$this->init();
$is_new = !$this->obj->uid();
// set common object properties
parent::set($object);
// increment sequence on updates
$object['sequence'] = !$is_new ? $this->obj->sequence()+1 : 0;
$this->obj->setSequence($object['sequence']);
$this->obj->setSummary($object['title']);
$this->obj->setLocation($object['location']);
$this->obj->setDescription($object['description']);
$this->obj->setPriority($object['priority']);
$this->obj->setClassification($this->sensitivity_map[$object['sensitivity']]);
$this->obj->setCategories(self::array2vector($object['categories']));
$this->obj->setUrl(strval($object['url']));
// process event attendees
$attendees = new vectorattendee;
foreach ((array)$object['attendees'] as $attendee) {
if ($attendee['role'] == 'ORGANIZER') {
$object['organizer'] = $attendee;
}
else if ($attendee['email'] != $object['organizer']['email']) {
$cr = new ContactReference(ContactReference::EmailReference, $attendee['email']);
$cr->setName($attendee['name']);
$att = new Attendee;
$att->setContact($cr);
$att->setPartStat($this->part_status_map[$attendee['status']]);
$att->setRole($this->role_map[$attendee['role']] ? $this->role_map[$attendee['role']] : kolabformat::Required);
$att->setCutype($this->cutype_map[$attendee['cutype']] ? $this->cutype_map[$attendee['cutype']] : kolabformat::CutypeIndividual);
$att->setRSVP((bool)$attendee['rsvp']);
if ($att->isValid()) {
$attendees->push($att);
}
else {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Invalid event attendee: " . json_encode($attendee),
), true);
}
}
}
$this->obj->setAttendees($attendees);
if ($object['organizer']) {
$organizer = new ContactReference(ContactReference::EmailReference, $object['organizer']['email']);
$organizer->setName($object['organizer']['name']);
$this->obj->setOrganizer($organizer);
}
// save recurrence rule
+ $rr = new RecurrenceRule;
+ $rr->setFrequency(RecurrenceRule::FreqNone);
+
if ($object['recurrence']) {
- $rr = new RecurrenceRule;
$rr->setFrequency($this->rrule_type_map[$object['recurrence']['FREQ']]);
if ($object['recurrence']['INTERVAL'])
$rr->setInterval(intval($object['recurrence']['INTERVAL']));
if ($object['recurrence']['BYDAY']) {
$byday = new vectordaypos;
foreach (explode(',', $object['recurrence']['BYDAY']) as $day) {
$occurrence = 0;
if (preg_match('/^([\d-]+)([A-Z]+)$/', $day, $m)) {
$occurrence = intval($m[1]);
$day = $m[2];
}
if (isset($this->weekday_map[$day]))
$byday->push(new DayPos($occurrence, $this->weekday_map[$day]));
}
$rr->setByday($byday);
}
if ($object['recurrence']['BYMONTHDAY']) {
$bymday = new vectori;
foreach (explode(',', $object['recurrence']['BYMONTHDAY']) as $day)
$bymday->push(intval($day));
$rr->setBymonthday($bymday);
}
if ($object['recurrence']['BYMONTH']) {
$bymonth = new vectori;
foreach (explode(',', $object['recurrence']['BYMONTH']) as $month)
$bymonth->push(intval($month));
$rr->setBymonth($bymonth);
}
if ($object['recurrence']['COUNT'])
$rr->setCount(intval($object['recurrence']['COUNT']));
else if ($object['recurrence']['UNTIL'])
$rr->setEnd(self::get_datetime($object['recurrence']['UNTIL'], null, true));
if ($rr->isValid()) {
- $this->obj->setRecurrenceRule($rr);
-
// add exception dates (only if recurrence rule is valid)
$exdates = new vectordatetime;
foreach ((array)$object['recurrence']['EXDATE'] as $exdate)
$exdates->push(self::get_datetime($exdate, null, true));
$this->obj->setExceptionDates($exdates);
}
else {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Invalid event recurrence rule: " . json_encode($object['recurrence']),
), true);
}
}
+ $this->obj->setRecurrenceRule($rr);
+
// save alarm
$valarms = new vectoralarm;
if ($object['alarms']) {
list($offset, $type) = explode(":", $object['alarms']);
if ($type == 'EMAIL') { // email alarms implicitly go to event owner
$recipients = new vectorcontactref;
$recipients->push(new ContactReference(ContactReference::EmailReference, $object['_owner']));
$alarm = new Alarm($object['title'], strval($object['description']), $recipients);
}
else { // default: display alarm
$alarm = new Alarm($object['title']);
}
if (preg_match('/^@(\d+)/', $offset, $d)) {
$alarm->setStart(self::get_datetime($d[1], new DateTimeZone('UTC')));
}
else if (preg_match('/^([-+]?)(\d+)([SMHDW])/', $offset, $d)) {
$days = $hours = $minutes = $seconds = 0;
switch ($d[3]) {
case 'W': $days = 7*intval($d[2]); break;
case 'D': $days = intval($d[2]); break;
case 'H': $hours = intval($d[2]); break;
case 'M': $minutes = intval($d[2]); break;
case 'S': $seconds = intval($d[2]); break;
}
$alarm->setRelativeStart(new Duration($days, $hours, $minutes, $seconds, $d[1] == '-'), $d[1] == '-' ? kolabformat::Start : kolabformat::End);
}
$valarms->push($alarm);
}
$this->obj->setAlarms($valarms);
}
/**
* Callback for kolab_storage_cache to get words to index for fulltext search
*
* @return array List of words to save in cache
*/
public function get_words()
{
$data = '';
foreach (self::$fulltext_cols as $colname) {
list($col, $field) = explode(':', $colname);
if ($field) {
$a = array();
foreach ((array)$this->data[$col] as $attr)
$a[] = $attr[$field];
$val = join(' ', $a);
}
else {
$val = is_array($this->data[$col]) ? join(' ', $this->data[$col]) : $this->data[$col];
}
if (strlen($val))
$data .= $val . ' ';
}
return array_unique(rcube_utils::normalize_string($data, true));
}
}
\ No newline at end of file
diff --git a/lib/plugins/libkolab/lib/kolab_storage.php b/lib/plugins/libkolab/lib/kolab_storage.php
index 4205cf3..2f3cdc5 100644
--- a/lib/plugins/libkolab/lib/kolab_storage.php
+++ b/lib/plugins/libkolab/lib/kolab_storage.php
@@ -1,863 +1,884 @@
<?php
/**
* Kolab storage class providing static methods to access groupware objects on a Kolab server.
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* Copyright (C) 2012, 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/>.
*/
class kolab_storage
{
const CTYPE_KEY = '/shared/vendor/kolab/folder-type';
const CTYPE_KEY_PRIVATE = '/private/vendor/kolab/folder-type';
const COLOR_KEY_SHARED = '/shared/vendor/kolab/color';
const COLOR_KEY_PRIVATE = '/private/vendor/kolab/color';
const NAME_KEY_SHARED = '/shared/vendor/kolab/displayname';
const NAME_KEY_PRIVATE = '/private/vendor/kolab/displayname';
public static $version = '3.0';
public static $last_error;
private static $ready = false;
private static $subscriptions;
private static $states;
private static $config;
private static $imap;
/**
* Setup the environment needed by the libs
*/
public static function setup()
{
if (self::$ready)
return true;
$rcmail = rcube::get_instance();
self::$config = $rcmail->config;
self::$version = strval($rcmail->config->get('kolab_format_version', self::$version));
self::$imap = $rcmail->get_storage();
self::$ready = class_exists('kolabformat') &&
(self::$imap->get_capability('METADATA') || self::$imap->get_capability('ANNOTATEMORE') || self::$imap->get_capability('ANNOTATEMORE2'));
if (self::$ready) {
// set imap options
self::$imap->set_options(array(
'skip_deleted' => true,
'threading' => false,
));
self::$imap->set_pagesize(9999);
}
else if (!class_exists('kolabformat')) {
rcube::raise_error(array(
'code' => 900, 'type' => 'php',
'message' => "required kolabformat module not found"
), true);
}
else {
rcube::raise_error(array(
'code' => 900, 'type' => 'php',
'message' => "IMAP server doesn't support METADATA or ANNOTATEMORE"
), true);
}
return self::$ready;
}
/**
* Get a list of storage folders for the given data type
*
* @param string Data type to list folders for (contact,distribution-list,event,task,note)
*
* @return array List of Kolab_Folder objects (folder names in UTF7-IMAP)
*/
public static function get_folders($type)
{
$folders = $folderdata = array();
if (self::setup()) {
foreach ((array)self::list_folders('', '*', $type, null, $folderdata) as $foldername) {
$folders[$foldername] = new kolab_storage_folder($foldername, $folderdata[$foldername]);
}
}
return $folders;
}
/**
* Getter for the storage folder for the given type
*
* @param string Data type to list folders for (contact,distribution-list,event,task,note)
* @return object kolab_storage_folder The folder object
*/
public static function get_default_folder($type)
{
if (self::setup()) {
foreach ((array)self::list_folders('', '*', $type . '.default', false, $folderdata) as $foldername) {
return new kolab_storage_folder($foldername, $folderdata[$foldername]);
}
}
return null;
}
/**
* Getter for a specific storage folder
*
* @param string IMAP folder to access (UTF7-IMAP)
* @return object kolab_storage_folder The folder object
*/
public static function get_folder($folder)
{
return self::setup() ? new kolab_storage_folder($folder) : null;
}
/**
* Getter for a single Kolab object, identified by its UID.
* This will search all folders storing objects of the given type.
*
* @param string Object UID
* @param string Object type (contact,distribution-list,event,task,note)
* @return array The Kolab object represented as hash array or false if not found
*/
public static function get_object($uid, $type)
{
self::setup();
$folder = null;
foreach ((array)self::list_folders('', '*', $type) as $foldername) {
if (!$folder)
$folder = new kolab_storage_folder($foldername);
else
$folder->set_folder($foldername);
if ($object = $folder->get_object($uid))
return $object;
}
return false;
}
/**
*
*/
public static function get_freebusy_server()
{
return unslashify(self::$config->get('kolab_freebusy_server', 'https://' . $_SESSION['imap_host'] . '/freebusy'));
}
/**
* Compose an URL to query the free/busy status for the given user
*/
public static function get_freebusy_url($email)
{
return self::get_freebusy_server() . '/' . $email . '.ifb';
}
/**
* Creates folder ID from folder name
*
* @param string $folder Folder name (UTF7-IMAP)
*
* @return string Folder ID string
*/
public static function folder_id($folder)
{
return asciiwords(strtr($folder, '/.-', '___'));
}
/**
* Deletes IMAP folder
*
* @param string $name Folder name (UTF7-IMAP)
*
* @return bool True on success, false on failure
*/
public static function folder_delete($name)
{
// clear cached entries first
if ($folder = self::get_folder($name))
$folder->cache->purge();
$success = self::$imap->delete_folder($name);
self::$last_error = self::$imap->get_error_str();
return $success;
}
/**
* Creates IMAP folder
*
* @param string $name Folder name (UTF7-IMAP)
* @param string $type Folder type
* @param bool $subscribed Sets folder subscription
* @param bool $active Sets folder state (client-side subscription)
*
* @return bool True on success, false on failure
*/
public static function folder_create($name, $type = null, $subscribed = false, $active = false)
{
self::setup();
if ($saved = self::$imap->create_folder($name, $subscribed)) {
// set metadata for folder type
if ($type) {
$saved = self::set_folder_type($name, $type);
// revert if metadata could not be set
if (!$saved) {
self::$imap->delete_folder($name);
}
// activate folder
else if ($active) {
self::set_state($name, true);
}
}
}
if ($saved) {
return true;
}
self::$last_error = self::$imap->get_error_str();
return false;
}
/**
* Renames IMAP folder
*
* @param string $oldname Old folder name (UTF7-IMAP)
* @param string $newname New folder name (UTF7-IMAP)
*
* @return bool True on success, false on failure
*/
public static function folder_rename($oldname, $newname)
{
self::setup();
$success = self::$imap->rename_folder($oldname, $newname);
self::$last_error = self::$imap->get_error_str();
return $success;
}
/**
* Rename or Create a new IMAP folder.
*
* Does additional checks for permissions and folder name restrictions
*
* @param array Hash array with folder properties and metadata
* - name: Folder name
* - oldname: Old folder name when changed
* - parent: Parent folder to create the new one in
* - type: Folder type to create
* - subscribed: Subscribed flag (IMAP subscription)
* - active: Activation flag (client-side subscription)
* @return mixed New folder name or False on failure
*/
public static function folder_update(&$prop)
{
self::setup();
$folder = rcube_charset::convert($prop['name'], RCUBE_CHARSET, 'UTF7-IMAP');
$oldfolder = $prop['oldname']; // UTF7
$parent = $prop['parent']; // UTF7
$delimiter = self::$imap->get_hierarchy_delimiter();
if (strlen($oldfolder)) {
$options = self::$imap->folder_info($oldfolder);
}
if (!empty($options) && ($options['norename'] || $options['protected'])) {
}
// sanity checks (from steps/settings/save_folder.inc)
else if (!strlen($folder)) {
self::$last_error = 'cannotbeempty';
return false;
}
else if (strlen($folder) > 128) {
self::$last_error = 'nametoolong';
return false;
}
else {
// these characters are problematic e.g. when used in LIST/LSUB
foreach (array($delimiter, '%', '*') as $char) {
if (strpos($folder, $char) !== false) {
self::$last_error = 'forbiddencharacter';
return false;
}
}
}
if (!empty($options) && ($options['protected'] || $options['norename'])) {
$folder = $oldfolder;
}
else if (strlen($parent)) {
$folder = $parent . $delimiter . $folder;
}
else {
// add namespace prefix (when needed)
$folder = self::$imap->mod_folder($folder, 'in');
}
// Check access rights to the parent folder
if (strlen($parent) && (!strlen($oldfolder) || $oldfolder != $folder)) {
$parent_opts = self::$imap->folder_info($parent);
if ($parent_opts['namespace'] != 'personal'
&& (empty($parent_opts['rights']) || !preg_match('/[ck]/', implode($parent_opts['rights'])))
) {
self::$last_error = 'No permission to create folder';
return false;
}
}
// update the folder name
if (strlen($oldfolder)) {
if ($oldfolder != $folder) {
$result = self::folder_rename($oldfolder, $folder);
}
else
$result = true;
}
// create new folder
else {
$result = self::folder_create($folder, $prop['type'], $prop['subscribed'], $prop['active']);
}
// save displayname and color in METADATA
// TODO: also save 'showalarams' and other properties here
if ($result) {
$ns = null;
foreach (array('color' => array(self::COLOR_KEY_SHARED,self::COLOR_KEY_PRIVATE),
'displayname' => array(self::NAME_KEY_SHARED,self::NAME_KEY_PRIVATE)) as $key => $metakeys) {
if (!empty($prop[$key])) {
if (!isset($ns))
$ns = self::$imap->folder_namespace($folder);
$meta_saved = false;
if ($ns == 'personal') // save in shared namespace for personal folders
$meta_saved = self::$imap->set_metadata($folder, array($metakeys[0] => $prop[$key]));
if (!$meta_saved) // try in private namespace
$meta_saved = self::$imap->set_metadata($folder, array($metakeys[1] => $prop[$key]));
if ($meta_saved)
unset($prop[$key]); // unsetting will prevent fallback to local user prefs
}
}
}
return $result ? $folder : false;
}
/**
* Getter for human-readable name of Kolab object (folder)
* See http://wiki.kolab.org/UI-Concepts/Folder-Listing for reference
*
* @param string $folder IMAP folder name (UTF7-IMAP)
* @param string $folder_ns Will be set to namespace name of the folder
*
* @return string Name of the folder-object
*/
public static function object_name($folder, &$folder_ns=null)
{
self::setup();
// find custom display name in folder METADATA
$metadata = self::$imap->get_metadata($folder, array(self::NAME_KEY_PRIVATE, self::NAME_KEY_SHARED));
if (($name = $metadata[$folder][self::NAME_KEY_PRIVATE]) || ($name = $metadata[$folder][self::NAME_KEY_SHARED])) {
return $name;
}
$found = false;
$namespace = self::$imap->get_namespace();
if (!empty($namespace['shared'])) {
foreach ($namespace['shared'] as $ns) {
if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) {
$prefix = '';
$folder = substr($folder, strlen($ns[0]));
$delim = $ns[1];
$found = true;
$folder_ns = 'shared';
break;
}
}
}
if (!$found && !empty($namespace['other'])) {
foreach ($namespace['other'] as $ns) {
if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) {
// remove namespace prefix
$folder = substr($folder, strlen($ns[0]));
$delim = $ns[1];
// get username
$pos = strpos($folder, $delim);
if ($pos) {
$prefix = '('.substr($folder, 0, $pos).') ';
$folder = substr($folder, $pos+1);
}
else {
$prefix = '('.$folder.')';
$folder = '';
}
$found = true;
$folder_ns = 'other';
break;
}
}
}
if (!$found && !empty($namespace['personal'])) {
foreach ($namespace['personal'] as $ns) {
if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) {
// remove namespace prefix
$folder = substr($folder, strlen($ns[0]));
$prefix = '';
$delim = $ns[1];
$found = true;
break;
}
}
}
if (empty($delim))
$delim = self::$imap->get_hierarchy_delimiter();
$folder = rcube_charset::convert($folder, 'UTF7-IMAP');
$folder = html::quote($folder);
$folder = str_replace(html::quote($delim), ' &raquo; ', $folder);
if ($prefix)
$folder = html::quote($prefix) . ' ' . $folder;
if (!$folder_ns)
$folder_ns = 'personal';
return $folder;
}
/**
* Helper method to generate a truncated folder name to display
*/
public static function folder_displayname($origname, &$names)
{
$name = $origname;
// find folder prefix to truncate
for ($i = count($names)-1; $i >= 0; $i--) {
if (strpos($name, $names[$i] . ' &raquo; ') === 0) {
$length = strlen($names[$i] . ' &raquo; ');
$prefix = substr($name, 0, $length);
$count = count(explode(' &raquo; ', $prefix));
$name = str_repeat('&nbsp;&nbsp;', $count-1) . '&raquo; ' . substr($name, $length);
break;
}
}
$names[] = $origname;
return $name;
}
/**
* Creates a SELECT field with folders list
*
* @param string $type Folder type
* @param array $attrs SELECT field attributes (e.g. name)
* @param string $current The name of current folder (to skip it)
*
* @return html_select SELECT object
*/
public static function folder_selector($type, $attrs, $current = '')
{
// get all folders of specified type
$folders = self::get_folders($type);
$delim = self::$imap->get_hierarchy_delimiter();
$names = array();
$len = strlen($current);
if ($len && ($rpos = strrpos($current, $delim))) {
$parent = substr($current, 0, $rpos);
$p_len = strlen($parent);
}
// Filter folders list
foreach ($folders as $c_folder) {
$name = $c_folder->name;
// skip current folder and it's subfolders
if ($len && ($name == $current || strpos($name, $current.$delim) === 0)) {
continue;
}
// always show the parent of current folder
if ($p_len && $name == $parent) { }
// skip folders where user have no rights to create subfolders
else if ($c_folder->get_owner() != $_SESSION['username']) {
$rights = $c_folder->get_myrights();
if (!preg_match('/[ck]/', $rights)) {
continue;
}
}
$names[$name] = self::object_name($name);
}
// Make sure parent folder is listed (might be skipped e.g. if it's namespace root)
if ($p_len && !isset($names[$parent])) {
$names[$parent] = self::object_name($parent);
}
// Sort folders list
asort($names, SORT_LOCALE_STRING);
// Build SELECT field of parent folder
$attrs['is_escaped'] = true;
$select = new html_select($attrs);
$select->add('---', '');
$listnames = array();
foreach (array_keys($names) as $imap_name) {
$name = $origname = $names[$imap_name];
// find folder prefix to truncate
for ($i = count($listnames)-1; $i >= 0; $i--) {
if (strpos($name, $listnames[$i].' &raquo; ') === 0) {
$length = strlen($listnames[$i].' &raquo; ');
$prefix = substr($name, 0, $length);
$count = count(explode(' &raquo; ', $prefix));
$name = str_repeat('&nbsp;&nbsp;', $count-1) . '&raquo; ' . substr($name, $length);
break;
}
}
$listnames[] = $origname;
$select->add($name, $imap_name);
}
return $select;
}
/**
* Returns a list of folder names
*
* @param string Optional root folder
* @param string Optional name pattern
* @param string Data type to list folders for (contact,distribution-list,event,task,note,mail)
* @param boolean Enable to return subscribed folders only (null to use configured subscription mode)
* @param array Will be filled with folder-types data
*
* @return array List of folders
*/
public static function list_folders($root = '', $mbox = '*', $filter = null, $subscribed = null, &$folderdata = array())
{
if (!self::setup()) {
return null;
}
// use IMAP subscriptions
if ($subscribed === null && self::$config->get('kolab_use_subscriptions')) {
$subscribed = true;
}
if (!$filter) {
// Get ALL folders list, standard way
if ($subscribed) {
return self::$imap->list_folders_subscribed($root, $mbox);
}
else {
return self::$imap->list_folders($root, $mbox);
}
}
$prefix = $root . $mbox;
+ $regexp = '/^' . preg_quote($filter, '/') . '(\..+)?$/';
// get folders types
- $folderdata = self::$imap->get_metadata($prefix, array(self::CTYPE_KEY, self::CTYPE_KEY_PRIVATE));
+ $folderdata = self::folders_typedata($prefix);
if (!is_array($folderdata)) {
return array();
}
- $folderdata = array_map(array('kolab_storage', 'folder_select_metadata'), $folderdata);
- $regexp = '/^' . preg_quote($filter, '/') . '(\..+)?$/';
-
// In some conditions we can skip LIST command (?)
if (!$subscribed && $filter != 'mail' && $prefix == '*') {
foreach ($folderdata as $folder => $type) {
if (!preg_match($regexp, $type)) {
unset($folderdata[$folder]);
}
}
return array_keys($folderdata);
}
// Get folders list
if ($subscribed) {
$folders = self::$imap->list_folders_subscribed($root, $mbox);
}
else {
$folders = self::$imap->list_folders($root, $mbox);
}
// In case of an error, return empty list (?)
if (!is_array($folders)) {
return array();
}
// Filter folders list
foreach ($folders as $idx => $folder) {
$type = $folderdata[$folder];
if ($filter == 'mail' && empty($type)) {
continue;
}
if (empty($type) || !preg_match($regexp, $type)) {
unset($folders[$idx]);
}
}
return $folders;
}
+ /**
+ * Returns folder types indexed by folder name
+ *
+ * @param string $prefix Folder prefix (Default '*' for all folders)
+ *
+ * @return array|bool List of folders, False on failure
+ */
+ static function folders_typedata($prefix = '*')
+ {
+ if (!self::setup()) {
+ return false;
+ }
+
+ $folderdata = self::$imap->get_metadata($prefix, array(self::CTYPE_KEY, self::CTYPE_KEY_PRIVATE));
+
+ if (!is_array($folderdata)) {
+ return false;
+ }
+
+ return array_map(array('kolab_storage', 'folder_select_metadata'), $folderdata);
+ }
+
+
/**
* Callback for array_map to select the correct annotation value
*/
static function folder_select_metadata($types)
{
if (!empty($types[self::CTYPE_KEY_PRIVATE])) {
return $types[self::CTYPE_KEY_PRIVATE];
}
else if (!empty($types[self::CTYPE_KEY])) {
list($ctype, $suffix) = explode('.', $types[self::CTYPE_KEY]);
return $ctype;
}
return null;
}
/**
* Returns type of IMAP folder
*
* @param string $folder Folder name (UTF7-IMAP)
*
* @return string Folder type
*/
static function folder_type($folder)
{
self::setup();
$metadata = self::$imap->get_metadata($folder, array(self::CTYPE_KEY, self::CTYPE_KEY_PRIVATE));
if (!is_array($metadata)) {
return null;
}
if (!empty($metadata[$folder])) {
return self::folder_select_metadata($metadata[$folder]);
}
return 'mail';
}
/**
* Sets folder content-type.
*
* @param string $folder Folder name
* @param string $type Content type
*
* @return boolean True on success
*/
static function set_folder_type($folder, $type='mail')
{
self::setup();
list($ctype, $subtype) = explode('.', $type);
$success = self::$imap->set_metadata($folder, array(self::CTYPE_KEY => $ctype, self::CTYPE_KEY_PRIVATE => $subtype ? $type : null));
if (!$success) // fallback: only set private annotation
$success |= self::$imap->set_metadata($folder, array(self::CTYPE_KEY_PRIVATE => $type));
return $success;
}
/**
* Check subscription status of this folder
*
* @param string $folder Folder name
*
* @return boolean True if subscribed, false if not
*/
public static function folder_is_subscribed($folder)
{
if (self::$subscriptions === null) {
self::setup();
self::$subscriptions = self::$imap->list_folders_subscribed();
}
return in_array($folder, self::$subscriptions);
}
/**
* Change subscription status of this folder
*
* @param string $folder Folder name
*
* @return True on success, false on error
*/
public static function folder_subscribe($folder)
{
self::setup();
if (self::$imap->subscribe($folder)) {
self::$subscriptions === null;
return true;
}
return false;
}
/**
* Change subscription status of this folder
*
* @param string $folder Folder name
*
* @return True on success, false on error
*/
public static function folder_unsubscribe($folder)
{
self::setup();
if (self::$imap->unsubscribe($folder)) {
self::$subscriptions === null;
return true;
}
return false;
}
/**
* Check activation status of this folder
*
* @param string $folder Folder name
*
* @return boolean True if active, false if not
*/
public static function folder_is_active($folder)
{
$active_folders = self::get_states();
return in_array($folder, $active_folders);
}
/**
* Change activation status of this folder
*
* @param string $folder Folder name
*
* @return True on success, false on error
*/
public static function folder_activate($folder)
{
return self::set_state($folder, true);
}
/**
* Change activation status of this folder
*
* @param string $folder Folder name
*
* @return True on success, false on error
*/
public static function folder_deactivate($folder)
{
return self::set_state($folder, false);
}
/**
* Return list of active folders
*/
private static function get_states()
{
if (self::$states !== null) {
return self::$states;
}
$rcube = rcube::get_instance();
$folders = $rcube->config->get('kolab_active_folders');
if ($folders !== null) {
self::$states = !empty($folders) ? explode('**', $folders) : array();
}
// for backward-compatibility copy server-side subscriptions to activation states
else {
self::setup();
if (self::$subscriptions === null) {
self::$subscriptions = self::$imap->list_folders_subscribed();
}
self::$states = self::$subscriptions;
$folders = implode(self::$states, '**');
$rcube->user->save_prefs(array('kolab_active_folders' => $folders));
}
return self::$states;
}
/**
* Update list of active folders
*/
private static function set_state($folder, $state)
{
self::get_states();
// update in-memory list
$idx = array_search($folder, self::$states);
if ($state && $idx === false) {
self::$states[] = $folder;
}
else if (!$state && $idx !== false) {
unset(self::$states[$idx]);
}
// update user preferences
$folders = implode(self::$states, '**');
$rcube = rcube::get_instance();
return $rcube->user->save_prefs(array('kolab_active_folders' => $folders));
}
}
diff --git a/lib/plugins/libkolab/lib/kolab_storage_folder.php b/lib/plugins/libkolab/lib/kolab_storage_folder.php
index 84cb041..f046bbf 100644
--- a/lib/plugins/libkolab/lib/kolab_storage_folder.php
+++ b/lib/plugins/libkolab/lib/kolab_storage_folder.php
@@ -1,1123 +1,1126 @@
<?php
/**
* The kolab_storage_folder class represents an IMAP folder on the Kolab server.
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* Copyright (C) 2012, 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/>.
*/
class kolab_storage_folder
{
/**
* The folder name.
* @var string
*/
public $name;
/**
* The type of this folder.
* @var string
*/
public $type;
/**
* Is this folder set to be the default for its type
* @var boolean
*/
public $default = false;
/**
* Is this folder set to be default
* @var boolean
*/
public $cache;
private $type_annotation;
private $namespace;
private $imap;
private $info;
private $idata;
private $owner;
private $resource_uri;
/**
* Default constructor
*/
function __construct($name, $type = null)
{
$this->imap = rcube::get_instance()->get_storage();
$this->imap->set_options(array('skip_deleted' => true));
$this->cache = new kolab_storage_cache($this);
$this->set_folder($name, $type);
}
/**
* Set the IMAP folder this instance connects to
*
* @param string The folder name/path
* @param string Optional folder type if known
*/
public function set_folder($name, $ftype = null)
{
$this->type_annotation = $ftype ? $ftype : kolab_storage::folder_type($name);
list($this->type, $suffix) = explode('.', $this->type_annotation);
$this->default = $suffix == 'default';
$this->name = $name;
$this->resource_uri = null;
$this->imap->set_folder($this->name);
$this->cache->set_folder($this);
}
/**
*
*/
private function get_folder_info()
{
if (!isset($this->info))
$this->info = $this->imap->folder_info($this->name);
return $this->info;
}
/**
* Make IMAP folder data available for this folder
*/
public function get_imap_data()
{
if (!isset($this->idata))
$this->idata = $this->imap->folder_data($this->name);
return $this->idata;
}
/**
* Returns IMAP metadata/annotations (GETMETADATA/GETANNOTATION)
*
* @param array List of metadata keys to read
* @return array Metadata entry-value hash array on success, NULL on error
*/
public function get_metadata($keys)
{
$metadata = $this->imap->get_metadata($this->name, (array)$keys);
return $metadata[$this->name];
}
/**
* Sets IMAP metadata/annotations (SETMETADATA/SETANNOTATION)
*
* @param array $entries Entry-value array (use NULL value as NIL)
* @return boolean True on success, False on failure
*/
public function set_metadata($entries)
{
return $this->imap->set_metadata($this->name, $entries);
}
/**
* Returns the owner of the folder.
*
* @return string The owner of this folder.
*/
public function get_owner()
{
// return cached value
if (isset($this->owner))
return $this->owner;
$info = $this->get_folder_info();
$rcmail = rcube::get_instance();
switch ($info['namespace']) {
case 'personal':
$this->owner = $rcmail->get_user_name();
break;
case 'shared':
$this->owner = 'anonymous';
break;
default:
list($prefix, $user) = explode($this->imap->get_hierarchy_delimiter(), $info['name']);
if (strpos($user, '@') === false) {
$domain = strstr($rcmail->get_user_name(), '@');
if (!empty($domain))
$user .= $domain;
}
$this->owner = $user;
break;
}
return $this->owner;
}
/**
* Getter for the name of the namespace to which the IMAP folder belongs
*
* @return string Name of the namespace (personal, other, shared)
*/
public function get_namespace()
{
if (!isset($this->namespace))
$this->namespace = $this->imap->folder_namespace($this->name);
return $this->namespace;
}
/**
* Get IMAP ACL information for this folder
*
* @return string Permissions as string
*/
public function get_myrights()
{
$rights = $this->info['rights'];
if (!is_array($rights))
$rights = $this->imap->my_rights($this->name);
return join('', (array)$rights);
}
/**
* Get the display name value of this folder
*
* @return string Folder name
*/
public function get_name()
{
return kolab_storage::object_name($this->name, $this->namespace);
}
/**
* Get the color value stored in metadata
*
* @param string Default color value to return if not set
* @return mixed Color value from IMAP metadata or $default is not set
*/
public function get_color($default = null)
{
// color is defined in folder METADATA
$metadata = $this->get_metadata(array(kolab_storage::COLOR_KEY_PRIVATE, kolab_storage::COLOR_KEY_SHARED));
if (($color = $metadata[kolab_storage::COLOR_KEY_PRIVATE]) || ($color = $metadata[kolab_storage::COLOR_KEY_SHARED])) {
return $color;
}
return $default;
}
/**
* Compose a unique resource URI for this IMAP folder
*/
public function get_resource_uri()
{
if (!empty($this->resource_uri))
return $this->resource_uri;
// strip namespace prefix from folder name
$ns = $this->get_namespace();
$nsdata = $this->imap->get_namespace($ns);
if (is_array($nsdata[0]) && strlen($nsdata[0][0]) && strpos($this->name, $nsdata[0][0]) === 0) {
$subpath = substr($this->name, strlen($nsdata[0][0]));
if ($ns == 'other') {
list($user, $suffix) = explode($nsdata[0][1], $subpath, 2);
$subpath = $suffix;
}
}
else {
$subpath = $this->name;
}
// compose fully qualified ressource uri for this instance
$this->resource_uri = 'imap://' . urlencode($this->get_owner()) . '@' . $this->imap->options['host'] . '/' . $subpath;
return $this->resource_uri;
}
/**
* Check activation status of this folder
*
* @return boolean True if enabled, false if not
*/
public function is_active()
{
return kolab_storage::folder_is_active($this->name);
}
/**
* Change activation status of this folder
*
* @param boolean The desired subscription status: true = active, false = not active
*
* @return True on success, false on error
*/
public function activate($active)
{
return $active ? kolab_storage::folder_activate($this->name) : kolab_storage::folder_deactivate($this->name);
}
/**
* Check subscription status of this folder
*
* @return boolean True if subscribed, false if not
*/
public function is_subscribed()
{
return kolab_storage::folder_is_subscribed($this->name);
}
/**
* Change subscription status of this folder
*
* @param boolean The desired subscription status: true = subscribed, false = not subscribed
*
* @return True on success, false on error
*/
public function subscribe($subscribed)
{
return $subscribed ? kolab_storage::folder_subscribe($this->name) : kolab_storage::folder_unsubscribe($this->name);
}
/**
* Get number of objects stored in this folder
*
* @param mixed Pseudo-SQL query as list of filter parameter triplets
* or string with object type (e.g. contact, event, todo, journal, note, configuration)
* @return integer The number of objects of the given type
* @see self::select()
*/
public function count($type_or_query = null)
{
if (!$type_or_query)
$query = array(array('type','=',$this->type));
else if (is_string($type_or_query))
$query = array(array('type','=',$type_or_query));
else
$query = $this->_prepare_query((array)$type_or_query);
// synchronize cache first
$this->cache->synchronize();
return $this->cache->count($query);
}
/**
* List all Kolab objects of the given type
*
* @param string $type Object type (e.g. contact, event, todo, journal, note, configuration)
* @return array List of Kolab data objects (each represented as hash array)
*/
public function get_objects($type = null)
{
if (!$type) $type = $this->type;
// synchronize caches
$this->cache->synchronize();
// fetch objects from cache
return $this->cache->select(array(array('type','=',$type)));
}
/**
* Select *some* Kolab objects matching the given query
*
* @param array Pseudo-SQL query as list of filter parameter triplets
* triplet: array('<colname>', '<comparator>', '<value>')
* @return array List of Kolab data objects (each represented as hash array)
*/
public function select($query = array())
{
// check query argument
if (empty($query))
return $this->get_objects();
// synchronize caches
$this->cache->synchronize();
// fetch objects from cache
return $this->cache->select($this->_prepare_query($query));
}
/**
* Getter for object UIDs only
*
* @param array Pseudo-SQL query as list of filter parameter triplets
* @return array List of Kolab object UIDs
*/
public function get_uids($query = array())
{
// synchronize caches
$this->cache->synchronize();
// fetch UIDs from cache
return $this->cache->select($this->_prepare_query($query), true);
}
/**
* Helper method to sanitize query arguments
*/
private function _prepare_query($query)
{
$type = null;
foreach ($query as $i => $param) {
if ($param[0] == 'type') {
$type = $param[2];
}
else if (($param[0] == 'dtstart' || $param[0] == 'dtend' || $param[0] == 'changed')) {
if (is_object($param[2]) && is_a($param[2], 'DateTime'))
$param[2] = $param[2]->format('U');
if (is_numeric($param[2]))
$query[$i][2] = date('Y-m-d H:i:s', $param[2]);
}
}
// add type selector if not in $query
if (!$type)
$query[] = array('type','=',$this->type);
return $query;
}
/**
* Getter for a single Kolab object, identified by its UID
*
- * @param string Object UID
+ * @param string $uid Object UID
+ * @param string $type Object type (e.g. contact, event, todo, journal, note, configuration)
+ * Defaults to folder type
+ *
* @return array The Kolab object represented as hash array
*/
- public function get_object($uid)
+ public function get_object($uid, $type = null)
{
// synchronize caches
$this->cache->synchronize();
$msguid = $this->cache->uid2msguid($uid);
- if ($msguid && ($object = $this->cache->get($msguid, '*')))
+
+ if ($msguid && ($object = $this->cache->get($msguid, $type))) {
return $object;
+ }
return false;
}
/**
* Fetch a Kolab object attachment which is stored in a separate part
* of the mail MIME message that represents the Kolab record.
*
* @param string Object's UID
* @param string The attachment's mime number
* @param string IMAP folder where message is stored;
* If set, that also implies that the given UID is an IMAP UID
* @param bool True to print the part content
* @param resource File pointer to save the message part
* @param boolean Disables charset conversion
*
* @return mixed The attachment content as binary string
*/
public function get_attachment($uid, $part, $mailbox = null, $print = false, $fp = null, $skip_charset_conv = false)
{
if ($msguid = ($mailbox ? $uid : $this->cache->uid2msguid($uid))) {
$this->imap->set_folder($mailbox ? $mailbox : $this->name);
return $this->imap->get_message_part($msguid, $part, null, $print, $fp, $skip_charset_conv);
}
return null;
}
/**
* Fetch the mime message from the storage server and extract
* the Kolab groupware object from it
*
* @param string The IMAP message UID to fetch
* @param string The object type expected (use wildcard '*' to accept all types)
* @param string The folder name where the message is stored
* @return mixed Hash array representing the Kolab object, a kolab_format instance or false if not found
*/
public function read_object($msguid, $type = null, $folder = null)
{
if (!$type) $type = $this->type;
if (!$folder) $folder = $this->name;
$this->imap->set_folder($folder);
$headers = $this->imap->get_message_headers($msguid);
$message = null;
// Message doesn't exist?
if (empty($headers)) {
return false;
}
// extract the X-Kolab-Type header from the XML attachment part if missing
if (empty($headers->others['x-kolab-type'])) {
$message = new rcube_message($msguid);
foreach ((array)$message->attachments as $part) {
if (strpos($part->mimetype, kolab_format::KTYPE_PREFIX) === 0) {
$headers->others['x-kolab-type'] = $part->mimetype;
break;
}
}
}
// fix buggy messages stating the X-Kolab-Type header twice
else if (is_array($headers->others['x-kolab-type'])) {
$headers->others['x-kolab-type'] = reset($headers->others['x-kolab-type']);
}
// no object type header found: abort
if (empty($headers->others['x-kolab-type'])) {
rcube::raise_error(array(
'code' => 600,
'type' => 'php',
'file' => __FILE__,
'line' => __LINE__,
'message' => "No X-Kolab-Type information found in message $msguid ($this->name).",
), true);
return false;
}
$object_type = kolab_format::mime2object_type($headers->others['x-kolab-type']);
$content_type = kolab_format::KTYPE_PREFIX . $object_type;
// check object type header and abort on mismatch
if ($type != '*' && $object_type != $type)
return false;
if (!$message) $message = new rcube_message($msguid);
$attachments = array();
// get XML part
foreach ((array)$message->attachments as $part) {
if (!$xml && ($part->mimetype == $content_type || preg_match('!application/([a-z]+\+)?xml!', $part->mimetype))) {
$xml = $part->body ? $part->body : $message->get_part_content($part->mime_id);
}
else if ($part->filename || $part->content_id) {
$key = $part->content_id ? trim($part->content_id, '<>') : $part->filename;
$size = null;
// Use Content-Disposition 'size' as for the Kolab Format spec.
if (isset($part->d_parameters['size'])) {
$size = $part->d_parameters['size'];
}
// we can trust part size only if it's not encoded
else if ($part->encoding == 'binary' || $part->encoding == '7bit' || $part->encoding == '8bit') {
$size = $part->size;
}
$attachments[$key] = array(
'id' => $part->mime_id,
'name' => $part->filename,
'mimetype' => $part->mimetype,
'size' => $size,
);
}
}
if (!$xml) {
rcube::raise_error(array(
'code' => 600,
'type' => 'php',
'file' => __FILE__,
'line' => __LINE__,
'message' => "Could not find Kolab data part in message $msguid ($this->name).",
), true);
return false;
}
// check kolab format version
$format_version = $headers->others['x-kolab-mime-version'];
if (empty($format_version)) {
list($xmltype, $subtype) = explode('.', $object_type);
$xmlhead = substr($xml, 0, 512);
// detect old Kolab 2.0 format
if (strpos($xmlhead, '<' . $xmltype) !== false && strpos($xmlhead, 'xmlns=') === false)
$format_version = '2.0';
else
$format_version = '3.0'; // assume 3.0
}
// get Kolab format handler for the given type
$format = kolab_format::factory($object_type, $format_version);
if (is_a($format, 'PEAR_Error'))
return false;
// load Kolab object from XML part
$format->load($xml);
if ($format->is_valid()) {
$object = $format->to_array(array('_attachments' => $attachments));
$object['_type'] = $object_type;
$object['_msguid'] = $msguid;
$object['_mailbox'] = $this->name;
$object['_formatobj'] = $format;
return $object;
}
else {
// try to extract object UID from XML block
if (preg_match('!<uid>(.+)</uid>!Uims', $xml, $m))
$msgadd = " UID = " . trim(strip_tags($m[1]));
rcube::raise_error(array(
'code' => 600,
'type' => 'php',
'file' => __FILE__,
'line' => __LINE__,
'message' => "Could not parse Kolab object data in message $msguid ($this->name)." . $msgadd,
), true);
}
return false;
}
/**
* Save an object in this folder.
*
* @param array $object The array that holds the data of the object.
* @param string $type The type of the kolab object.
* @param string $uid The UID of the old object if it existed before
* @return boolean True on success, false on error
*/
public function save(&$object, $type = null, $uid = null)
{
if (!$type)
$type = $this->type;
// copy attachments from old message
if (!empty($object['_msguid']) && ($old = $this->cache->get($object['_msguid'], $type, $object['_mailbox']))) {
foreach ((array)$old['_attachments'] as $key => $att) {
if (!isset($object['_attachments'][$key])) {
$object['_attachments'][$key] = $old['_attachments'][$key];
}
// unset deleted attachment entries
if ($object['_attachments'][$key] == false) {
unset($object['_attachments'][$key]);
}
// load photo.attachment from old Kolab2 format to be directly embedded in xcard block
else if ($type == 'contact' && ($key == 'photo.attachment' || $key == 'kolab-picture.png') && $att['id']) {
if (!isset($object['photo']))
$object['photo'] = $this->get_attachment($object['_msguid'], $att['id'], $object['_mailbox']);
unset($object['_attachments'][$key]);
}
}
}
// save contact photo to attachment for Kolab2 format
if (kolab_storage::$version == '2.0' && $object['photo']) {
$attkey = 'kolab-picture.png'; // this file name is hard-coded in libkolab/kolabformatV2/contact.cpp
$object['_attachments'][$attkey] = array(
- 'mimetype'=> rc_image_content_type($object['photo']),
+ 'mimetype'=> rcube_mime::image_content_type($object['photo']),
'content' => preg_match('![^a-z0-9/=+-]!i', $object['photo']) ? $object['photo'] : base64_decode($object['photo']),
);
}
// process attachments
if (is_array($object['_attachments'])) {
$numatt = count($object['_attachments']);
foreach ($object['_attachments'] as $key => $attachment) {
// make sure size is set, so object saved in cache contains this info
if (!isset($attachment['size'])) {
if (!empty($attachment['content'])) {
if (is_resource($attachment['content'])) {
// this need to be a seekable resource, otherwise
// fstat() failes and we're unable to determine size
// here nor in rcube_imap_generic before IMAP APPEND
$stat = fstat($attachment['content']);
$attachment['size'] = $stat ? $stat['size'] : 0;
}
else {
$attachment['size'] = strlen($attachment['content']);
}
}
else if (!empty($attachment['path'])) {
$attachment['size'] = filesize($attachment['path']);
}
$object['_attachments'][$key] = $attachment;
}
// generate unique keys (used as content-id) for attachments
if (is_numeric($key) && $key < $numatt) {
// derrive content-id from attachment file name
$ext = preg_match('/(\.[a-z0-9]{1,6})$/i', $attachment['name'], $m) ? $m[1] : null;
$basename = preg_replace('/[^a-z0-9_.-]/i', '', basename($attachment['name'], $ext)); // to 7bit ascii
if (!$basename) $basename = 'noname';
$cid = $basename . '.' . microtime(true) . $ext;
$object['_attachments'][$cid] = $attachment;
unset($object['_attachments'][$key]);
}
}
}
// save recurrence exceptions as individual objects due to lack of support in Kolab v2 format
if (kolab_storage::$version == '2.0' && $object['recurrence']['EXCEPTIONS']) {
$this->save_recurrence_exceptions($object, $type);
}
// check IMAP BINARY extension support for 'file' objects
// allow configuration to workaround bug in Cyrus < 2.4.17
$rcmail = rcube::get_instance();
$binary = $type == 'file' && !$rcmail->config->get('kolab_binary_disable') && $this->imap->get_capability('BINARY');
// generate and save object message
if ($raw_msg = $this->build_message($object, $type, $binary, $body_file)) {
// resolve old msguid before saving
if ($uid && empty($object['_msguid']) && ($msguid = $this->cache->uid2msguid($uid))) {
$object['_msguid'] = $msguid;
$object['_mailbox'] = $this->name;
}
$result = $this->imap->save_message($this->name, $raw_msg, null, false, null, null, $binary);
// delete old message
if ($result && !empty($object['_msguid']) && !empty($object['_mailbox'])) {
$this->imap->delete_message($object['_msguid'], $object['_mailbox']);
$this->cache->set($object['_msguid'], false, $object['_mailbox']);
}
// update cache with new UID
if ($result) {
$object['_msguid'] = $result;
$this->cache->insert($result, $object);
// remove temp file
if ($body_file) {
@unlink($body_file);
}
}
}
return $result;
}
/**
* Save recurrence exceptions as individual objects.
* The Kolab v2 format doesn't allow us to save fully embedded exception objects.
*
* @param array Hash array with event properties
* @param string Object type
*/
private function save_recurrence_exceptions(&$object, $type = null)
{
if ($object['recurrence']['EXCEPTIONS']) {
$exdates = array();
foreach ((array)$object['recurrence']['EXDATE'] as $exdate) {
$key = is_a($exdate, 'DateTime') ? $exdate->format('Y-m-d') : strval($exdate);
$exdates[$key] = 1;
}
// save every exception as individual object
foreach((array)$object['recurrence']['EXCEPTIONS'] as $exception) {
$exception['uid'] = self::recurrence_exception_uid($object['uid'], $exception['start']->format('Ymd'));
$exception['sequence'] = $object['sequence'] + 1;
if ($exception['thisandfuture']) {
$exception['recurrence'] = $object['recurrence'];
// adjust the recurrence duration of the exception
if ($object['recurrence']['COUNT']) {
$recurrence = new kolab_date_recurrence($object['_formatobj']);
if ($end = $recurrence->end()) {
unset($exception['recurrence']['COUNT']);
$exception['recurrence']['UNTIL'] = new DateTime('@'.$end);
}
}
// set UNTIL date if we have a thisandfuture exception
$untildate = clone $exception['start'];
$untildate->sub(new DateInterval('P1D'));
$object['recurrence']['UNTIL'] = $untildate;
unset($object['recurrence']['COUNT']);
}
else {
if (!$exdates[$exception['start']->format('Y-m-d')])
$object['recurrence']['EXDATE'][] = clone $exception['start'];
unset($exception['recurrence']);
}
unset($exception['recurrence']['EXCEPTIONS'], $exception['_formatobj'], $exception['_msguid']);
$this->save($exception, $type, $exception['uid']);
}
unset($object['recurrence']['EXCEPTIONS']);
}
}
/**
* Generate an object UID with the given recurrence-ID in a way that it is
* unique (the original UID is not a substring) but still recoverable.
*/
private static function recurrence_exception_uid($uid, $recurrence_id)
{
$offset = -2;
return substr($uid, 0, $offset) . '-' . $recurrence_id . '-' . substr($uid, $offset);
}
/**
* Delete the specified object from this folder.
*
* @param mixed $object The Kolab object to delete or object UID
* @param boolean $expunge Should the folder be expunged?
*
* @return boolean True if successful, false on error
*/
public function delete($object, $expunge = true)
{
$msguid = is_array($object) ? $object['_msguid'] : $this->cache->uid2msguid($object);
$success = false;
if ($msguid && $expunge) {
$success = $this->imap->delete_message($msguid, $this->name);
}
else if ($msguid) {
$success = $this->imap->set_flag($msguid, 'DELETED', $this->name);
}
if ($success) {
$this->cache->set($msguid, false);
}
return $success;
}
/**
*
*/
public function delete_all()
{
$this->cache->purge();
return $this->imap->clear_folder($this->name);
}
/**
* Restore a previously deleted object
*
* @param string Object UID
* @return mixed Message UID on success, false on error
*/
public function undelete($uid)
{
if ($msguid = $this->cache->uid2msguid($uid, true)) {
if ($this->imap->set_flag($msguid, 'UNDELETED', $this->name)) {
return $msguid;
}
}
return false;
}
/**
* Move a Kolab object message to another IMAP folder
*
* @param string Object UID
* @param string IMAP folder to move object to
* @return boolean True on success, false on failure
*/
public function move($uid, $target_folder)
{
if ($msguid = $this->cache->uid2msguid($uid)) {
if ($this->imap->move_message($msguid, $target_folder, $this->name)) {
$this->cache->move($msguid, $uid, $target_folder);
return true;
}
else {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Failed to move message $msguid to $target_folder: " . $this->imap->get_error_str(),
), true);
}
}
return false;
}
/**
* Creates source of the configuration object message
*
* @param array $object The array that holds the data of the object.
* @param string $type The type of the kolab object.
* @param bool $binary Enables use of binary encoding of attachment(s)
* @param string $body_file Reference to filename of message body
*
* @return mixed Message as string or array with two elements
* (one for message file path, second for message headers)
*/
private function build_message(&$object, $type, $binary, &$body_file)
{
// load old object to preserve data we don't understand/process
if (is_object($object['_formatobj']))
$format = $object['_formatobj'];
else if ($object['_msguid'] && ($old = $this->cache->get($object['_msguid'], $type, $object['_mailbox'])))
$format = $old['_formatobj'];
// create new kolab_format instance
if (!$format)
$format = kolab_format::factory($type, kolab_storage::$version);
if (PEAR::isError($format))
return false;
$format->set($object);
$xml = $format->write(kolab_storage::$version);
$object['uid'] = $format->uid; // read UID from format
$object['_formatobj'] = $format;
if (empty($xml) || !$format->is_valid() || empty($object['uid'])) {
return false;
}
$mime = new Mail_mime("\r\n");
$rcmail = rcube::get_instance();
$headers = array();
$files = array();
$part_id = 1;
$encoding = $binary ? 'binary' : 'base64';
if ($user_email = $rcmail->get_user_email()) {
$headers['From'] = $user_email;
$headers['To'] = $user_email;
}
$headers['Date'] = date('r');
$headers['X-Kolab-Type'] = kolab_format::KTYPE_PREFIX . $type;
$headers['X-Kolab-Mime-Version'] = kolab_storage::$version;
$headers['Subject'] = $object['uid'];
// $headers['Message-ID'] = $rcmail->gen_message_id();
$headers['User-Agent'] = $rcmail->config->get('useragent');
// Check if we have enough memory to handle the message in it
// It's faster than using files, so we'll do this if we only can
if (!empty($object['_attachments']) && ($mem_limit = parse_bytes(ini_get('memory_limit'))) > 0) {
$memory = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB
foreach ($object['_attachments'] as $id => $attachment) {
$memory += $attachment['size'];
}
// 1.33 is for base64, we need at least 4x more memory than the message size
if ($memory * ($binary ? 1 : 1.33) * 4 > $mem_limit) {
$marker = '%%%~~~' . md5(microtime(true) . $memory) . '~~~%%%';
$is_file = true;
$temp_dir = unslashify($rcmail->config->get('temp_dir'));
$mime->setParam('delay_file_io', true);
}
}
$mime->headers($headers);
$mime->setTXTBody("This is a Kolab Groupware object. "
. "To view this object you will need an email client that understands the Kolab Groupware format. "
. "For a list of such email clients please visit http://www.kolab.org/\n\n");
$ctype = kolab_storage::$version == '2.0' ? $format->CTYPEv2 : $format->CTYPE;
// Convert new lines to \r\n, to wrokaround "NO Message contains bare newlines"
// when APPENDing from temp file
$xml = preg_replace('/\r?\n/', "\r\n", $xml);
$mime->addAttachment($xml, // file
$ctype, // content-type
'kolab.xml', // filename
false, // is_file
'8bit', // encoding
'attachment', // disposition
RCUBE_CHARSET // charset
);
$part_id++;
// save object attachments as separate parts
foreach ((array)$object['_attachments'] as $key => $att) {
if (empty($att['content']) && !empty($att['id'])) {
// @TODO: use IMAP CATENATE to skip attachment fetch+push operation
$msguid = !empty($object['_msguid']) ? $object['_msguid'] : $object['uid'];
if ($is_file) {
$att['path'] = tempnam($temp_dir, 'rcmAttmnt');
if (($fp = fopen($att['path'], 'w')) && $this->get_attachment($msguid, $att['id'], $object['_mailbox'], false, $fp, true)) {
fclose($fp);
}
else {
return false;
}
}
else {
$att['content'] = $this->get_attachment($msguid, $att['id'], $object['_mailbox'], false, null, true);
}
}
$headers = array('Content-ID' => Mail_mimePart::encodeHeader('Content-ID', '<' . $key . '>', RCUBE_CHARSET, 'quoted-printable'));
$name = !empty($att['name']) ? $att['name'] : $key;
// To store binary files we can use faster method
// without writting full message content to a temporary file but
// directly to IMAP, see rcube_imap_generic::append().
// I.e. use file handles where possible
- if (!empty($att['content'])) {
+ if (!empty($att['path'])) {
+ if ($is_file && $binary) {
+ $files[] = fopen($att['path'], 'r');
+ $mime->addAttachment($marker, $att['mimetype'], $name, false, $encoding, 'attachment', '', '', '', null, null, '', RCUBE_CHARSET, $headers);
+ }
+ else {
+ $mime->addAttachment($att['path'], $att['mimetype'], $name, true, $encoding, 'attachment', '', '', '', null, null, '', RCUBE_CHARSET, $headers);
+ }
+ }
+ else {
if (is_resource($att['content']) && $is_file && $binary) {
$files[] = $att['content'];
$mime->addAttachment($marker, $att['mimetype'], $name, false, $encoding, 'attachment', '', '', '', null, null, '', RCUBE_CHARSET, $headers);
}
else {
if (is_resource($att['content'])) {
@rewind($att['content']);
$att['content'] = stream_get_contents($att['content']);
}
$mime->addAttachment($att['content'], $att['mimetype'], $name, false, $encoding, 'attachment', '', '', '', null, null, '', RCUBE_CHARSET, $headers);
}
- $part_id++;
- }
- else if (!empty($att['path'])) {
- if ($is_file && $binary) {
- $files[] = fopen($att['path'], 'r');
- $mime->addAttachment($marker, $att['mimetype'], $name, false, $encoding, 'attachment', '', '', '', null, null, '', RCUBE_CHARSET, $headers);
- }
- else {
- $mime->addAttachment($att['path'], $att['mimetype'], $name, true, $encoding, 'attachment', '', '', '', null, null, '', RCUBE_CHARSET, $headers);
- }
- $part_id++;
}
- $object['_attachments'][$key]['id'] = $part_id;
+ $object['_attachments'][$key]['id'] = ++$part_id;
}
if (!$is_file || !empty($files)) {
$message = $mime->getMessage();
}
// parse message and build message array with
// attachment file pointers in place of file markers
if (!empty($files)) {
$message = explode($marker, $message);
$tmp = array();
foreach ($message as $msg_part) {
$tmp[] = $msg_part;
if ($file = array_shift($files)) {
$tmp[] = $file;
}
}
$message = $tmp;
}
// write complete message body into temp file
else if ($is_file) {
// use common temp dir
$body_file = tempnam($temp_dir, 'rcmMsg');
if (PEAR::isError($mime_result = $mime->saveMessageBody($body_file))) {
self::raise_error(array('code' => 650, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Could not create message: ".$mime_result->getMessage()),
true, false);
return false;
}
$message = array(trim($mime->txtHeaders()) . "\r\n\r\n", fopen($body_file, 'r'));
}
return $message;
}
/**
* Triggers any required updates after changes within the
* folder. This is currently only required for handling free/busy
* information with Kolab.
*
* @return boolean|PEAR_Error True if successfull.
*/
public function trigger()
{
$owner = $this->get_owner();
$result = false;
switch($this->type) {
case 'event':
if ($this->get_namespace() == 'personal') {
$result = $this->trigger_url(
sprintf('%s/trigger/%s/%s.pfb',
kolab_storage::get_freebusy_server(),
urlencode($owner),
urlencode($this->imap->mod_folder($this->name))
),
$this->imap->options['user'],
$this->imap->options['password']
);
}
break;
default:
return true;
}
if ($result && is_object($result) && is_a($result, 'PEAR_Error')) {
return PEAR::raiseError(sprintf("Failed triggering folder %s. Error was: %s",
$this->name, $result->getMessage()));
}
return $result;
}
/**
* Triggers a URL.
*
* @param string $url The URL to be triggered.
* @param string $auth_user Username to authenticate with
* @param string $auth_passwd Password for basic auth
* @return boolean|PEAR_Error True if successfull.
*/
private function trigger_url($url, $auth_user = null, $auth_passwd = null)
{
require_once('HTTP/Request2.php');
try {
$rcmail = rcube::get_instance();
$request = new HTTP_Request2($url);
$request->setConfig(array('ssl_verify_peer' => $rcmail->config->get('kolab_ssl_verify_peer', true)));
// set authentication credentials
if ($auth_user && $auth_passwd)
$request->setAuth($auth_user, $auth_passwd);
$result = $request->send();
// rcube::write_log('trigger', $result->getBody());
}
catch (Exception $e) {
return PEAR::raiseError($e->getMessage());
}
return true;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Dec 18, 1:28 PM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
418800
Default Alt Text
(234 KB)

Event Timeline