Page MenuHomePhorge

No OneTemporary

Size
125 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/kolab_sync_data.php b/lib/kolab_sync_data.php
index b589714..0517e69 100644
--- a/lib/kolab_sync_data.php
+++ b/lib/kolab_sync_data.php
@@ -1,1274 +1,1205 @@
<?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> |
+--------------------------------------------------------------------------+
*/
+/**
+ * Base class for Syncroton data backends
+ */
abstract class kolab_sync_data implements Syncroton_Data_IData
{
/**
* ActiveSync protocol version
*
* @var int
*/
protected $asversion = 0;
/**
* information about the current device
*
* @var Syncroton_Model_IDevice
*/
protected $device;
/**
* timestamp to use for all sync requests
*
* @var DateTime
*/
protected $syncTimeStamp;
/**
* name of model to use
*
* @var string
*/
protected $modelName;
/**
* type of the default folder
*
* @var int
*/
protected $defaultFolderType;
/**
* default container for new entries
*
* @var string
*/
protected $defaultFolder;
/**
* type of user created folders
*
* @var int
*/
protected $folderType;
- /**
- * Default namespace
- *
- * @var string
- */
- protected $defaultNS = 'Calendar';
-
/**
* Internal cache for kolab_storage folder objects
*
* @var array
*/
protected $folders = array();
/**
* Timezone
*
* @var string
*/
protected $timezone;
const RESULT_OBJECT = 0;
const RESULT_UID = 1;
const RESULT_COUNT = 2;
/**
* Recurrence types
*/
const RECUR_TYPE_DAILY = 0; // Recurs daily.
const RECUR_TYPE_WEEKLY = 1; // Recurs weekly
const RECUR_TYPE_MONTHLY = 2; // Recurs monthly
const RECUR_TYPE_MONTHLY_DAYN = 3; // Recurs monthly on the nth day
const RECUR_TYPE_YEARLY = 5; // Recurs yearly
const RECUR_TYPE_YEARLY_DAYN = 6; // Recurs yearly on the nth day
/**
* Day of week constants
*/
const RECUR_DOW_SUNDAY = 1;
const RECUR_DOW_MONDAY = 2;
const RECUR_DOW_TUESDAY = 4;
const RECUR_DOW_WEDNESDAY = 8;
const RECUR_DOW_THURSDAY = 16;
const RECUR_DOW_FRIDAY = 32;
const RECUR_DOW_SATURDAY = 64;
const RECUR_DOW_LAST = 127; // The last day of the month. Used as a special value in monthly or yearly recurrences.
/**
* Mapping of recurrence types
*
* @var array
*/
protected $recurTypeMap = array(
'DAILY' => self::RECUR_TYPE_DAILY,
'WEEKLY' => self::RECUR_TYPE_WEEKLY,
'MONTHLY' => self::RECUR_TYPE_MONTHLY,
'MONTHLY' => self::RECUR_TYPE_MONTHLY_DAYN,
'YEARLY' => self::RECUR_TYPE_YEARLY,
'YEARLY' => self::RECUR_TYPE_YEARLY_DAYN,
);
/**
* Mapping of weekdays
* NOTE: ActiveSync uses a bitmask
*
* @var array
*/
protected $recurDayMap = array(
'SU' => self::RECUR_DOW_SUNDAY,
'MO' => self::RECUR_DOW_MONDAY,
'TU' => self::RECUR_DOW_TUESDAY,
'WE' => self::RECUR_DOW_WEDNESDAY,
'TH' => self::RECUR_DOW_THURSDAY,
'FR' => self::RECUR_DOW_FRIDAY,
'SA' => self::RECUR_DOW_SATURDAY,
);
/**
* the constructor
*
* @param Syncroton_Model_IDevice $device
* @param DateTime $syncTimeStamp
*/
public function __construct(Syncroton_Model_IDevice $device, DateTime $syncTimeStamp)
{
$this->backend = kolab_sync_backend::get_instance();
$this->device = $device;
$this->asversion = floatval($device->acsversion);
$this->syncTimeStamp = $syncTimeStamp;
$this->defaultRootFolder = $this->defaultFolder . '::Syncroton';
// set internal timezone of kolab_format to user timezone
try {
$this->timezone = rcube::get_instance()->config->get('timezone', 'GMT');
kolab_format::$timezone = new DateTimeZone($this->timezone);
}
catch (Exception $e) {
//rcube::raise_error($e, true);
$this->timezone = 'GMT';
kolab_format::$timezone = new DateTimeZone('GMT');
}
}
/**
* return list of supported folders for this backend
*
* @return array
*/
public function getAllFolders()
{
$list = array();
// device supports multiple folders ?
if (in_array(strtolower($this->device->devicetype), array('iphone', 'ipad', 'thundertine', 'windowsphone'))) {
// get the folders the user has access to
$list = $this->backend->folders_list($this->device->deviceid, $this->modelName);
}
if ($default = $this->getDefaultFolder()) {
$list = array($default['serverId'] => $default);
}
foreach ($list as $idx => $folder) {
$list[$idx] = new Syncroton_Model_Folder($folder);
}
return $list;
}
/**
* Returns default folder for current class type.
*/
protected function getDefaultFolder()
{
// Check if there's any folder configured for sync
$folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
if (empty($folders)) {
return null;
}
foreach ($folders as $folder) {
if ($folder['type'] == $this->defaultFolderType) {
$default = $folder;
break;
}
}
// Return first on the list if there's no default
if (empty($default)) {
$default = $folders[key($folders)];
}
// Remember real folder ID and set ID/name to root folder
$default['realid'] = $default['serverId'];
$default['serverId'] = $this->defaultRootFolder;
$default['displayName'] = $this->defaultFolder;
return $default;
}
/**
* Creates a folder
*/
public function createFolder(Syncroton_Model_IFolder $folder)
{
$parentid = $folder->parentId;
$type = $folder->type;
$display_name = $folder->displayName;
if ($parentid) {
$parent = $this->backend->folder_id2name($parentid, $this->device->deviceid);
}
$name = rcube_charset::convert($display_name, kolab_sync::CHARSET, 'UTF7-IMAP');
if ($parent !== null) {
$rcube = rcube::get_instance();
$storage = $rcube->get_storage();
$delim = $storage->get_hierarchy_delimiter();
$name = $parent . $delim . $name;
}
// Create IMAP folder
$result = $this->backend->folder_create($name, $type, $this->device->deviceid);
if ($result) {
$folder->serverId = $this->backend->folder_id($name);
return $folder;
}
// @TODO: throw exception
}
/**
* Updates a folder
*/
public function updateFolder(Syncroton_Model_IFolder $folder)
{
$parentid = $folder->parentId;
$type = $folder->type;
$display_name = $folder->displayName;
$old_name = $this->backend->folder_id2name($folder->serverId, $this->device->deviceid);
if ($parentid) {
$parent = $this->backend->folder_id2name($parentid, $this->device->deviceid);
}
$name = rcube_charset::convert($display_name, kolab_sync::CHARSET, 'UTF7-IMAP');
if ($parent !== null) {
$rcube = rcube::get_instance();
$storage = $rcube->get_storage();
$delim = $storage->get_hierarchy_delimiter();
$name = $parent . $delim . $name;
}
// Rename/move IMAP folder
if ($name == $old_name) {
$result = true;
// @TODO: folder type change?
}
else {
$result = $this->backend->folder_rename($old_name, $name, $type, $this->device->deviceid);
}
if ($result) {
$folder->serverId = $this->backend->folder_id($name);
return $folder;
}
// @TODO: throw exception
}
/**
* Deletes a folder
*/
public function deleteFolder($folder)
{
if ($folder instanceof Syncroton_Model_IFolder) {
$folder = $folder->serverId;
}
$name = $this->backend->folder_id2name($folder, $this->device->deviceid);
// @TODO: throw exception
return $this->backend->folder_delete($name, $this->device->deviceid);
}
public function moveItem($srcFolderId, $serverId, $dstFolderId)
{
$item = $this->getObject($srcFolderId, $serverId, $folder);
if ($item && $folder) {
$dstname = $this->backend->folder_id2name($dstFolderId, $this->device->deviceid);
if ($dstname === null) {
return;
}
if (!$folder->move($serverId, $dstname)) {
return;
}
}
return $item['uid'];
}
/**
* Add entry
*
* @param string $folderId Folder identifier
* @param Syncroton_Model_IEntry $entry Entry object
*
* @return string ID of the created entry
*/
public function createEntry($folderId, Syncroton_Model_IEntry $entry)
{
$entry = $this->toKolab($entry, $folderId);
$entry = $this->createObject($folderId, $entry);
// @TODO: throw exception on error
return $entry['uid'];
}
/**
* update existing entry
*
* @param string $folderId
* @param string $serverId
* @param SimpleXMLElement $entry
*
* @return string ID of the updated entry
*/
public function updateEntry($folderId, $serverId, Syncroton_Model_IEntry $entry)
{
-/* @TODO:
- Certain in-schema properties remain untouched in the following three cases:
-
- If there is only an email:Flag ([MS-ASEMAIL] section 2.2.2.27), email:Read ([MS-ASEMAIL] section 2.2.2.47), or email:Categories ([MS-ASEMAIL] section 2.2.2.9) change (that is, if only an email:Flag, email:Categories or email:Read element is present), all other properties will remain unchanged and the client SHOULD NOT send the other elements in the request. If all the other elements are sent, extra bandwidth is used, but no errors occur.
-
- If a calendar:Exceptions ([MS-ASCAL] section 2.2.2.20) node is not specified, the properties for that calendar:Exceptions node will remain unchanged. If a calendar:Exception ([MS-ASCAL] section 2.2.2.19) node within the calendar:Exceptions node is not present, that particular exception will remain unchanged.
-
- If the airsyncbase:Body, airsyncbase:Data, or contacts:Picture elements are not present, the corresponding properties will remain unchanged.
-
- In all other cases, if an in-schema property is not specified in a change request, the property is actively deleted from the item on the server. A client MUST be aware of this when it is sending Sync requests; otherwise, data can be unintentionally removed.
-*/
$oldEntry = $this->getObject($folderId, $serverId);
if (empty($oldEntry)) {
throw new Syncroton_Exception_NotFound('id not found');
}
$entry = $this->toKolab($entry, $folderId, $oldEntry);
$entry = $this->updateObject($folderId, $serverId, $entry);
// @TODO: throw exception on error
return $entry['uid'];
}
/**
* delete entry
*
* @param string $folderId
* @param string $serverId
* @param array $collectionData
*/
public function deleteEntry($folderId, $serverId, $collectionData)
{
$this->deleteObject($folderId, $serverId);
}
public function getFileReference($fileReference)
{
// to be implemented by Email data class
// @TODO: throw "unimplemented" exception here?
}
/**
* Search for existing entries
*
* @param string $folderid
* @param array $filter
* @param int $result_type Type of the result (see RESULT_* constants)
*
* @return array|int Search result as count or array of uids/objects
*/
protected function searchEntries($folderid, $filter = array(), $result_type = self::RESULT_UID)
{
if ($folderid == $this->defaultRootFolder) {
$folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
$folders = array_keys($folders);
}
else {
$folders = array($folderid);
}
// there's a PHP Warning from kolab_storage if $filter isn't an array
if (empty($filter)) {
$filter = array();
}
$result = $result_type == self::RESULT_COUNT ? 0 : array();
foreach ($folders as $folderid) {
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
if ($foldername === null) {
continue;
}
$folder = $this->getFolderObject($foldername);
if (!$folder) {
continue;
}
switch ($result_type) {
case self::RESULT_COUNT:
$result += (int) $folder->count($filter);
break;
case self::RESULT_UID:
if ($uids = $folder->get_uids($filter)) {
$result = array_merge($result, $uids);
}
break;
case self::RESULT_OBJECT:
default:
if ($objects = $folder->select($filter)) {
$result = array_merge($result, $objects);
}
}
}
return $result;
}
/**
* Returns filter query array according to specified ActiveSync FilterType
*
* @param int $filter_type Filter type
*
* @param array Filter query
*/
protected function filter($filter_type = 0)
{
- /*
- From Syncroton_Command_Sync:
- const FILTER_NOTHING = 0;
- const FILTER_1_DAY_BACK = 1;
- const FILTER_3_DAYS_BACK = 2;
- const FILTER_1_WEEK_BACK = 3;
- const FILTER_2_WEEKS_BACK = 4;
- const FILTER_1_MONTH_BACK = 5;
- const FILTER_3_MONTHS_BACK = 6;
- const FILTER_6_MONTHS_BACK = 7;
- const FILTER_INCOMPLETE = 8;
- Doc: MS-ASCMD 2.2.3.64.2 FilterType (Sync)
- */
// overwrite by child class according to specified type
return array();
}
/**
* get all entries changed between to dates
*
* @param string $folderId
* @param DateTime $start
* @param DateTime $end
* @return array
*/
public function getChangedEntries($folderId, DateTime $start, DateTime $end = null)
{
$filter = array();
$filter[] = array('changed', '>', $start);
if ($endTimeStamp) {
$filter[] = array('changed', '<=', $end);
}
$result = $this->searchEntries($folderId, $filter, self::RESULT_UID);
return $result;
}
-
/**
* get count of entries changed between two dates
*
* @param string $folderId
* @param DateTime $start
* @param DateTime $end
*
* @return int
*/
public function getChangedEntriesCount($folderId, DateTime $start, DateTime $end = null)
{
$filter = array();
$filter[] = array('changed', '>', $start);
if ($endTimeStamp) {
$filter[] = array('changed', '<=', $end);
}
$result = $this->searchEntries($folderId, $filter, self::RESULT_COUNT);
return $result;
}
/**
* get id's of all entries available on the server
*
* @param string $folderId
* @param int $filterType
*
* @return array
*/
public function getServerEntries($folder_id, $filter_type)
{
$filter = $this->filter($filter_type);
$result = $this->searchEntries($folder_id, $filter, self::RESULT_UID);
return $result;
}
/**
* get count of all entries available on the server
*
* @param string $folderId
* @param int $filterType
*
* @return int
*/
public function getServerEntriesCount($folder_id, $filter_type)
{
$filter = $this->filter($filter_type);
$result = $this->searchEntries($folder_id, $filter, self::RESULT_COUNT);
return $result;
}
public function getCountOfChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState)
{
$allClientEntries = $contentBackend->getFolderState($this->device, $folder);
// @TODO: Consider looping over all folders here, not in getServerEntries() and
// getChangedEntriesCount(). This way we could break the loop and not check all folders (see @TODO below)
// or at least skip redundant cache sync of the same folder
$allServerEntries = $this->getServerEntries($folder->serverId, $folder->lastfiltertype);
$addedEntries = array_diff($allServerEntries, $allClientEntries);
$deletedEntries = array_diff($allClientEntries, $allServerEntries);
// @TODO: Count is needed only for GetItemEstimate command
// in Ping command we need only information that anything is changed/added/deleted
// so in case when count($addedEntries) + count($deletedEntries) != 0 we don't need
// to count changed entries here. We could also revert the order in such case
// and execute getChangedEntriesCount() before
$changedEntries = $this->getChangedEntriesCount($folder->serverId, $syncState->lastsync);
return count($addedEntries) + count($deletedEntries) + $changedEntries;
}
/**
* Fetches the entry from the backend
*/
protected function getObject($folderid, $entryid, &$folder = null)
{
if ($folderid instanceof Syncroton_Model_IFolder) {
$folderid = $folderid->serverId;
}
if ($folderid == $this->defaultRootFolder) {
$folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
$folders = array_keys($folders);
}
else {
$folders = array($folderid);
}
foreach ($folders as $folderid) {
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
if ($foldername === null) {
continue;
}
$folder = $this->getFolderObject($foldername);
if ($object = $folder->get_object($entryid)) {
$object['_folderid'] = $folderid;
return $object;
}
}
}
/**
* Saves the entry on the backend
*/
protected function createObject($folderid, $data)
{
if ($folderid == $this->defaultRootFolder) {
$default = $this->getDefaultFolder();
$folderid = isset($default['realid']) ? $default['realid'] : $default['serverId'];
}
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
$folder = $this->getFolderObject($foldername);
if ($folder->save($data)) {
return $data;
}
}
/**
* Updates the entry on the backend
*/
protected function updateObject($folderid, $entryid, $data)
{
$object = $this->getObject($folderid, $entryid);
if ($object) {
$folder = $this->getFolderObject($object['_mailbox']);
if ($folder->save($data)) {
return $data;
}
}
}
/**
* Removes the entry from the backend
*/
protected function deleteObject($folderid, $entryid)
{
$object = $this->getObject($folderid, $entryid);
if ($object) {
$folder = $this->getFolderObject($object['_mailbox']);
return $folder->delete($entryid);
}
}
/**
* Returns Folder object (uses internal cache)
*
* @param string $name Folder name (UTF7-IMAP)
*
* @return kolab_storage_folder Folder object
*/
protected function getFolderObject($name)
{
if (!isset($this->folders[$name])) {
$this->folders[$name] = kolab_storage::get_folder($name);
}
return $this->folders[$name];
}
/**
* Returns ActiveSync settings of specified folder
*
* @param string $name Folder name (UTF7-IMAP)
*
* @return array Folder settings
*/
protected function getFolderConfig($name)
{
$metadata = $this->backend->folder_meta();
$deviceid = $this->device->deviceid;
$config = $metadata[$name]['FOLDER'][$deviceid];
return array(
'ALARMS' => $config['S'] == 2,
);
}
/**
* Convert contact from xml to kolab format
*
* @param Syncroton_Model_IEntry $data Contact data
* @param string $folderId Folder identifier
* @param array $entry Old Contact data for merge
*
* @return array
*/
abstract function toKolab(Syncroton_Model_IEntry $data, $folderId, $entry = null);
/**
* Removes control chars from string which are not allowed in ActiveSync
*
* @param string $value Text
*
* @return string Text
* @deprecated
*/
public static function quote($value)
{
if ($value && !ctype_print($value)) {
$value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', null, $value);
}
return $value;
}
/**
* Extracts data from kolab data array
*/
protected function getKolabDataItem($data, $name)
{
$name_items = explode('.', $name);
$count = count($name_items);
// multi-level array (e.g. address, phone)
if ($count == 3) {
$name = $name_items[0];
$type = $name_items[1];
$key_name = $name_items[2];
if (!empty($data[$name]) && is_array($data[$name])) {
foreach ($data[$name] as $element) {
if ($element['type'] == $type) {
return $element[$key_name];
}
}
}
return null;
}
/*
// hash array e.g. organizer
else if ($count == 2) {
$name = $name_items[0];
$type = $name_items[1];
$key_name = $name_items[2];
if (!empty($data[$name]) && is_array($data[$name])) {
foreach ($data[$name] as $element) {
if ($element['type'] == $type) {
return $element[$key_name];
}
}
}
return null;
}
*/
$name_items = explode(':', $name);
$name = $name_items[0];
if (empty($data[$name])) {
return null;
}
// simple array (e.g. email)
if (count($name_items) == 2) {
return $data[$name][$name_items[1]];
}
return $data[$name];
}
/**
* Saves data in kolab data array
*/
protected function setKolabDataItem(&$data, $name, $value)
{
if (empty($value)) {
return $this->unsetKolabDataItem($data, $name);
}
$name_items = explode('.', $name);
// multi-level array (e.g. address, phone)
if (count($name_items) == 3) {
$name = $name_items[0];
$type = $name_items[1];
$key_name = $name_items[2];
if (!isset($data[$name])) {
$data[$name] = array();
}
foreach ($data[$name] as $idx => $element) {
if ($element['type'] == $type) {
$found = $idx;
break;
}
}
if (!isset($found)) {
$data[$name] = array_values($data[$name]);
$found = count($data[$name]);
$data[$name][$found] = array('type' => $type);
}
$data[$name][$found][$key_name] = $value;
return;
}
$name_items = explode(':', $name);
$name = $name_items[0];
// simple array (e.g. email)
if (count($name_items) == 2) {
$data[$name][$name_items[1]] = $value;
return;
}
$data[$name] = $value;
}
/**
* Unsets data item in kolab data array
*/
protected function unsetKolabDataItem(&$data, $name)
{
$name_items = explode('.', $name);
// multi-level array (e.g. address, phone)
if (count($name_items) == 3) {
$name = $name_items[0];
$type = $name_items[1];
$key_name = $name_items[2];
if (!isset($data[$name])) {
return;
}
foreach ($data[$name] as $idx => $element) {
if ($element['type'] == $type) {
$found = $idx;
break;
}
}
if (!isset($found)) {
return;
}
unset($data[$name][$found][$key_name]);
// if there's only one element and it's 'type', remove it
if (count($data[$name][$found]) == 1 && isset($data[$name][$found]['type'])) {
unset($data[$name][$found]['type']);
}
if (empty($data[$name][$found])) {
unset($data[$name][$found]);
}
if (empty($data[$name])) {
unset($data[$name]);
}
return;
}
$name_items = explode(':', $name);
$name = $name_items[0];
// simple array (e.g. email)
if (count($name_items) == 2) {
unset($data[$name][$name_items[1]]);
if (empty($data[$name])) {
unset($data[$name]);
}
return;
}
unset($data[$name]);
}
/**
* Setter for Body attribute according to client version
*
* @param string $value Body
* @param array $param Body parameters
*
* @reurn Syncroton_Model_EmailBody Body element
*/
protected function setBody($value, $params = array())
{
if (empty($value)) {
return;
}
$params['Data'] = $value;
if (!isset($params['Type'])) {
$params['Type'] = 1;
}
return new Syncroton_Model_EmailBody($params);
-/*
- // strip off any non printable control characters
- $value = self::quote($value);
-
- if ($this->asversion >= 12) {
- $body = $domParent->appendChild(new DOMElement('Body', null, 'uri:AirSyncBase'));
-
- $body->appendChild(new DOMElement('Type', 1, 'uri:AirSyncBase'));
-
- $dataTag = new DOMElement('Data', null, 'uri:AirSyncBase');
-
- // ... append it to parent node aka append it to the document ...
- $body->appendChild($dataTag);
-
- // ... and now add the content (DomText takes care of special chars)
- $dataTag->appendChild(new DOMText($value));
- } else {
- // create a new DOMElement ...
- $node = new DOMElement('Body', null, 'uri:' . $this->defaultNS);
-
- // ... append it to parent node aka append it to the document ...
- $domParent->appendChild($node);
-
- // ... and now add the content (DomText takes care of special chars)
- $node->appendChild(new DOMText($value));
- }
-*/
}
/**
* Getter for Body attribute value according to client version
*
* @param mixed $data Data element
*/
protected function getBody($data)
{
if ($data && $data->Data) {
return $data->Data;
}
-/*
- if ($this->asversion >= 12) {
- $airSyncBase = $data->children('uri:AirSyncBase');
-
- if (isset($airSyncBase->Body)) {
- return (string)$airSyncBase->Body->Data;
- }
- }
- else if (isset($classData->Body)) {
- return (string) $classData->Body;
- }
-*/
}
/**
* Converts PHP DateTime, date (YYYY-MM-DD) or unixtimestamp into PHP DateTime in UTC
*
* @param DateTime|int|string $date Unix timestamp, date (YYYY-MM-DD) or PHP DateTime object
*
* @return DateTime Datetime object
*/
protected static function date_from_kolab($date)
{
if (!empty($date)) {
if (is_numeric($date)) {
$date = new DateTime('@' . $date);
}
else if (is_string($date)) {
$date = new DateTime($date, new DateTimeZone('UTC'));
}
else {
$date = clone $date;
$tz = $date->getTimezone();
$tz_name = $tz->getName();
// convert to UTC if needed
if ($tz_name != 'UTC') {
$date->setTimezone(new DateTimeZone('UTC'));
}
}
return $date;
}
}
/**
* Convert Kolab event/task recurrence into ActiveSync
*/
protected function recurrence_from_kolab($data, $type = 'Event')
{
if (empty($data['recurrence'])) {
return null;
}
$recurrence = array();
$r = $data['recurrence'];
// required fields
switch($r['FREQ']) {
case 'DAILY':
$recurrence['Type'] = self::RECUR_TYPE_DAILY;
break;
case 'WEEKLY':
$recurrence['Type'] = self::RECUR_TYPE_WEEKLY;
$recurrence['DayOfWeek'] = $this->day2bitmask($r['BYDAY']);
break;
case 'MONTHLY':
if (!empty($r['BYMONTHDAY'])) {
// @TODO: ActiveSync doesn't support multi-valued month days,
// should we replicate the recurrence element for each day of month?
$month_day = array_shift(explode(',', $r['BYMONTHDAY']));
$recurrence['Type'] = self::RECUR_TYPE_MONTHLY;
$recurrence['DayOfMonth'] = $month_day;
}
else {
$week = (int) substr($r['BYDAY'], 0, -2);
$week = ($week == -1) ? 5 : $week;
$day = substr($r['BYDAY'], -2);
$recurrence['Type'] = self::RECUR_TYPE_MONTHLY_DAYN;
$recurrence['WeekOfMonth'] = $week;
$recurrence['DayOfWeek'] = $this->day2bitmask($day);
}
break;
case 'YEARLY':
// @TODO: ActiveSync doesn't support multi-valued months,
// should we replicate the recurrence element for each month?
$month = array_shift(explode(',', $r['BYMONTH']));
if (!empty($r['BYDAY'])) {
$week = (int) substr($r['BYDAY'], 0, -2);
$week = ($week == -1) ? 5 : $week;
$day = substr($r['BYDAY'], -2);
$recurrence['Type'] = self::RECUR_TYPE_YEARLY_DAYN;
$recurrence['WeekOfMonth'] = $week;
$recurrence['DayOfWeek'] = $this->day2bitmask($day);
$recurrence['MonthOfYear'] = $month;
}
else if (!empty($r['BYMONTHDAY'])) {
// @TODO: ActiveSync doesn't support multi-valued month days,
// should we replicate the recurrence element for each day of month?
$month_day = array_shift(explode(',', $r['BYMONTHDAY']));
$recurrence['Type'] = self::RECUR_TYPE_YEARLY;
$recurrence['DayOfMonth'] = $month_day;
$recurrence['MonthOfYear'] = $month;
}
else {
$recurrence['Type'] = self::RECUR_TYPE_YEARLY;
$recurrence['MonthOfYear'] = $month;
}
break;
}
// required field
$recurrence['Interval'] = $r['INTERVAL'] ? $r['INTERVAL'] : 1;
if (!empty($r['UNTIL'])) {
$recurrence['Until'] = $this->date_from_kolab($r['UNTIL']);
}
else if (!empty($r['COUNT'])) {
$recurrence['Occurrences'] = $r['COUNT'];
}
$class = 'Syncroton_Model_' . $type . 'Recurrence';
return new $class($recurrence);
}
/**
* Convert ActiveSync event/task recurrence into Kolab
*/
protected function recurrence_to_kolab($data, $timezone = null)
{
if (!isset($data->Recurrence) || isset($data->Recurrence->Type)) {
return null;
}
$type = $data->Recurrence->Type;
$rrule['FREQ'] = array_search($type, $this->recurTypeMap);
switch ($type) {
case self::RECUR_TYPE_DAILY:
break;
case self::RECUR_TYPE_WEEKLY:
$rrule['BYDAY'] = $this->bitmask2day($data->Recurrence->DayOfWeek);
break;
case self::RECUR_TYPE_MONTHLY:
$rrule['BYMONTHDAY'] = $data->Recurrence->DayOfMonth;
break;
case self::RECUR_TYPE_MONTHLY_DAYN:
$week = $data->Recurrence->WeekOfMonth;
$day = $data->Recurrence->DayOfWeek;
$byDay = $week == 5 ? -1 : $week;
$byDay .= $this->bitmask2day($day);
$rrule['BYDAY'] = $byDay;
break;
case self::RECUR_TYPE_YEARLY:
$rrule['BYMONTH'] = $data->Recurrence->MonthOfYear;
$rrule['BYMONTHDAY'] = $data->Recurrence->DayOfMonth;
break;
case self::RECUR_TYPE_YEARLY_DAYN:
$rrule['BYMONTH'] = $data->Recurrence->MonthOfYear;
$week = $data->Recurrence->WeekOfMonth;
$day = $data->Recurrence->DayOfWeek;
$byDay = $week == 5 ? -1 : $week;
$byDay .= $this->bitmask2day($day);
$rrule['BYDAY'] = $byDay;
break;
}
$rrule['INTERVAL'] = isset($data->Recurrence->Interval) ? $data->Recurrence->Interval : 1;
if (isset($data->Recurrence->Until)) {
if ($timezone) {
$data->Recurrence->Until->setTimezone($timezone);
}
$rrule['UNTIL'] = $data->Recurrence->Until;
}
else if (!empty($data->Recurrence->Occurrences)) {
$rrule['COUNT'] = $data->Recurrence->Occurrences;
}
return $rrule;
}
/**
* Convert Kolab event recurrence exceptions into ActiveSync
*/
protected function exceptions_from_kolab($data)
{
/*
// handle exceptions of repeating events
if($data->exdate && $data->exdate->count() > 0) {
$exceptionsTag = $_domParent->appendChild(new DOMElement('Exceptions', null, 'uri:Calendar'));
foreach ($data->exdate as $exception) {
$exceptionTag = $exceptionsTag->appendChild(new DOMElement('Exception', null, 'uri:Calendar'));
$exceptionTag->appendChild(new DOMElement('Deleted', (int)$exception->is_deleted, 'uri:Calendar'));
$exceptionTag->appendChild(new DOMElement('ExceptionStartTime', $exception->getOriginalDtStart()->format('Ymd\THis') . 'Z', 'uri:Calendar'));
if ((int)$exception->is_deleted === 0) {
$this->appendXML($exceptionTag, $_collectionData, $exception);
}
}
}
*/
}
/**
* Convert ActiveSync event recurrence exceptions into Kolab
*/
protected function exceptions_to_kolab($data, $timezone = null)
{
/*
// handle exceptions from recurrence
if (isset($xmlData->Exceptions)) {
$exdates = new Tinebase_Record_RecordSet('Calendar_Model_Event');
foreach ($xmlData->Exceptions->Exception as $exception) {
$eventException = new Calendar_Model_Event(array(
'recurid' => new Tinebase_DateTime((string)$exception->ExceptionStartTime)
));
if ((int)$exception->Deleted === 0) {
$eventException->is_deleted = false;
$this->toTineModel($exception, $eventException);
} else {
$eventException->is_deleted = true;
}
$exdates->addRecord($eventException);
}
$event->exdate = $exdates;
}
*/
}
/**
* Converts string of days (TU,TH) to bitmask used by ActiveSync
*
* @param string $days
*
* @return int
*/
protected function day2bitmask($days)
{
$days = explode(',', $days);
$result = 0;
foreach ($days as $day) {
$result = $result + $this->recurDayMap[$day];
}
return $result;
}
-
/**
* Convert bitmask used by ActiveSync to string of days (TU,TH)
*
* @param int $days
*
* @return string
*/
protected function bitmask2day($days)
{
$days_arr = array();
for ($bitmask = 1; $bitmask <= self::RECUR_DOW_SATURDAY; $bitmask = $bitmask << 1) {
$dayMatch = $days & $bitmask;
if ($dayMatch === $bitmask) {
$days_arr[] = array_search($bitmask, $this->recurDayMap);
}
}
$result = implode(',', $days_arr);
return $result;
}
}
diff --git a/lib/kolab_sync_data_calendar.php b/lib/kolab_sync_data_calendar.php
index 2a3d66d..26e7975 100644
--- a/lib/kolab_sync_data_calendar.php
+++ b/lib/kolab_sync_data_calendar.php
@@ -1,552 +1,545 @@
<?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> |
+--------------------------------------------------------------------------+
*/
/**
- *
+ * Calendar (Events) data class for Syncroton
*/
class kolab_sync_data_calendar extends kolab_sync_data
{
/**
* Mapping from ActiveSync Calendar namespace fields
*/
protected $mapping = array(
'AllDayEvent' => 'allday',
//'Attendee' => 'attendee', // Attendees member
//'Attendees' => 'attendees',
//'AttendeeStatus' => 'attendeestatus', // Attendee member
//'AttendeeType' => 'attendeetype', // Attendee member
'Body' => 'description',
//'BodyTruncated' => 'bodytruncated',
'BusyStatus' => 'free_busy',
//'Categories' => 'categories',
//'Category' => 'category', // Categories member
//'DayOfMonth' => 'dayofmonth', // Recurrence member
//'DayOfWeek' => 'dayofweek', // Recurrence member
//'Deleted' => 'deleted', // Exception member
'DtStamp' => 'changed',
//'Email' => 'email', // Attendee member
'EndTime' => 'end',
//'Exception' => 'exception', // recurrence pattern exception, Exceptions member
//'Exceptions' => 'exceptions',
//'ExceptionStartTime' => 'exceptionstarttime', // Exception member
//'FirstDayOfWeek' => 'firstdayofweek', // Recurrence member
//'Interval' => 'interval', // Recurrence member
//'IsLeapMonth' => 'isleapmonth', // Recurrence member
'Location' => 'location',
//'MeetingStatus' => 'meetingstatus',
//'MonthOfYear' => 'monthofyear', // Recurrence member
//'Name' => 'name', // Attendee member
//'Occurrences' => 'occurences', // Recurrence member
//'OrganizerEmail' => 'organizeremail',
//'OrganizerName' => 'organizername',
//'Recurrence' => 'recurrence',
//'Reminder' => 'reminder',
//'ResponseRequested' => 'responserequested',
//'ResponseType' => 'responsetype',
'Sensitivity' => 'sensitivity',
'StartTime' => 'start',
'Subject' => 'title',
//'Timezone' => 'timezone',
//'Type' => 'type', // Recurrence member
'UID' => 'uid',
//'Until' => 'until', // Recurrence member
//'WeekOfMonth' => 'weekofmonth', // Recurrence member
);
/**
* Kolab object type
*
* @var string
*/
protected $modelName = 'event';
/**
* Type of the default folder
*
* @var int
*/
protected $defaultFolderType = Syncroton_Command_FolderSync::FOLDERTYPE_CALENDAR;
/**
* Default container for new entries
*
* @var string
*/
protected $defaultFolder = 'Calendar';
/**
* Type of user created folders
*
* @var int
*/
protected $folderType = Syncroton_Command_FolderSync::FOLDERTYPE_CALENDAR_USER_CREATED;
- /**
- * Default namespace
- *
- * @var string
- */
- protected $defaultNS = 'Calendar';
-
/**
* attendee status
*/
const ATTENDEE_STATUS_UNKNOWN = 0;
const ATTENDEE_STATUS_TENTATIVE = 2;
const ATTENDEE_STATUS_ACCEPTED = 3;
const ATTENDEE_STATUS_DECLINED = 4;
const ATTENDEE_STATUS_NOTRESPONDED = 5;
/**
* attendee types
*/
const ATTENDEE_TYPE_REQUIRED = 1;
const ATTENDEE_TYPE_OPTIONAL = 2;
const ATTENDEE_TYPE_RESOURCE = 3;
/**
* busy status constants
*/
const BUSY_STATUS_FREE = 0;
const BUSY_STATUS_TENTATIVE = 1;
const BUSY_STATUS_BUSY = 2;
const BUSY_STATUS_OUTOFOFFICE = 3;
/**
* Sensitivity values
*/
const SENSITIVITY_NORMAL = 0;
const SENSITIVITY_PERSONAL = 1;
const SENSITIVITY_PRIVATE = 2;
const SENSITIVITY_CONFIDENTIAL = 3;
/**
* Mapping of attendee status
*
* @var array
*/
protected $attendeeStatusMap = array(
'UNKNOWN' => self::ATTENDEE_STATUS_UNKNOWN,
'TENTATIVE' => self::ATTENDEE_STATUS_TENTATIVE,
'ACCEPTED' => self::ATTENDEE_STATUS_ACCEPTED,
'DECLINED' => self::ATTENDEE_STATUS_DECLINED,
'DELEGATED' => self::ATTENDEE_STATUS_UNKNOWN,
'NEEDS-ACTION' => self::ATTENDEE_STATUS_UNKNOWN,
//self::ATTENDEE_STATUS_NOTRESPONDED,
);
/**
* Mapping of attendee type
*
* NOTE: recurrences need extra handling!
* @var array
*/
protected $attendeeTypeMap = array(
'REQ-PARTICIPANT' => self::ATTENDEE_TYPE_REQUIRED,
'OPT-PARTICIPANT' => self::ATTENDEE_TYPE_OPTIONAL,
// 'NON-PARTICIPANT' => self::ATTENDEE_TYPE_RESOURCE,
// 'CHAIR' => self::ATTENDEE_TYPE_RESOURCE,
);
/**
* Mapping of busy status
*
* @var array
*/
protected $busyStatusMap = array(
'free' => self::BUSY_STATUS_FREE,
'tentative' => self::BUSY_STATUS_TENTATIVE,
'busy' => self::BUSY_STATUS_BUSY,
'outofoffice' => self::BUSY_STATUS_OUTOFOFFICE,
);
/**
* mapping of sensitivity
*
* @var array
*/
protected $sensitivityMap = array(
'public' => self::SENSITIVITY_PERSONAL,
'private' => self::SENSITIVITY_PRIVATE,
'confidential' => self::SENSITIVITY_CONFIDENTIAL,
);
/**
* Appends contact data to xml element
*
* @param Syncroton_Model_SyncCollection $collection Collection data
* @param string $serverId Local entry identifier
*/
public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId)
{
$event = is_array($serverId) ? $serverId : $this->getObject($collection->collectionId, $serverId);
$config = $this->getFolderConfig($event['_mailbox']);
$result = array();
// Timezone
// Kolab Format 3.0 and xCal does support timezone per-date, but ActiveSync allows
// only one timezone per-event. We'll use timezone of the start date
if ($event['start'] instanceof DateTime) {
$timezone = $event['start']->getTimezone();
if ($timezone && ($tz_name = $timezone->getName()) != 'UTC') {
$tzc = kolab_sync_timezone_converter::getInstance();
if ($tz_name = $tzc->encodeTimezone($tz_name)) {
$result['Timezone'] = $tz_name;
}
}
}
// Calendar namespace fields
foreach ($this->mapping as $key => $name) {
$value = $this->getKolabDataItem($event, $name);
switch ($name) {
case 'changed':
case 'end':
case 'start':
// For all-day events Kolab uses different times
// At least Android doesn't display such event as all-day event
if ($value && $event['allday']) {
if ($name == 'start') {
$value->setTime(0, 0, 0);
}
else if ($name == 'end') {
$value->setTime(0, 0, 0);
$value->modify('+1 day');
}
}
$value = self::date_from_kolab($value);
break;
case 'sensitivity':
$value = intval($this->sensitivityMap[$value]);
break;
case 'free_busy':
$value = $this->busyStatusMap[$value];
break;
case 'description':
$value = $this->setBody($value);
break;
}
if (empty($value) || is_array($value)) {
continue;
}
$result[$key] = $value;
}
// Event reminder time
if ($config['ALARMS'] && ($minutes = $this->from_kolab_alarm($event['alarms']))) {
$result['Reminder'] = $minutes;
}
// Categories, Roundcube Calendar plugin supports only one category at a time
if (!empty($event['categories'])) {
$result['Categories'] = (array) $event['categories'];
}
// Organizer
if (!empty($event['attendees'])) {
foreach ($event['attendees'] as $idx => $attendee) {
if ($attendee['role'] == 'ORGANIZER') {
$organizer = $attendee;
if ($name = self::quote($attendee['name'])) {
$result['OrganizerName'] = $name;
}
if ($email = self::quote($attendee['email'])) {
$result['OrganizerEmail'] = $email;
}
unset($event['attendees'][$idx]);
break;
}
}
}
// Attendees
if (!empty($event['attendees'])) {
$result['Attendees'] = array();
foreach ($event['attendees'] as $idx => $attendee) {
$att = array();
if ($attendee['name']) {
$att['Name'] = $name;
}
if ($attendee['email']) {
$att['Email'] = $email;
}
if ($this->asversion >= 12) {
$type = isset($attendee['role']) ? $this->attendeeTypeMap[$attendee['role']] : null;
$status = isset($attendee['status']) ? $this->attendeeStatusMap[$attende['status']] : null;
$att['AttendeeType'] = $type ? $type : self::ATTENDEE_TYPE_REQUIRED;
$att['AttendeeStatus'] = $status ? $status : self::ATTENDEE_STATUS_UNKNOWN;
}
$result['Attendees'][] = new Syncroton_Model_EventAttendee($att);
}
/*
// set own status
if (($ownAttendee = Calendar_Model_Attender::getOwnAttender($event->attendee)) !== null
&& ($busyType = array_search($ownAttendee->status, $this->_busyStatusMapping)) !== false
) {
$result['BusyStatus'] = $busyType;
}
*/
}
// Event meeting status
$result['MeetingStatus'] = intval(!empty($result['Attendees']));
// Recurrence
$result['Recurrence'] = $this->recurrence_from_kolab($event);
return new Syncroton_Model_Event($result);
}
/**
* convert contact from xml to libkolab array
*
* @param Syncroton_Model_IEntry $data Contact to convert
* @param string $folderid Folder identifier
* @param array $entry Existing entry
*
* @return array
*/
public function toKolab(Syncroton_Model_IEntry $data, $folderid, $entry = null)
{
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
$event = !empty($entry) ? $entry : array();
$config = $this->getFolderConfig($foldername);
$event['allday'] = 0;
// Timezone
if (isset($data->Timezone)) {
$tzc = kolab_sync_timezone_converter::getInstance();
$expected = kolab_format::$timezone->getName();
if (!empty($event['start']) && ($event['start'] instanceof DateTime)) {
$expected = $event['start']->getTimezone()->getName();
}
$timezone = $tzc->getTimezone($data->Timezone, $expected);
try {
$timezone = new DateTimeZone($timezone);
}
catch (Exception $e) {
$timezone = null;
}
}
if (empty($timezone)) {
$timezone = new DateTimeZone('UTC');
}
// Calendar namespace fields
foreach ($this->mapping as $key => $name) {
$value = $data->$key;
switch ($name) {
case 'changed':
$value = null;
break;
case 'end':
case 'start':
if ($timezone && $value) {
$value->setTimezone($timezone);
}
// In ActiveSync all-day event ends on 00:00:00 next day
if ($value && $data->AllDayEvent && $name == 'end') {
$value->modify('-1 second');
}
break;
case 'sensitivity':
$map = array_flip($this->sensitivityMap);
$value = $map[$value];
break;
case 'free_busy':
$map = array_flip($this->busyStatusMap);
$value = $map[$value];
break;
case 'description':
$value = $this->getBody($value);
// If description isn't specified keep old description
if ($value === null) {
continue 2;
}
break;
}
$this->setKolabDataItem($event, $name, $value);
}
// Reminder
// @TODO: should alarms be used when importing event from phone?
if ($config['ALARMS']) {
$event['alarms'] = $this->to_kolab_alarm($data->Reminder, $event);
}
$event['attendees'] = array();
$event['categories'] = array();
// Categories
if (isset($data->Categories)) {
foreach ($data->Categories as $category) {
$event['categories'][] = $category;
}
}
// Organizer
$name = $data->OrganizerName;
$email = $data->OrganizerEmail;
if ($name || $email) {
$event['attendees'][] = array(
'role' => 'ORGANIZER',
'name' => $name,
'email' => $email,
);
}
// Attendees
if (isset($data->Attendees)) {
foreach ($data->Attendees as $attendee) {
$role = false;
if (isset($attendee->AttendeeType)) {
$role = array_search($attendee->AttendeeType, $this->attendeeTypeMap);
}
if ($role === false) {
$role = array_search(self::ATTENDEE_TYPE_REQUIRED, $this->attendeeTypeMap);
}
// AttendeeStatus send only on repsonse (?)
$event['attendees'][] = array(
'role' => $role,
'name' => $attendee->Name,
'email' => $attendee->Email,
);
}
}
// recurrence
$event['recurrence'] = $this->recurrence_to_kolab($data, $timezone);
return $event;
}
/**
* Returns filter query array according to specified ActiveSync FilterType
*
* @param int $filter_type Filter type
*
* @param array Filter query
*/
protected function filter($filter_type = 0)
{
$filter = array();
switch ($filter_type) {
case Syncroton_Command_Sync::FILTER_2_WEEKS_BACK:
$mod = '-2 weeks';
break;
case Syncroton_Command_Sync::FILTER_1_MONTH_BACK:
$mod = '-1 month';
break;
case Syncroton_Command_Sync::FILTER_3_MONTHS_BACK:
$mod = '-3 months';
break;
case Syncroton_Command_Sync::FILTER_6_MONTHS_BACK:
$mod = '-6 months';
break;
}
if (!empty($mod)) {
$dt = new DateTime('now', new DateTimeZone('UTC'));
$dt->modify($mod);
$filter[] = array('dtend', '>', $dt);
}
return $filter;
}
/**
* Converts libkolab alarms string into number of minutes
*/
protected function from_kolab_alarm($value)
{
// e.g. '-15M:DISPLAY'
// Ignore EMAIL alarms
if (preg_match('/^-([0-9]+)([WDHMS]):(DISPLAY|AUDIO)$/', $value, $matches)) {
$value = intval($matches[1]);
switch ($matches[2]) {
case 'S': $value = 1; break;
case 'H': $value *= 60; break;
case 'D': $value *= 24 * 60; break;
case 'W': $value *= 7 * 24 * 60; break;
}
return $value;
}
}
/**
* Converts ActiveSync libkolab alarms string into number of minutes
*/
protected function to_kolab_alarm($value, $event)
{
// Get alarm type from old event object if exists
if (!empty($event['alarms']) && preg_match('/:(.*)$/', $event['alarms'], $matches)) {
$type = $matches[1];
}
if ($value) {
return sprintf('-%dM:%s', $value, $type ? $type : 'DISPLAY');
}
if ($type == 'DISPLAY' || $type == 'AUDIO') {
return null;
}
return $event['alarms'];
}
}
diff --git a/lib/kolab_sync_data_contacts.php b/lib/kolab_sync_data_contacts.php
index 37142c7..58adf36 100644
--- a/lib/kolab_sync_data_contacts.php
+++ b/lib/kolab_sync_data_contacts.php
@@ -1,249 +1,242 @@
<?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> |
+--------------------------------------------------------------------------+
*/
/**
- *
+ * COntacts data class for Syncroton
*/
class kolab_sync_data_contacts extends kolab_sync_data
{
/**
* Mapping from ActiveSync Contacts namespace fields
*/
protected $mapping = array(
'Anniversary' => 'anniversary',
'AssistantName' => 'assistant:0',
//'AssistnamePhoneNumber' => 'assistnamephonenumber',
'Birthday' => 'birthday',
'Body' => 'notes',
//'BodySize' => 'bodysize',
//'BodyTruncated' => 'bodytruncated',
//'Business2PhoneNumber' => 'business2phonenumber',
'BusinessAddressCity' => 'address.work.locality',
'BusinessAddressCountry' => 'address.work.country',
'BusinessAddressPostalCode' => 'address.work.code',
'BusinessAddressState' => 'address.work.region',
'BusinessAddressStreet' => 'address.work.street',
'BusinessFaxNumber' => 'phone.workfax.number',
'BusinessPhoneNumber' => 'phone.work.number',
'CarPhoneNumber' => 'phone.car.number',
//'Categories' => 'categories',
//'Category' => 'category',
'Children' => 'children',
//'Child' => 'child',
'CompanyName' => 'organization',
'Department' => 'department',
'Email1Address' => 'email:0',
'Email2Address' => 'email:1',
'Email3Address' => 'email:2',
//'FileAs' => 'fileas', //@TODO: ?
'FirstName' => 'firstname',
//'Home2PhoneNumber' => 'home2phonenumber',
'HomeAddressCity' => 'address.home.locality',
'HomeAddressCountry' => 'address.home.country',
'HomeAddressPostalCode' => 'address.home.code',
'HomeAddressState' => 'address.home.region',
'HomeAddressStreet' => 'address.home.street',
'HomeFaxNumber' => 'phone.homefax.number',
'HomePhoneNumber' => 'phone.home.number',
'JobTitle' => 'jobtitle',
'LastName' => 'surname',
'MiddleName' => 'middlename',
'MobilePhoneNumber' => 'phone.mobile.number',
//'OfficeLocation' => 'officelocation',
'OtherAddressCity' => 'address.office.locality',
'OtherAddressCountry' => 'address.office.country',
'OtherAddressPostalCode' => 'address.office.code',
'OtherAddressState' => 'address.office.region',
'OtherAddressStreet' => 'address.office.street',
'PagerNumber' => 'phone.pager.number',
//'RadioPhoneNumber' => 'radiophonenumber',
'Spouse' => 'spouse',
'Suffix' => 'suffix',
'Title' => 'prefix',
'WebPage' => 'website.homepage.url',
//'YomiCompanyName' => 'yomicompanyname',
//'YomiFirstName' => 'yomifirstname',
//'YomiLastName' => 'yomilastname',
//'Rtf' => 'rtf',
'Picture' => 'photo',
// Mapping from ActiveSync Contacts2 namespace fields
//'CustomerId' => 'customerid',
//'GovernmentId' => 'governmentid',
'IMAddress' => 'im:0',
'IMAddress2' => 'im:1',
'IMAddress3' => 'im:2',
'ManagerName' => 'manager:0',
//'CompanyMainPhone' => 'companymainphone',
//'AccountName' => 'accountname',
'NickName' => 'nickname',
//'MMS' => 'mms',
);
/**
* Kolab object type
*
* @var string
*/
protected $modelName = 'contact';
/**
* Type of the default folder
*
* @var int
*/
protected $defaultFolderType = Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT;
/**
* Default container for new entries
*
* @var string
*/
protected $defaultFolder = 'Contacts';
/**
* Type of user created folders
*
* @var int
*/
protected $folderType = Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED;
- /**
- * Default namespace
- *
- * @var string
- */
- protected $defaultNS = 'Contacts';
-
/**
* Creates model object
*
* @param Syncroton_Model_SyncCollection $collection Collection data
* @param string $serverId Local entry identifier
*/
public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId)
{
$data = is_array($serverId) ? $serverId : $this->getObject($collection->collectionId, $serverId);
$result = array();
// Contacts namespace fields
foreach ($this->mapping as $key => $name) {
$value = $this->getKolabDataItem($data, $name);
switch ($name) {
case 'photo':
if ($value) {
// ActiveSync limits photo size to 48KB (of base64 encoded string)
if (strlen($value) * 1.33 > 48 * 1024) {
continue;
}
}
break;
case 'birthday':
case 'anniversary':
$value = self::date_from_kolab($value);
break;
case 'notes':
$value = $this->setBody($value);
break;
}
if (empty($value) || is_array($value)) {
continue;
}
$result[$key] = $value;
}
return new Syncroton_Model_Contact($result);
}
/**
* convert contact from xml to libkolab array
*
* @param Syncroton_Model_IEntry $data Contact to convert
* @param string $folderId Folder identifier
* @param array $entry Existing entry
*
* @return array Kolab object array
*/
public function toKolab(Syncroton_Model_IEntry $data, $folderId, $entry = null)
{
$contact = !empty($entry) ? $entry : array();
// Contacts namespace fields
foreach ($this->mapping as $key => $name) {
$value = $data->$key;
switch ($name) {
case 'address.work.street':
if (strtolower($this->device->devicetype) == 'palm') {
// palm pre sends the whole address in the <Contacts:BusinessStreet> tag
$value = null;
}
break;
case 'email:0':
case 'email:1':
case 'email:2':
// android send email address as
// Lars Kneschke <l.kneschke@metaways.de>
if (preg_match('/(.*)<(.+@[^@]+)>/', $value, $matches)) {
$value = trim($matches[2]);
}
break;
case 'website.homepage.url':
// remove facebook urls
if (preg_match('/^fb:\/\//', $value)) {
$value = null;
}
break;
case 'notes':
$value = $this->getBody($value);
// If note isn't specified keep old note
if ($value === null) {
continue 2;
}
break;
case 'photo':
// If photo isn't specified keep old photo
if ($value === null) {
continue 2;
}
break;
}
$this->setKolabDataItem($contact, $name, $value);
}
return $contact;
}
}
diff --git a/lib/kolab_sync_data_email.php b/lib/kolab_sync_data_email.php
index 582f171..f8b7df8 100644
--- a/lib/kolab_sync_data_email.php
+++ b/lib/kolab_sync_data_email.php
@@ -1,1120 +1,1113 @@
<?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> |
+--------------------------------------------------------------------------+
*/
/**
- *
+ * Email data class for Syncroton
*/
class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_IDataSearch
{
const MAX_SEARCH_RESULT = 200;
/**
* Mapping from ActiveSync Email namespace fields
*/
protected $mapping = array(
'Cc' => 'cc',
//'ContentClass' => 'contentclass',
'DateReceived' => 'internaldate',
//'DisplayTo' => 'displayto', //?
//'Flag' => 'flag',
//'FlagType' => 'flagtype', // child of Flag element
'From' => 'from',
//'Importance' => 'importance',
'InternetCPID' => 'charset',
//'MessageClass' => 'messageclass',
'ReplyTo' => 'replyto',
//'Read' => 'read',
'Subject' => 'subject',
//'ThreadTopic' => 'threadtopic',
'To' => 'to',
);
/**
* Kolab object type
*
* @var string
*/
protected $modelName = 'mail';
/**
* Type of the default folder
*
* @var int
*/
protected $defaultFolderType = Syncroton_Command_FolderSync::FOLDERTYPE_INBOX;
/**
* Default container for new entries
*
* @var string
*/
protected $defaultFolder = 'INBOX';
/**
* Type of user created folders
*
* @var int
*/
protected $folderType = Syncroton_Command_FolderSync::FOLDERTYPE_MAIL_USER_CREATED;
- /**
- * Default namespace
- *
- * @var string
- */
- protected $defaultNS = 'Email';
-
/**
* the constructor
*
* @param Syncroton_Model_IDevice $device
* @param DateTime $syncTimeStamp
*/
public function __construct(Syncroton_Model_IDevice $device, DateTime $syncTimeStamp)
{
parent::__construct($device, $syncTimeStamp);
$this->storage = rcube::get_instance()->get_storage();
}
/**
* Creates model object
*
* @param Syncroton_Model_SyncCollection $collection Collection data
* @param string $serverId Local entry identifier
*
* @return Syncroton_Model_Email Email object
*/
public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId)
{
$message = $this->getObject($serverId);
if (empty($message)) {
// @TODO: exception
return;
}
$msg = $this->parseMessageId($serverId);
$headers = $message->headers; // rcube_message_header
// Calendar namespace fields
foreach ($this->mapping as $key => $name) {
$value = null;
switch ($name) {
case 'internaldate':
$value = self::date_from_kolab(rcube_imap_generic::strToTime($headers->internaldate));
break;
case 'cc':
case 'to':
case 'replyto':
case 'from':
$addresses = rcube_mime::decode_address_list($headers->$name, null, true, $headers->charset);
foreach ($addresses as $idx => $part) {
$name = $part['name'];
$mailto = $part['mailto'];
$string = $part['string'];
// @TODO: convert to utf8?
// @FIXME: set name + address or address only?
//rcube_utils::idn_to_utf8();
$addresses[$idx] = format_email_recipient($mailto, $name);
}
$value = implode(',', $addresses);
break;
case 'subject':
$value = $headers->get('subject');
break;
case 'charset':
$value = self::charset_to_cp($headers->charset);
break;
}
if (empty($value) || is_array($value)) {
continue;
}
$result[$key] = $value;
}
// $result['ConversationId'] = 'FF68022058BD485996BE15F6F6D99320';
// $result['ConversationIndex'] = 'CA2CFA8A23';
// Read flag
$result['Read'] = intval(!empty($headers->flags['SEEN']));
// Flagged message
if (!empty($headers->flags['FLAGGED'])) {
// Use FollowUp flag which is used in Android when message is marked with a star
$result['Flag'] = new Syncroton_Model_EmailFlag(array(
'FlagType' => 'FollowUp',
'Status' => Syncroton_Model_EmailFlag::STATUS_ACTIVE,
));
}
// Importance/Priority
if ($headers->priority) {
if ($headers->priority < 3) {
$result['importance'] = 2; // High
}
else if ($headers->priority > 3) {
$result['Importance'] = 0; // Low
}
}
// get truncation
$truncateAt = null;
$opts = $collection->options;
$prefs = $opts['bodyPreferences'];
if ($opts['mimeSupport'] == 2 && ($this->asversion <= 12 || !empty($prefs[4]))) {
if (!empty($prefs[4]) && !empty($prefs[4]['truncationSize'])) {
$truncateAt = $prefs[4]['truncationSize'];
}
$airSyncBaseType = 4;
}
else if (!empty($prefs[2])) {
if (!empty($prefs[2]['truncationSize'])) {
$truncateAt = $prefs[2]['truncationSize'];
}
$airSyncBaseType = 2;
}
else {
// @TODO: In Sync examples there's one in which bodyPreferences is not defined
// in such case Truncated=1 and there's no body sent to the client
// only it's estimated size
if (!empty($prefs[1]) && !empty($prefs[1]['truncationSize'])) {
$truncateAt = $prefs[1]['truncationSize'];
}
$airSyncBaseType = 1;
}
if (isset($opts['mimeTruncation']) && $opts['mimeTruncation'] < 8) {
switch ($opts['mimeTruncation']) {
case Syncroton_Command_Sync::TRUNCATE_ALL:
$truncateAt = 0;
break;
case Syncroton_Command_Sync::TRUNCATE_4096:
$truncateAt = 4096;
break;
case Syncroton_Command_Sync::TRUNCATE_5120:
$truncateAt = 5120;
break;
case Syncroton_Command_Sync::TRUNCATE_7168:
$truncateAt = 7168;
break;
case Syncroton_Command_Sync::TRUNCATE_10240:
$truncateAt = 10240;
break;
case Syncroton_Command_Sync::TRUNCATE_20480:
$truncateAt = 20480;
break;
case Syncroton_Command_Sync::TRUNCATE_51200:
$truncateAt = 51200;
break;
case Syncroton_Command_Sync::TRUNCATE_102400:
$truncateAt = 102400;
break;
}
}
// Message body
if ($airSyncBaseType == 4) {
$messageBody = $this->storage->get_raw_body($message->uid);
}
else {
$messageBody = $this->getMessageBody($message, $airSyncBaseType == 2);
}
// strip out any non utf-8 characters
$messageBody = rcube_charset::clean($messageBody);
$real_length = $body_length = strlen($messageBody);
if ($truncateAt !== null && $body_length > $truncateAt) {
$messageBody = substr($messageBody, 0, $truncateAt);
$body_length = $truncateAt;
$isTruncacted = 1;
}
else {
$isTruncacted = 0;
}
if ($body_length > 0) {
$body_params = array(
'Type' => $airSyncBaseType,
'Truncated' => $isTruncacted,
);
if ($isTruncated) {
$body_params['EstimatedDataSize'] = $real_length;
}
$result['Body'] = $this->setBody($messageBody, $body_params);
}
$result['NativeBodyType'] = intval($message->has_html_part(false));
// Message class
$result['MessageClass'] = 'IPM.Note' . ($airSyncBaseType == 4 ? '.SMIME' : '');
$result['ContentClass'] = 'urn:content-classes:message';
// attachments
$attachments = array_merge($message->attachments, $message->inline_parts);
if (!empty($attachments)) {
$result['Attachments'] = array();
foreach ($attachments as $attachment) {
$att = array();
$filename = $attachment->filename;
if (empty($filename) && $attachment->mimetype == 'text/html') {
$filename = 'HTML Part';
}
$att['DisplayName'] = $filename;
$att['FileReference'] = $serverId . '::' . $attachment->mime_id;
$att['Method'] = 1;
$att['EstimatedDataSize'] = $attachment->size;
if (!empty($attachment->content_id)) {
$att['ContentId'] = $attachment->content_id;
}
if (!empty($attachment->content_location)) {
$att['ContentLocation'] = $attachment->content_location;
}
if (in_array($attachment, $message->inline_parts)) {
$att['IsInline'] = 1;
}
$result['Attachments'][] = new Syncroton_Model_EmailAttachment($att);
}
}
return new Syncroton_Model_Email($result);
}
/**
* Returns properties of a message for Search response
*
* @param string $longId Message identifier
* @param array $options Search options
*
* @return Syncroton_Model_Email Email object
*/
public function getSearchEntry($longId, $options)
{
$collection = new Syncroton_Model_SyncCollection(array(
'options' => $options,
));
return $this->getEntry($collection, $longId);
}
/**
* convert contact from xml to libkolab array
*
* @param Syncroton_Model_IEntry $data Contact to convert
* @param string $folderid Folder identifier
* @param array $entry Existing entry
*
* @return array
*/
public function toKolab(Syncroton_Model_IEntry $data, $folderid, $entry = null)
{
// does nothing => you can't add emails via ActiveSync
}
/**
* Returns filter query array according to specified ActiveSync FilterType
*
* @param int $filter_type Filter type
*
* @param array Filter query
*/
protected function filter($filter_type = 0)
{
$filter = array();
switch ($filter_type) {
case Syncroton_Command_Sync::FILTER_1_DAY_BACK:
$mod = '-1 day';
break;
case Syncroton_Command_Sync::FILTER_3_DAYS_BACK:
$mod = '-3 days';
break;
case Syncroton_Command_Sync::FILTER_2_WEEKS_BACK:
$mod = '-2 weeks';
break;
case Syncroton_Command_Sync::FILTER_1_MONTH_BACK:
$mod = '-1 month';
break;
}
if (!empty($mod)) {
$dt = new DateTime('now', new DateTimeZone('UTC'));
$dt->modify($mod);
// RFC3501: IMAP SEARCH
$filter[] = 'SINCE ' . $dt->format('d-m-Y');
}
return $filter;
}
/**
* Returns default folder for current class type.
*/
protected function getDefaultFolder()
{
// There's always INBOX
$real_id = $this->backend->folder_id('INBOX', 'mail.inbox');
$folder_id = $this->defaultRootFolder;
return array(
'displayName' => 'Inbox',
'realid' => $real_id,
'serverId' => $folder_id,
'parentId' => 0,
'type' => $this->defaultFolderType,
);
}
public function moveItem($srcFolderId, $serverId, $dstFolderId)
{
$msg = $this->parseMessageId($serverId);
$dstname = $this->backend->folder_id2name($dstFolderId, $this->device->deviceid);
if ($dstname === null) {
return;
}
if (!$this->storage->move_message($msg['uid'], $dstname, $msg['foldername'])) {
return;
}
// Use COPYUID feature (RFC2359) to get the new UID of the copied message
$copyuid = $this->storage->conn->data['COPYUID'];
if (is_array($copyuid) && ($uid = $copyuid[1])) {
return $uid;
}
}
/**
* add entry from xml data
*
* @param string $folderId
* @param Syncroton_Model_IEntry $entry
*
* @return array
*/
public function createEntry($folderId, Syncroton_Model_IEntry $entry)
{
}
/**
* update existing entry
*
* @param string $folderId
* @param string $serverId
* @param Syncroton_Model_IEntry $entry
* @return array
*/
public function updateEntry($folderId, $serverId, Syncroton_Model_IEntry $entry)
{
$msg = $this->parseMessageId($serverId);
$message = $this->getObject($serverId);
$is_flagged = !empty($message->headers->flags['FLAGGED']);
// Read status change
if (isset($entry->Read)) {
// here we update only Read flag
$flag = (((int)$entry->Read != 1) ? 'UN' : '') . 'SEEN';
$this->storage->set_flag($msg['uid'], $flag, $msg['foldername']);
}
// Flag change
if (empty($entry->Flag)) {
if ($is_flagged) {
$this->storage->set_flag($msg['uid'], 'UNFLAGGED', $msg['foldername']);
}
}
else if (!$is_flagged && !empty($entry->Flag)) {
if ($entry->Flag->FlagType && preg_match('/^follow\s*up/i', $entry->Flag->FlagType)) {
$this->storage->set_flag($msg['uid'], 'FLAGGED', $msg['foldername']);
}
}
}
/**
* delete entry
*
* @param string $folderId
* @param string $serverId
* @param Syncroton_Model_SyncCollection $collection
*/
public function deleteEntry($folderId, $serverId, $collection)
{
$trash = kolab_sync::get_instance()->config->get('trash_mbox');
$msg = $this->parseMessageId($serverId);
// move message to trash folder
if ($collection->deletesAsMoves
&& strlen($trash)
&& $trash != $msg['foldername']
&& $this->storage->folder_exists($trash)
) {
$this->storage->move_message($msg['uid'], $trash, $msg['foldername']);
}
// set delete flag
else {
$this->storage->set_flag($msg['uid'], 'DELETED', $msg['foldername']);
}
}
public function sendEmail($body, $saveInSent)
{
$sent = $this->backend->send_message($body, $smtp_error);
if ($sent && $saveInSent) {
$this->saveInSent($body);
}
}
public function forwardEmail($itemId, $body, $saveInSent, $replaceMime)
{
/*
@TODO:
The SmartForward command can be applied to a meeting. When SmartForward is applied to a recurring meeting,
the InstanceId element (section 2.2.3.83.2) specifies the ID of a particular occurrence in the recurring meeting.
If SmartForward is applied to a recurring meeting and the InstanceId element is absent, the server SHOULD
forward the entire recurring meeting. If the value of the InstanceId element is invalid, the server responds
with Status element (section 2.2.3.162.15) value 104, as specified in section 2.2.4.
When the SmartForward command is used for an appointment, the original message is included by the server
as an attachment to the outgoing message. When the SmartForward command is used for a normal message
or a meeting, the behavior of the SmartForward command is the same as that of the SmartReply command (section 2.2.2.18).
*/
$msg = $this->parseMessageId($itemId);
$message = $this->getObject($itemId);
// Parse message
list($headers, $body) = $this->backend->parse_mime($body, true);
// Get original message body
// @TODO: here we're assuming that reply message is in text/plain format
// So, original message will be converted to plain text if needed
// @TODO: what about forward-as-attachment?
$message_body = $this->getMessageBody($message, false);
$message_body = trim($message_body);
// Join bodies
$body = rtrim($body) . "\n\n" . ltrim($message_body);
// @TODO: add attachments from the original message
// Create complete message source
$body = $this->backend->build_mime($headers, $body);
// Send message
$sent = $this->backend->send_message($body, $smtp_error);
// Set FORWARDED flag on the replied message
if ($sent && empty($message->headers->flags['FORWARDED'])) {
$this->storage->set_flag($msg['uid'], 'FORWARDED', $msg['foldername']);
}
// Save sent message in Sent folder
if ($sent && $saveInSent) {
$this->saveInSent($body);
}
}
public function replyEmail($itemId, $body, $saveInSent, $replaceMime)
{
$msg = $this->parseMessageId($itemId);
$message = $this->getObject($itemId);
// Parse message
// @TODO: messages with attachments
list($headers, $body) = $this->backend->parse_mime($body, true);
// Get original message body
// @TODO: here we're assuming that reply message is in text/plain format
// So, original message will be converted to plain text if needed
$message_body = $this->getMessageBody($message, false);
// Quote original message body
$message_body = self::wrap_and_quote(trim($message_body), 72);
// Join bodies
$body = rtrim($body) . "\n" . ltrim($message_body);
// Create complete message source
$body = $this->backend->build_mime($headers, $body);
// Send message
$sent = $this->backend->send_message($body, $smtp_error);
// Set ANSWERED flag on the replied message
if ($sent && empty($message->headers->flags['ANSWERED'])) {
$this->storage->set_flag($msg['uid'], 'ANSWERED', $msg['foldername']);
}
// Save sent message in Sent folder
if ($sent && $saveInSent) {
$this->saveInSent($body);
}
}
/**
* Append the message into Sent folder
*
* @param string $message Complete message source
*
* @return int|bool Appended message UID or True on success, False on error
*/
protected function saveInSent($message)
{
$sent_folder = kolab_sync::get_instance()->config->get('sent_mbox');
if (strlen($sent_folder) && $this->storage->folder_exists($sent_folder)) {
return $this->storage->save_message($sent_folder, $message);
}
return false;
}
/**
* Search for existing entries
*
* @param string $folderid
* @param array $filter
* @param int $result_type Type of the result (see RESULT_* constants)
*
* @return array|int Search result as count or array of uids/objects
*/
protected function searchEntries($folderid, $filter = array(), $result_type = self::RESULT_UID)
{
if ($folderid == $this->defaultRootFolder) {
$folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
$folders = array_keys($folders);
}
else {
$folders = array($folderid);
}
$filter_str = 'ALL UNDELETED';
// convert filter into one IMAP search string
foreach ($filter as $idx => $filter_item) {
if (is_array($filter_item)) {
// @TODO
// convert 'changed' entries into IMAP search string
// for now we just return empty result
return $result_type == self::RESULT_COUNT ? 0 : array();
}
else {
$filter_str .= ' ' . $filter_item;
}
}
$result = $result_type == self::RESULT_COUNT ? 0 : array();
// no sorting for best performance
$sort_by = null;
foreach ($folders as $folderid) {
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
if ($foldername === null) {
continue;
}
// $this->storage->set_folder($foldername);
$this->storage->folder_sync($foldername);
$search = $this->storage->search_once($foldername, $filter_str);
if (!($search instanceof rcube_result_index)) {
continue;
}
switch ($result_type) {
case self::RESULT_COUNT:
$result += (int) $search->count();
break;
case self::RESULT_UID:
if ($uids = $search->get()) {
foreach ($uids as $idx => $uid) {
$uids[$idx] = $this->createMessageId($folderid, $uid);
}
$result = array_merge($result, $uids);
}
break;
/*
case self::RESULT_OBJECT:
default:
if ($objects = $folder->select($filter)) {
$result = array_merge($result, $objects);
}
*/
}
}
return $result;
}
/**
* ActiveSync Search handler
*
* @param array $query Search query parameters
* @param array $options Search options
*
* @return array List of Syncroton_Model_StoreResponseResult objects
*/
public function search($query, $options)
{
list ($folders, $search_str) = $this->parse_search_query($query, $options);
if (empty($search_str)) {
return array();
}
$result = array();
// no sorting for best performance
$sort_by = null;
// @TODO: caching with Options->RebuildResults support
foreach ($folders as $folderid) {
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
if ($foldername === null) {
continue;
}
// $this->storage->set_folder($foldername);
$this->storage->folder_sync($foldername);
$search = $this->storage->search_once($foldername, $search_str);
if (!($search instanceof rcube_result_index)) {
continue;
}
$uids = $search->get();
foreach ($uids as $idx => $uid) {
$uids[$idx] = new Syncroton_Model_StoreResponseResult(array(
'LongId' => $this->createMessageId($folderid, $uid),
'CollectionId' => $folderid,
'Class' => 'Email',
));
}
$result = array_merge($result, $uids);
// We don't want to search all folders if we've got already a lot messages
if (count($result) >= self::MAX_SEARCH_RESULT) {
break;
}
}
return array_values($result);
}
/**
* Converts ActiveSync search parameters into IMAP search string
*/
protected function parse_search_query($query, $options)
{
if (empty($query)) {
return array();
}
$search_str = '';
$folders = array();
if (!is_array($query)) {
$search = $query;
}
else {
if (isset($query['and']['freeText']) && strlen($query['and']['freeText'])) {
$search = $query['and']['freeText'];
}
if (!empty($query['and']['collections'])) {
$folders[] = $query['and']['collections'];
}
if (!empty($query['and']['greaterThan'])
&& !empty($query['and']['greaterThan']['dateReceived'])
&& !empty($query['and']['greaterThan']['value'])
) {
$search_str .= ' SINCE ' . trim($query['and']['greaterThan']['value']);
}
if (!empty($query['and']['lessThan'])
&& !empty($query['and']['lessThan']['dateReceived'])
&& !empty($query['and']['lessThan']['value'])
) {
$search_str .= ' BEFORE ' . trim($query['and']['lessThan']['value']);
}
}
if ($search !== null) {
// @FIXME: should we use TEXT/BODY search?
$search_keys = array('SUBJECT', 'TO', 'FROM', 'CC');
$search_str .= str_repeat(' OR', count($search_keys)-1);
foreach ($search_keys as $key) {
$search_str .= sprintf(" %s {%d}\r\n%s", $key, strlen($search), $search);
}
}
if (empty($search_str)) {
return array();
}
$search_str = 'ALL UNDELETED ' . trim($search_str);
// @TODO: DeepTraversal
if (empty($folders)) {
$folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
$folders = array_keys($folders);
}
return array($folders, $search_str);
}
/**
* Fetches the entry from the backend
*/
protected function getObject($entryid, &$folder = null)
{
$message = $this->parseMessageId($entryid);
if (empty($message)) {
// @TODO: exception?
return null;
}
// set current folder
$this->storage->set_folder($message['foldername']);
// get message
$message = new rcube_message($message['uid']);
return $message;
}
/**
* @return Syncroton_Model_FileReference
*/
public function getFileReference($fileReference)
{
list($folderid, $uid, $part_id) = explode('::', $fileReference);
$message = $this->getObject($fileReference);
$part = $message->mime_parts[$part_id];
$body = $message->get_part_content($part_id);
$content_type = $part->mimetype;
return new Syncroton_Model_FileReference(array(
'ContentType' => $content_type,
'Data' => $body,
));
}
/**
* Parses entry ID to get folder name and UID of the message
*/
protected function parseMessageId($entryid)
{
// replyEmail/forwardEmail
if (is_array($entryid)) {
$entryid = $entryid['itemId'];
}
list($folderid, $uid) = explode('::', $entryid);
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
if ($foldername === null || $foldername === false) {
// @TODO exception?
return null;
}
return array(
'uid' => $uid,
'folderid' => $folderid,
'foldername' => $foldername,
);
}
/**
* Creates entry ID of the message
*/
public function createMessageId($folderid, $uid)
{
return $folderid . '::' . $uid;
}
/**
* Returns body of the message in specified format
*/
protected function getMessageBody($message, $html = false)
{
if (!is_array($message->parts) && empty($message->body)) {
return '';
}
if (!empty($message->parts)) {
foreach ($message->parts as $part) {
// skip no-content and attachment parts (#1488557)
if ($part->type != 'content' || !$part->size || $message->is_attachment($part)) {
continue;
}
return $this->getMessagePartBody($message, $part, $html);
}
}
return $this->getMessagePartBody($message, $message, $html);
}
/**
* Returns body of the message part in specified format
*/
protected function getMessagePartBody($message, $part, $html = false)
{
// Check if we have enough memory to handle the message in it
// @FIXME: we need up to 5x more memory than the body
if (!rcube_utils::mem_check($part->size * 5)) {
return '';
}
if (empty($part->ctype_parameters) || empty($part->ctype_parameters['charset'])) {
$part->ctype_parameters['charset'] = $message->headers->charset;
}
// fetch part if not available
if (!isset($part->body)) {
$part->body = $message->get_part_content($part->mime_id);
}
// message is cached but not exists, or other error
if ($part->body === false) {
return '';
}
$body = $part->body;
if ($html) {
if ($part->ctype_secondary == 'html') {
}
else {
$body = '<pre>' . $body . '</pre>';
}
}
else {
if ($part->ctype_secondary == 'html') {
$txt = new html2text($body, false, true);
$body = $txt->get_text();
}
else {
if ($part->ctype_secondary == 'plain' && $part->ctype_parameters['format'] == 'flowed') {
$body = rcube_mime::unfold_flowed($body);
}
}
}
return $body;
}
public static function charset_to_cp($charset)
{
// @TODO: ?????
// The body is converted to utf-8 in get_part_content(), what about headers?
return 65001; // UTF-8
$aliases = array(
'asmo708' => 708,
'shiftjis' => 932,
'gb2312' => 936,
'ksc56011987' => 949,
'big5' => 950,
'utf16' => 1200,
'utf16le' => 1200,
'unicodefffe' => 1201,
'utf16be' => 1201,
'johab' => 1361,
'macintosh' => 10000,
'macjapanese' => 10001,
'macchinesetrad' => 10002,
'mackorean' => 10003,
'macarabic' => 10004,
'machebrew' => 10005,
'macgreek' => 10006,
'maccyrillic' => 10007,
'macchinesesimp' => 10008,
'macromanian' => 10010,
'macukrainian' => 10017,
'macthai' => 10021,
'macce' => 10029,
'macicelandic' => 10079,
'macturkish' => 10081,
'maccroatian' => 10082,
'utf32' => 12000,
'utf32be' => 12001,
'chinesecns' => 20000,
'chineseeten' => 20002,
'ia5' => 20105,
'ia5german' => 20106,
'ia5swedish' => 20107,
'ia5norwegian' => 20108,
'usascii' => 20127,
'ibm273' => 20273,
'ibm277' => 20277,
'ibm278' => 20278,
'ibm280' => 20280,
'ibm284' => 20284,
'ibm285' => 20285,
'ibm290' => 20290,
'ibm297' => 20297,
'ibm420' => 20420,
'ibm423' => 20423,
'ibm424' => 20424,
'ebcdickoreanextended' => 20833,
'ibmthai' => 20838,
'koi8r' => 20866,
'ibm871' => 20871,
'ibm880' => 20880,
'ibm905' => 20905,
'ibm00924' => 20924,
'cp1025' => 21025,
'koi8u' => 21866,
'iso88591' => 28591,
'iso88592' => 28592,
'iso88593' => 28593,
'iso88594' => 28594,
'iso88595' => 28595,
'iso88596' => 28596,
'iso88597' => 28597,
'iso88598' => 28598,
'iso88599' => 28599,
'iso885913' => 28603,
'iso885915' => 28605,
'xeuropa' => 29001,
'iso88598i' => 38598,
'iso2022jp' => 50220,
'csiso2022jp' => 50221,
'iso2022jp' => 50222,
'iso2022kr' => 50225,
'eucjp' => 51932,
'euccn' => 51936,
'euckr' => 51949,
'hzgb2312' => 52936,
'gb18030' => 54936,
'isciide' => 57002,
'isciibe' => 57003,
'isciita' => 57004,
'isciite' => 57005,
'isciias' => 57006,
'isciior' => 57007,
'isciika' => 57008,
'isciima' => 57009,
'isciigu' => 57010,
'isciipa' => 57011,
'utf7' => 65000,
'utf8' => 65001,
);
$charset = strtolower($charset);
$charset = preg_replace(array('/^x-/', '/[^a-z0-9]/'), '', $charset);
if (isset($aliases[$charset])) {
return $aliases[$charset];
}
if (preg_match('/^(ibm|dos|cp|windows|win)[0-9]+/', $charset, $m)) {
return substr($charset, strlen($m[1]) + 1);
}
}
/**
* Wrap text to a given number of characters per line
* but respect the mail quotation of replies messages (>).
* Finally add another quotation level by prepending the lines
* with >
*
* @param string $text Text to wrap
* @param int $length The line width
*
* @return string The wrapped text
*/
protected static function wrap_and_quote($text, $length = 72)
{
// Function stolen from Roundcube ;)
// Rebuild the message body with a maximum of $max chars, while keeping quoted message.
$max = min(77, $length + 8);
$lines = preg_split('/\r?\n/', trim($text));
$out = '';
foreach ($lines as $line) {
// don't wrap already quoted lines
if ($line[0] == '>') {
$line = '>' . rtrim($line);
}
else if (mb_strlen($line) > $max) {
$newline = '';
foreach (explode("\n", rcube_mime::wordwrap($line, $length - 2)) as $l) {
if (strlen($l)) {
$newline .= '> ' . $l . "\n";
}
else {
$newline .= ">\n";
}
}
$line = rtrim($newline);
}
else {
$line = '> ' . $line;
}
// Append the line
$out .= $line . "\n";
}
return $out;
}
}
diff --git a/lib/kolab_sync_data_gal.php b/lib/kolab_sync_data_gal.php
index 66d06da..e830790 100644
--- a/lib/kolab_sync_data_gal.php
+++ b/lib/kolab_sync_data_gal.php
@@ -1,391 +1,383 @@
<?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> |
+--------------------------------------------------------------------------+
*/
/**
- * GAL (Global Address List) data backend
+ * GAL (Global Address List) data backend for Syncroton
*/
class kolab_sync_data_gal extends kolab_sync_data implements Syncroton_Data_IDataSearch
{
const MAX_SEARCH_RESULT = 100;
/**
* LDAP search result
*
* @var array
*/
protected $result = array();
/**
* LDAP address books list
*
* @var array
*/
protected $address_books = array();
/**
* Mapping from ActiveSync Contacts namespace fields
*/
protected $mapping = array(
'Alias' => 'nickname',
'Company' => 'organization',
'DisplayName' => 'name',
'EmailAddress' => 'email',
'FirstName' => 'firstname',
'LastName' => 'surname',
'MobilePhone' => 'phone.mobile',
'Office' => 'office',
'Phone' => 'phone',
'Title' => 'jobtitle',
'Picture' => 'photo',
);
/**
* Kolab object type
*
* @var string
*/
protected $modelName = 'contact';
/**
* Type of the default folder
*
* @var int
*/
protected $defaultFolderType = Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT;
/**
* Default container for new entries
*
* @var string
*/
protected $defaultFolder = 'Contacts';
/**
* Type of user created folders
*
* @var int
*/
protected $folderType = Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED;
- /**
- * Default namespace
- *
- * @var string
- */
- protected $defaultNS = 'Contacts';
-
-
/**
* the constructor
*
* @param Syncroton_Model_IDevice $device
* @param DateTime $syncTimeStamp
*/
public function __construct(Syncroton_Model_IDevice $device, DateTime $syncTimeStamp)
{
parent::__construct($device, $syncTimeStamp);
// Use configured fields mapping
$rcube = rcube::get_instance();
$fieldmap = (array) $rcube->config->get('activesync_gal_fieldmap');
if (!empty($fieldmap)) {
$fieldmap = array_intersec_key($fieldmap, array_keys($this->mapping));
$this->mapping = array_merge($this->mapping, $fieldmap);
}
}
/**
* Not used but required by parent class
*/
public function toKolab(Syncroton_Model_IEntry $data, $folderId, $entry = null)
{
}
/**
* Not used but required by parent class
*/
public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId)
{
}
/**
* Returns properties of a message for Search response
*
* @param string $longId Message identifier
* @param array $options Search options
*
* @return Syncroton_Model_Email Email object
*/
public function getSearchEntry($longId, $options)
{
$data = $this->getObject($longId);
$result = array();
// Contacts namespace fields
foreach ($this->mapping as $key => $name) {
$value = $this->getLDAPDataItem($data, $name);
if (empty($value) || is_array($value)) {
continue;
}
switch ($name) {
case 'photo':
// @TODO: MaxPictures option
// ActiveSync limits photo size of GAL contact to 100KB
$maxsize = 102400;
if (!empty($options['picture']['maxSize'])) {
$maxsize = min($maxsize, $options['picture']['maxSize']);
}
if (strlen($value) > $maxsize) {
continue;
}
$value = new Syncroton_Model_GALPicture(array(
'Data' => $value, // binary
'Status' => Syncroton_Model_GALPicture::STATUS_SUCCESS,
));
break;
}
$result[$key] = $value;
}
return new Syncroton_Model_GAL($result);
}
/**
* ActiveSync Search handler
*
* @param string|array $query Search query parameters
* @param array $options Search options
*
* @return array List of Syncroton_Model_StoreResponseResult objects
*/
public function search($query, $options)
{
if (empty($query) || !is_string($query)) {
return array();
}
$records = array();
$rcube = rcube::get_instance();
// @TODO: caching with Options->RebuildResults support
$books = $this->get_address_sources();
$mode = 2; // use prefix mode
$fields = $rcube->config->get('contactlist_fields');
if (empty($fields)) {
$fields = '*';
}
foreach ($books as $idx => $book) {
$book = $this->get_address_book($idx);
if (!$book) {
continue;
}
$book->set_page(1);
$book->set_pagesize(self::MAX_SEARCH_RESULT);
$result = $book->search($fields, $query, $mode, true, true, 'email');
if (!$result->count) {
continue;
}
// get records
$result = $book->list_records();
while ($row = $result->next()) {
$row['sourceid'] = $idx;
$key = $this->contact_key($row);
unset($row['_raw_attrib']); // save some memory, @TODO: do this in rcube_ldap
$records[$key] = $row;
}
// We don't want to search all sources if we've got already a lot of contacts
if (count($records) >= self::MAX_SEARCH_RESULT) {
break;
}
}
// sort the records
ksort($records, SORT_LOCALE_STRING);
$result = array();
foreach ($records as $idx => $rec) {
$longId = $rec['ID'];
$result[] = new Syncroton_Model_StoreResponseResult(array(
'LongId' => $longId,
));
$this->result[$longId] = $rec;
unset($records[$idx]);
}
return $result;
}
/**
* Return contact object from LDAP
*
* @param string $id Contact identifier
*
* @return array Object data
*/
protected function getObject($id)
{
return $this->result[$id];
}
/**
* Return instance of the internal address book class
*
* @param string $id Address book identifier
*
* @return rcube_contacts Address book object
*/
protected function get_address_book($id)
{
$contacts = null;
$config = rcube::get_instance()->config;
$ldap_config = (array) $config->get('ldap_public');
// use existing instance
if (isset($this->address_books[$id]) && ($this->address_books[$id] instanceof rcube_addressbook)) {
$book = $this->address_books[$id];
}
else if ($id && $ldap_config[$id]) {
$book = new rcube_ldap($ldap_config[$id], $config->get('ldap_debug'),
$config->mail_domain($_SESSION['storage_host']));
}
if (!$book) {
rcube::raise_error(array(
'code' => 700, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Addressbook source ($id) not found!"),
true, false);
return null;
}
/*
// set configured sort order
if ($sort_col = $this->config->get('addressbook_sort_col'))
$contacts->set_sort_order($sort_col);
*/
// add to the 'books' array for shutdown function
$this->address_books[$id] = $book;
return $book;
}
/**
* Return LDAP address books list
*
* @return array Address books array
*/
protected function get_address_sources()
{
$config = rcube::get_instance()->config;
$ldap_config = (array) $config->get('ldap_public');
$async_books = $config->get('activesync_addressbooks');
if ($async_books === null) {
$async_books = (array) $config->get('autocomplete_addressbooks');
}
$list = array();
foreach ((array)$async_books as $id) {
$prop = $ldap_config[$id];
// handle misconfiguration
if (empty($prop) || !is_array($prop)) {
continue;
}
$list[$id] = array(
'id' => $id,
'name' => $prop['name'],
);
/*
// register source for shutdown function
if (!is_object($this->address_books[$id]))
$this->address_books[$id] = $list[$id];
}
*/
}
return $list;
}
/**
* Creates contact key for sorting by
*/
protected function contact_key($row)
{
$key = $row['name'] . ':' . $row['sourceid'];
// add email to a key to not skip contacts with the same name
if (!empty($row['email'])) {
if (is_array($row['email'])) {
$key .= ':' . implode(':', $row['email']);
}
else {
$key .= ':' . $row['email'];
}
}
return $key;
}
/**
* Extracts data from Roundcube LDAP data array
*/
protected function getLDAPDataItem($data, $name)
{
list($name, $index) = explode(':', $name);
$name = str_replace('.', ':', $name);
if (isset($data[$name])) {
if ($index) {
return is_array($data[$name]) ? $data[$name][$index] : null;
}
return is_array($data[$name]) ? array_shift($data[$name]) : $data[$name];
}
return null;
}
}
diff --git a/lib/kolab_sync_data_tasks.php b/lib/kolab_sync_data_tasks.php
index 099150a..be94f59 100644
--- a/lib/kolab_sync_data_tasks.php
+++ b/lib/kolab_sync_data_tasks.php
@@ -1,249 +1,242 @@
<?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> |
+--------------------------------------------------------------------------+
*/
/**
- *
+ * Tasks data class for Syncroton
*/
class kolab_sync_data_tasks extends kolab_sync_data
{
/**
* Mapping from ActiveSync Calendar namespace fields
*/
protected $mapping = array(
'Body' => 'description',
'Categories' => 'categories',
//'Complete' => 'complete', // handled separately
'DateCompleted' => 'changed',
'DueDate' => 'due',
'Importance' => 'priority',
//'Recurrence' => 'recurrence',
//'ReminderSet' => 'reminderset',
//'ReminderTime' => 'remindertime',
'Sensitivity' => 'sensitivity',
'StartDate' => 'start',
'Subject' => 'title',
'UtcDueDate' => 'due',
'UtcStartDate' => 'start',
);
/**
* Sensitivity values
*/
const SENSITIVITY_NORMAL = 0;
const SENSITIVITY_PERSONAL = 1;
const SENSITIVITY_PRIVATE = 2;
const SENSITIVITY_CONFIDENTIAL = 3;
/**
* mapping of sensitivity
*
* @var array
*/
protected $sensitivityMap = array(
'public' => self::SENSITIVITY_PERSONAL,
'private' => self::SENSITIVITY_PRIVATE,
'confidential' => self::SENSITIVITY_CONFIDENTIAL,
);
/**
* Kolab object type
*
* @var string
*/
protected $modelName = 'task';
/**
* Type of the default folder
*
* @var int
*/
protected $defaultFolderType = Syncroton_Command_FolderSync::FOLDERTYPE_TASK;
/**
* Default container for new entries
*
* @var string
*/
protected $defaultFolder = 'Tasks';
/**
* Type of user created folders
*
* @var int
*/
protected $folderType = Syncroton_Command_FolderSync::FOLDERTYPE_TASK_USER_CREATED;
- /**
- * Default namespace
- *
- * @var string
- */
- protected $defaultNS = 'Tasks';
-
/**
* Appends contact data to xml element
*
* @param Syncroton_Model_SyncCollection $collection Collection data
* @param string $serverId Local entry identifier
*/
public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId)
{
$task = is_array($serverId) ? $serverId : $this->getObject($collection->collectionId, $serverId);
$config = $this->getFolderConfig($task['_mailbox']);
$result = array();
// Completion status (required)
$result['Complete'] = intval(!empty($task['status']) && $task['status'] == 'COMPLETED');
// Calendar namespace fields
foreach ($this->mapping as $key => $name) {
$value = $this->getKolabDataItem($task, $name);
switch ($name) {
case 'due':
case 'start':
if (preg_match('/^UTC/i', $key)) {
$value = self::date_from_kolab($value);
}
break;
case 'changed':
$value = $result['Complete'] ? self::date_from_kolab($value) : null;
break;
case 'description':
$value = $this->setBody($value);
break;
case 'sensitivity':
$value = intval($this->sensitivityMap[$value]);
break;
case 'priority':
// ActiveSync has only 3 levels of importance:
// 0 - Low, 1 - Normal, 2 - High
// but Kolab uses ten levels:
// 0 - unknown and 1-9 where 1 is the highest
if ($value) {
$result['Importance'] = $value > 5 ? 2 : 0;
}
break;
}
if (empty($value) || is_array($value)) {
continue;
}
$result[$key] = $value;
}
// Recurrence
$result['Recurrence'] = $this->recurrence_from_kolab($task, 'Task');
return new Syncroton_Model_Task($result);
}
/**
* convert contact from xml to libkolab array
*
* @param Syncroton_Model_IEntry $data Contact to convert
* @param string $folderid Folder identifier
* @param array $entry Existing entry
*
* @return array
*/
public function toKolab(Syncroton_Model_IEntry $data, $folderid, $entry = null)
{
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
$task = !empty($entry) ? $entry : array();
$config = $this->getFolderConfig($foldername);
$task['allday'] = 0;
// Calendar namespace fields
foreach ($this->mapping as $key => $name) {
$value = $data->$key;
switch ($name) {
case 'sensitivity':
$map = array_flip($this->sensitivityMap);
$value = $map[$value];
break;
case 'description':
$value = $this->getBody($value);
// If description isn't specified keep old description
if ($value === null) {
continue 2;
}
break;
case 'priority':
if ($value !== null) {
if ($value == 1) {
$task['priority'] = 0;
}
else {
$task['priority'] = !$value ? 9 : 1;
}
}
break;
}
$this->setKolabDataItem($task, $name, $value);
}
if (!empty($data->Complete)) {
$task['status'] = 'COMPLETED';
$task['complete'] = 100;
}
// recurrence
$task['recurrence'] = $this->recurrence_to_kolab($data);
return $task;
}
/**
* Returns filter query array according to specified ActiveSync FilterType
*
* @param int $filter_type Filter type
*
* @param array Filter query
*/
protected function filter($filter_type = 0)
{
$filter = array();
if ($filter_type == Syncroton_Command_Sync::FILTER_INCOMPLETE) {
$filter[] = array('tags', '!~', 'x-complete');
}
return $filter;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Apr 18, 8:18 AM (1 h, 1 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
435595
Default Alt Text
(125 KB)

Event Timeline