Page MenuHomePhorge

No OneTemporary

Size
103 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/ext/Syncroton/Server.php b/lib/ext/Syncroton/Server.php
index a938ffb..8c3ccea 100644
--- a/lib/ext/Syncroton/Server.php
+++ b/lib/ext/Syncroton/Server.php
@@ -1,402 +1,402 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle incoming http ActiveSync requests
*
* @package Syncroton
*/
class Syncroton_Server
{
const PARAMETER_ATTACHMENTNAME = 0;
const PARAMETER_COLLECTIONID = 1;
const PARAMETER_ITEMID = 3;
const PARAMETER_OPTIONS = 7;
protected $_body;
/**
* informations about the currently device
*
* @var Syncroton_Backend_IDevice
*/
protected $_deviceBackend;
/**
* @var Zend_Log
*/
protected $_logger;
/**
* @var Zend_Controller_Request_Http
*/
protected $_request;
protected $_userId;
public function __construct($userId, Zend_Controller_Request_Http $request = null, $body = null)
{
if (Syncroton_Registry::isRegistered('loggerBackend')) {
$this->_logger = Syncroton_Registry::get('loggerBackend');
}
$this->_userId = $userId;
$this->_request = $request instanceof Zend_Controller_Request_Http ? $request : new Zend_Controller_Request_Http();
$this->_body = $body !== null ? $body : fopen('php://input', 'r');
$this->_deviceBackend = Syncroton_Registry::getDeviceBackend();
}
public function handle()
{
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . ' REQUEST METHOD: ' . $this->_request->getMethod());
switch($this->_request->getMethod()) {
case 'OPTIONS':
$this->_handleOptions();
break;
case 'POST':
$this->_handlePost();
break;
case 'GET':
echo "It works!<br>Your userid is: {$this->_userId} and your IP address is: {$_SERVER['REMOTE_ADDR']}.";
break;
}
}
/**
* handle options request
*/
protected function _handleOptions()
{
$command = new Syncroton_Command_Options();
-
+
$this->_sendHeaders($command->getHeaders());
}
protected function _sendHeaders(array $headers)
{
foreach ($headers as $name => $value) {
header($name . ': ' . $value);
}
}
/**
* handle post request
*/
protected function _handlePost()
{
$requestParameters = $this->_getRequestParameters($this->_request);
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . ' REQUEST ' . print_r($requestParameters, true));
$className = 'Syncroton_Command_' . $requestParameters['command'];
if(!class_exists($className)) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " command not supported: " . $requestParameters['command']);
header("HTTP/1.1 501 not implemented");
return;
}
// get user device
$device = $this->_getUserDevice($this->_userId, $requestParameters);
if ($requestParameters['contentType'] == 'application/vnd.ms-sync.wbxml' || $requestParameters['contentType'] == 'application/vnd.ms-sync') {
// decode wbxml request
try {
$decoder = new Syncroton_Wbxml_Decoder($this->_body);
$requestBody = $decoder->decode();
if ($this->_logger instanceof Zend_Log) {
$requestBody->formatOutput = true;
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " xml request:\n" . $requestBody->saveXML());
}
} catch(Syncroton_Wbxml_Exception_UnexpectedEndOfFile $e) {
$requestBody = NULL;
}
} else {
$requestBody = $this->_body;
}
header("MS-Server-ActiveSync: 14.00.0536.000");
try {
$command = new $className($requestBody, $device, $requestParameters);
$command->handle();
$response = $command->getResponse();
} catch (Syncroton_Exception_ProvisioningNeeded $sepn) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " provisioning needed");
if (version_compare($device->acsversion, '14.0', '>=')) {
$response = $sepn->domDocument;
} else {
// pre 14.0 method
header("HTTP/1.1 449 Retry after sending a PROVISION command");
return;
}
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " unexpected exception occured: " . get_class($e));
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " exception message: " . $e->getMessage());
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " " . $e->getTraceAsString());
header("HTTP/1.1 500 Internal server error");
return;
}
if ($response instanceof DOMDocument) {
if ($this->_logger instanceof Zend_Log) {
$response->formatOutput = true;
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " xml response:\n" . $response->saveXML());
$response->formatOutput = false;
}
if (isset($command) && $command instanceof Syncroton_Command_ICommand) {
$this->_sendHeaders($command->getHeaders());
}
$outputStream = fopen("php://temp", 'r+');
$encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3);
$encoder->encode($response);
if ($requestParameters['acceptMultipart'] == true) {
$parts = $command->getParts();
// output multipartheader
$bodyPartCount = 1 + count($parts);
// number of parts (4 bytes)
$header = pack('i', $bodyPartCount);
$partOffset = 4 + (($bodyPartCount * 2) * 4);
// wbxml body start and length
$streamStat = fstat($outputStream);
$header .= pack('ii', $partOffset, $streamStat['size']);
$partOffset += $streamStat['size'];
// calculate start and length of parts
foreach ($parts as $partId => $partStream) {
rewind($partStream);
$streamStat = fstat($partStream);
// part start and length
$header .= pack('ii', $partOffset, $streamStat['size']);
$partOffset += $streamStat['size'];
}
echo $header;
}
// output body
rewind($outputStream);
fpassthru($outputStream);
// output multiparts
if (isset($parts)) {
foreach ($parts as $partStream) {
rewind($partStream);
fpassthru($partStream);
}
}
}
}
/**
* return request params
*
* @return array
*/
protected function _getRequestParameters(Zend_Controller_Request_Http $request)
{
if (strpos($request->getRequestUri(), '&') === false) {
$commands = array(
0 => 'Sync',
1 => 'SendMail',
2 => 'SmartForward',
3 => 'SmartReply',
4 => 'GetAttachment',
9 => 'FolderSync',
10 => 'FolderCreate',
11 => 'FolderDelete',
12 => 'FolderUpdate',
13 => 'MoveItems',
14 => 'GetItemEstimate',
15 => 'MeetingResponse',
16 => 'Search',
17 => 'Settings',
18 => 'Ping',
19 => 'ItemOperations',
20 => 'Provision',
21 => 'ResolveRecipients',
22 => 'ValidateCert'
);
$requestParameters = substr($request->getRequestUri(), strpos($request->getRequestUri(), '?'));
$stream = fopen("php://temp", 'r+');
fwrite($stream, base64_decode($requestParameters));
rewind($stream);
// unpack the first 4 bytes
$unpacked = unpack('CprotocolVersion/Ccommand/vlocale', fread($stream, 4));
// 140 => 14.0
$protocolVersion = substr($unpacked['protocolVersion'], 0, -1) . '.' . substr($unpacked['protocolVersion'], -1);
$command = $commands[$unpacked['command']];
$locale = $unpacked['locale'];
// unpack deviceId
$length = ord(fread($stream, 1));
if ($length > 0) {
$toUnpack = fread($stream, $length);
$unpacked = unpack("H" . ($length * 2) . "string", $toUnpack);
$deviceId = $unpacked['string'];
}
// unpack policyKey
$length = ord(fread($stream, 1));
if ($length > 0) {
$unpacked = unpack('Vstring', fread($stream, $length));
$policyKey = $unpacked['string'];
}
// unpack device type
$length = ord(fread($stream, 1));
if ($length > 0) {
$unpacked = unpack('A' . $length . 'string', fread($stream, $length));
$deviceType = $unpacked['string'];
}
while (! feof($stream)) {
$tag = ord(fread($stream, 1));
$length = ord(fread($stream, 1));
switch ($tag) {
case self::PARAMETER_ATTACHMENTNAME:
$unpacked = unpack('A' . $length . 'string', fread($stream, $length));
$attachmentName = $unpacked['string'];
break;
case self::PARAMETER_COLLECTIONID:
$unpacked = unpack('A' . $length . 'string', fread($stream, $length));
$collectionId = $unpacked['string'];
break;
case self::PARAMETER_ITEMID:
$unpacked = unpack('A' . $length . 'string', fread($stream, $length));
$itemId = $unpacked['string'];
break;
case self::PARAMETER_OPTIONS:
$options = ord(fread($stream, 1));
$saveInSent = !!($options & 0x01);
$acceptMultiPart = !!($options & 0x02);
break;
default:
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " found unhandled command parameters");
}
}
$result = array(
'protocolVersion' => $protocolVersion,
'command' => $command,
'deviceId' => $deviceId,
'deviceType' => isset($deviceType) ? $deviceType : null,
'policyKey' => isset($policyKey) ? $policyKey : null,
'saveInSent' => isset($saveInSent) ? $saveInSent : false,
'collectionId' => isset($collectionId) ? $collectionId : null,
'itemId' => isset($itemId) ? $itemId : null,
'attachmentName' => isset($attachmentName) ? $attachmentName : null,
'acceptMultipart' => isset($acceptMultiPart) ? $acceptMultiPart : false
);
} else {
$result = array(
'protocolVersion' => $request->getServer('HTTP_MS_ASPROTOCOLVERSION'),
'command' => $request->getQuery('Cmd'),
'deviceId' => $request->getQuery('DeviceId'),
'deviceType' => $request->getQuery('DeviceType'),
'policyKey' => $request->getServer('HTTP_X_MS_POLICYKEY'),
'saveInSent' => $request->getQuery('SaveInSent') == 'T',
'collectionId' => $request->getQuery('CollectionId'),
'itemId' => $request->getQuery('ItemId'),
'attachmentName' => $request->getQuery('AttachmentName'),
'acceptMultipart' => $request->getServer('HTTP_MS_ASACCEPTMULTIPART') == 'T'
);
}
$result['userAgent'] = $request->getServer('HTTP_USER_AGENT', $result['deviceType']);
$result['contentType'] = $request->getServer('CONTENT_TYPE');
return $result;
}
/**
* get existing device of owner or create new device for owner
*
* @param unknown_type $ownerId
* @param unknown_type $deviceId
* @param unknown_type $deviceType
* @param unknown_type $userAgent
* @param unknown_type $protocolVersion
* @return Syncroton_Model_Device
*/
protected function _getUserDevice($ownerId, $requestParameters)
{
try {
$device = $this->_deviceBackend->getUserDevice($ownerId, $requestParameters['deviceId']);
$device->useragent = $requestParameters['userAgent'];
$device->acsversion = $requestParameters['protocolVersion'];
if ($device->isDirty()) {
$device = $this->_deviceBackend->update($device);
}
} catch (Syncroton_Exception_NotFound $senf) {
$device = $this->_deviceBackend->create(new Syncroton_Model_Device(array(
'owner_id' => $ownerId,
'deviceid' => $requestParameters['deviceId'],
'devicetype' => $requestParameters['deviceType'],
'useragent' => $requestParameters['userAgent'],
'acsversion' => $requestParameters['protocolVersion'],
'policyId' => Syncroton_Registry::isRegistered(Syncroton_Registry::DEFAULT_POLICY) ? Syncroton_Registry::get(Syncroton_Registry::DEFAULT_POLICY) : null
)));
}
return $device;
}
}
diff --git a/lib/kolab_sync_data.php b/lib/kolab_sync_data.php
index 96ab79e..0c6d0ef 100644
--- a/lib/kolab_sync_data.php
+++ b/lib/kolab_sync_data.php
@@ -1,1330 +1,1342 @@
<?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;
/**
* Internal cache for kolab_storage folder objects
*
* @var array
*/
protected $folders = array();
/**
* Timezone
*
* @var string
*/
protected $timezone;
+ /**
+ * List of device types with multiple folders support
+ *
+ * @var array
+ */
+ protected $ext_devices = array(
+ 'iphone',
+ 'ipad',
+ 'thundertine',
+ 'windowsphone',
+ );
+
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(
self::RECUR_TYPE_DAILY => 'DAILY',
self::RECUR_TYPE_WEEKLY => 'WEEKLY',
self::RECUR_TYPE_MONTHLY => 'MONTHLY',
self::RECUR_TYPE_MONTHLY_DAYN => 'MONTHLY',
self::RECUR_TYPE_YEARLY => 'YEARLY',
self::RECUR_TYPE_YEARLY_DAYN => 'YEARLY',
);
/**
* 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'))) {
+ if (in_array(strtolower($this->device->devicetype), $this->ext_devices)) {
// get the folders the user has access to
$list = $this->backend->folders_list($this->device->deviceid, $this->modelName);
}
else if ($default = $this->getDefaultFolder()) {
$list = array($default['serverId'] => $default);
}
// getAllFolders() is called only in FolderSync
// throw Syncroton_Exception_Status_FolderSync exception
if (!is_array($list)) {
throw new Syncroton_Exception_Status_FolderSync(Syncroton_Exception_Status_FolderSync::FOLDER_SERVER_ERROR);
}
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 $folders;
}
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)) {
$key = array_shift(array_keys($folders));
$default = $folders[$key];
// make sure the type is default here
$default['type'] = $this->defaultFolderType;
}
// 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);
}
/**
* Moves object into another location (folder)
*
* @param string $srcFolderId Source folder identifier
* @param string $serverId Object identifier
* @param string $dstFolderId Destination folder identifier
*
* @throws Syncroton_Exception_Status
* @return string New object identifier
*/
public function moveItem($srcFolderId, $serverId, $dstFolderId)
{
$item = $this->getObject($srcFolderId, $serverId, $folder);
if (!$item || !$folder) {
throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_SOURCE);
}
$dstname = $this->backend->folder_id2name($dstFolderId, $this->device->deviceid);
if ($dstname === null) {
throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_DESTINATION);
}
if (!$folder->move($serverId, $dstname)) {
throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_SOURCE);
}
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);
if (empty($entry)) {
throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_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)
{
$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);
if (empty($entry)) {
throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR);
}
return $entry['uid'];
}
/**
* delete entry
*
* @param string $folderId
* @param string $serverId
* @param array $collectionData
*/
public function deleteEntry($folderId, $serverId, $collectionData)
{
$deleted = $this->deleteObject($folderId, $serverId);
if (!$deleted) {
throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR);
}
}
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);
if (!is_array($folders)) {
throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR);
}
$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)
{
// 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
* @param int $filterType
*
* @return array
*/
public function getChangedEntries($folderId, DateTime $start, DateTime $end = null, $filter_type = null)
{
$filter = $this->filter($filter_type);
$filter[] = array('changed', '>', $start);
if ($end) {
$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
* @param int $filterType
*
* @return int
*/
public function getChangedEntriesCount($folderId, DateTime $start, DateTime $end = null, $filter_type = null)
{
$filter = $this->filter($filter_type);
$filter[] = array('changed', '>', $start);
if ($end) {
$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;
}
/**
* Returns number of changed objects in the backend folder
*
* @param Syncroton_Backend_IContent $contentBackend
* @param Syncroton_Model_IFolder $folder
* @param Syncroton_Model_ISyncState $syncState
*
* @return int
*/
public function getCountOfChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState)
{
$allClientEntries = $contentBackend->getFolderState($this->device, $folder);
$allServerEntries = $this->getServerEntries($folder->serverId, $folder->lastfiltertype);
$changedEntries = $this->getChangedEntriesCount($folder->serverId, $syncState->lastsync, null, $folder->lastfiltertype);
$addedEntries = array_diff($allServerEntries, $allClientEntries);
$deletedEntries = array_diff($allClientEntries, $allServerEntries);
return count($addedEntries) + count($deletedEntries) + $changedEntries;
}
/**
* Returns true if any data got modified in the backend folder
*
* @param Syncroton_Backend_IContent $contentBackend
* @param Syncroton_Model_IFolder $folder
* @param Syncroton_Model_ISyncState $syncState
*
* @return bool
*/
public function hasChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState)
{
if ($this->getChangedEntriesCount($folder->serverId, $syncState->lastsync, null, $folder->lastfiltertype)) {
return true;
}
$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
// 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);
return count($addedEntries) > 0 || count($deletedEntries) > 0;
}
/**
* 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);
if (!is_array($folders)) {
return null;
}
$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 ($folder && ($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();
if (!is_array($default)) {
return null;
}
$folderid = isset($default['realid']) ? $default['realid'] : $default['serverId'];
}
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
$folder = $this->getFolderObject($foldername);
if ($folder && $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 && $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 && $folder->delete($entryid);
}
// object doesn't exist, confirm deletion
return true;
}
/**
* Returns Folder object (uses internal cache)
*
* @param string $name Folder name (UTF7-IMAP)
*
* @return kolab_storage_folder Folder object
*/
protected function getFolderObject($name)
{
if ($name === null) {
return null;
}
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();
if (!is_array($metadata)) {
return array();
}
$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);
/**
* 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) && empty($params)) {
return;
}
// Old protocol version doesn't support AirSyncBase:Body, it's eg. WindowsCE
if ($this->asversion < 12) {
return;
}
if (!empty($value)) {
// cast to string to workaround issue described in Bug #1635
$params['data'] = (string) $value;
}
if (!isset($params['type'])) {
$params['type'] = Syncroton_Model_EmailBody::TYPE_PLAINTEXT;
}
return new Syncroton_Model_EmailBody($params);
}
/**
* Getter for Body attribute value according to client version
*
* @param mixed $body Body element
* @param int $type Result data type (to which the body will be converted, if specified).
* One of Syncroton_Model_EmailBody constants.
*
* @return string Body value
*/
protected function getBody($body, $type = null)
{
if ($body && $body->data) {
$data = $body->data;
}
// Convert to specified type
if ($data && $type && $body->type != $type) {
$converter = new kolab_sync_body_converter($data, $body->type);
$data = $converter->convert($type);
}
return $data;
}
/**
* 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 if ($date instanceof DateTime) {
$date = clone $date;
$tz = $date->getTimezone();
$tz_name = $tz->getName();
// convert to UTC if needed
if ($tz_name != 'UTC') {
$date->setTimezone(new DateTimeZone('UTC'));
}
}
else {
return null; // invalid input
}
return $date;
}
}
/**
* Convert Kolab event/task recurrence into ActiveSync
*/
protected function recurrence_from_kolab($collection, $data, &$result, $type = 'Event')
{
if (empty($data['recurrence'])) {
return;
}
$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'] = self::date_from_kolab($r['UNTIL']);
}
else if (!empty($r['COUNT'])) {
$recurrence['occurrences'] = $r['COUNT'];
}
$class = 'Syncroton_Model_' . $type . 'Recurrence';
$result['recurrence'] = new $class($recurrence);
// Tasks do not support exceptions
if ($type == 'Event') {
$result['exceptions'] = $this->exceptions_from_kolab($collection, $data, $result);
}
}
/**
* Convert ActiveSync event/task recurrence into Kolab
*/
protected function recurrence_to_kolab($data, $folderid, $timezone = null)
{
if (!($data->recurrence instanceof Syncroton_Model_EventRecurrence) || !isset($data->recurrence->type)) {
return null;
}
$recurrence = $data->recurrence;
$type = $recurrence->type;
switch ($type) {
case self::RECUR_TYPE_DAILY:
break;
case self::RECUR_TYPE_WEEKLY:
$rrule['BYDAY'] = $this->bitmask2day($recurrence->dayOfWeek);
break;
case self::RECUR_TYPE_MONTHLY:
$rrule['BYMONTHDAY'] = $recurrence->dayOfMonth;
break;
case self::RECUR_TYPE_MONTHLY_DAYN:
$week = $recurrence->weekOfMonth;
$day = $recurrence->dayOfWeek;
$byDay = $week == 5 ? -1 : $week;
$byDay .= $this->bitmask2day($day);
$rrule['BYDAY'] = $byDay;
break;
case self::RECUR_TYPE_YEARLY:
$rrule['BYMONTH'] = $recurrence->monthOfYear;
$rrule['BYMONTHDAY'] = $recurrence->dayOfMonth;
break;
case self::RECUR_TYPE_YEARLY_DAYN:
$rrule['BYMONTH'] = $recurrence->monthOfYear;
$week = $recurrence->weekOfMonth;
$day = $recurrence->dayOfWeek;
$byDay = $week == 5 ? -1 : $week;
$byDay .= $this->bitmask2day($day);
$rrule['BYDAY'] = $byDay;
break;
}
$rrule['FREQ'] = $this->recurTypeMap[$type];
$rrule['INTERVAL'] = isset($recurrence->interval) ? $recurrence->interval : 1;
if (isset($recurrence->until)) {
if ($timezone) {
$recurrence->until->setTimezone($timezone);
}
$rrule['UNTIL'] = $recurrence->until;
}
else if (!empty($recurrence->occurrences)) {
$rrule['COUNT'] = $recurrence->occurrences;
}
// recurrence exceptions (not supported by Tasks)
if ($data instanceof Syncroton_Model_Event) {
$this->exceptions_to_kolab($data, $rrule, $folderid, $timezone);
}
return $rrule;
}
/**
* Convert Kolab event recurrence exceptions into ActiveSync
*/
protected function exceptions_from_kolab($collection, $data, $result)
{
if (empty($data['recurrence']['EXCEPTIONS']) && empty($data['recurrence']['EXDATE'])) {
return null;
}
$ex_list = array();
// exceptions (modified occurences)
foreach ((array)$data['recurrence']['EXCEPTIONS'] as $exception) {
$exception['_mailbox'] = $data['_mailbox'];
$ex = $this->getEntry($collection, $exception, true);
$ex['exceptionStartTime'] = clone $ex['startTime'];
// remove fields not supported by Syncroton_Model_EventException
unset($ex['uID']);
// @TODO: 'thisandfuture=true' is not supported in Activesync
// we'd need to slit the event into two separate events
$ex_list[] = new Syncroton_Model_EventException($ex);
}
// exdate (deleted occurences)
foreach ((array)$data['recurrence']['EXDATE'] as $exception) {
if (!($exception instanceof DateTime)) {
continue;
}
// set event start time to exception date
// that can't be any time, tested with Android
$hour = $data['_start']->format('H');
$minute = $data['_start']->format('i');
$second = $data['_start']->format('s');
$exception->setTime($hour, $minute, $second);
$ex = array(
'deleted' => 1,
'exceptionStartTime' => self::date_from_kolab($exception),
);
$ex_list[] = new Syncroton_Model_EventException($ex);
}
return $ex_list;
}
/**
* Convert ActiveSync event recurrence exceptions into Kolab
*/
protected function exceptions_to_kolab($data, &$rrule, $folderid, $timezone = null)
{
$rrule['EXDATE'] = array();
$rrule['EXCEPTIONS'] = array();
// handle exceptions from recurrence
if (!empty($data->exceptions)) {
foreach ($data->exceptions as $exception) {
if ($exception->deleted) {
$date = clone $exception->exceptionStartTime;
if ($timezone) {
$date->setTimezone($timezone);
}
$date->setTime(0, 0, 0);
$rrule['EXDATE'][] = $date;
}
else if (!$exception->deleted) {
$ex = $this->toKolab($exception, $folderid, null, $timezone);
if ($data->allDayEvent) {
$ex['allday'] = 1;
}
$rrule['EXCEPTIONS'][] = $ex;
}
}
}
}
/**
* 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_email.php b/lib/kolab_sync_data_email.php
index f68609e..b696e54 100644
--- a/lib/kolab_sync_data_email.php
+++ b/lib/kolab_sync_data_email.php
@@ -1,1326 +1,1329 @@
<?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',
'from' => 'from',
//'importance' => 'importance',
'internetCPID' => 'charset',
//'messageClass' => 'messageclass',
'replyTo' => 'replyto',
//'read' => 'read',
'subject' => 'subject',
//'threadTopic' => 'threadtopic',
'to' => 'to',
);
/**
* Special folder type/name map
*
* @var array
*/
protected $folder_types = array(
2 => 'Inbox',
3 => 'Drafts',
4 => 'Deleted Items',
5 => 'Sent Items',
6 => 'Outbox',
);
/**
* 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;
/**
* 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();
+
+ // Outlook 2013 support multi-folder
+ $this->ext_devices[] = 'windowsoutlook15';
}
/**
* 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 and body type
$airSyncBaseType = Syncroton_Command_Sync::BODY_TYPE_PLAIN_TEXT;
$truncateAt = null;
$opts = $collection->options;
$prefs = $opts['bodyPreferences'];
if ($opts['mimeSupport'] == Syncroton_Command_Sync::MIMESUPPORT_SEND_MIME) {
$airSyncBaseType = Syncroton_Command_Sync::BODY_TYPE_MIME;
if (isset($prefs[Syncroton_Command_Sync::BODY_TYPE_MIME]['truncationSize'])) {
$truncateAt = $prefs[Syncroton_Command_Sync::BODY_TYPE_MIME]['truncationSize'];
}
else if (isset($opts['mimeTruncation']) && $opts['mimeTruncation'] < Syncroton_Command_Sync::TRUNCATE_NOTHING) {
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;
}
}
}
else {
// The spec is not very clear, but it looks that if MimeSupport is not set
// we can't add Syncroton_Command_Sync::BODY_TYPE_MIME to the supported types
// list below (Bug #1688)
$types = array(
Syncroton_Command_Sync::BODY_TYPE_HTML,
Syncroton_Command_Sync::BODY_TYPE_PLAIN_TEXT,
);
// @TODO: if client can support both HTML and TEXT use one of
// them which is better according to the real message body type
foreach ($types as $type) {
if (!empty($prefs[$type])) {
if (!empty($prefs[$type]['truncationSize'])) {
$truncateAt = $prefs[$type]['truncationSize'];
}
$airSyncBaseType = $type;
break;
}
}
}
// Message body
// 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)) {
$messageBody = '';
$real_length = $message->size;
$truncateAt = 0;
$body_length = 0;
$isTruncated = 1;
}
else if ($airSyncBaseType == Syncroton_Command_Sync::BODY_TYPE_MIME) {
$messageBody = $this->storage->get_raw_body($message->uid);
// strip out any non utf-8 characters
$messageBody = rcube_charset::clean($messageBody);
$real_length = $body_length = strlen($messageBody);
}
else {
$messageBody = $this->getMessageBody($message, $airSyncBaseType == Syncroton_Command_Sync::BODY_TYPE_HTML);
// strip out any non utf-8 characters
$messageBody = rcube_charset::clean($messageBody);
$real_length = $body_length = strlen($messageBody);
}
// truncate the body if needed
if ($truncateAt && $body_length > $truncateAt) {
$messageBody = mb_strcut($messageBody, 0, $truncateAt);
$body_length = strlen($messageBody);
$isTruncated = 1;
}
$body_params = array('type' => $airSyncBaseType);
if ($isTruncated) {
$body_params['truncated'] = 1;
$body_params['estimatedDataSize'] = $real_length;
}
// add Body element to the result
$result['body'] = $this->setBody($messageBody, $body_params);
// original body type
// @TODO: get this value from getMessageBody()
$result['nativeBodyType'] = $message->has_html_part() ? 2 : 1;
// Message class
// @TODO: add messageClass suffix for encrypted messages
$result['messageClass'] = 'IPM.Note';
$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;
}
/**
* Return list of supported folders for this backend
*
* @return array
*/
public function getAllFolders()
{
$list = $this->backend->folders_list($this->device->deviceid, $this->modelName);
if (!is_array($list)) {
throw new Syncroton_Exception_Status_FolderSync(Syncroton_Exception_Status_FolderSync::FOLDER_SERVER_ERROR);
}
// device doesn't support multiple folders
- if (!in_array(strtolower($this->device->devicetype), array('iphone', 'ipad', 'thundertine', 'windowsphone'))) {
+ if (!in_array(strtolower($this->device->devicetype), $this->ext_devices)) {
// We'll return max. one folder of supported type
$result = array();
$types = $this->folder_types;
foreach ($list as $idx => $folder) {
$type = $folder['type'] == 12 ? 2 : $folder['type']; // unknown to Inbox
if ($folder_id = $types[$type]) {
$result[$folder_id] = array(
'displayName' => $folder_id,
'serverId' => $folder_id,
'parentId' => 0,
'type' => $type,
);
}
}
$list = $result;
}
foreach ($list as $idx => $folder) {
$list[$idx] = new Syncroton_Model_Folder($folder);
}
return $list;
}
/**
* Return list of folders for specified folder ID
*
* @return array Folder identifiers list
*/
protected function extractFolders($folder_id)
{
$list = $this->backend->folders_list($this->device->deviceid, $this->modelName);
$result = array();
if (!is_array($list)) {
throw new Syncroton_Exception_NotFound('Folder not found');
}
// device supports multiple folders?
- if (in_array(strtolower($this->device->devicetype), array('iphone', 'ipad', 'thundertine', 'windowsphone'))) {
+ if (in_array(strtolower($this->device->devicetype), $this->ext_devices)) {
if ($list[$folder_id]) {
$result[] = $folder_id;
}
}
else if ($type = array_search($folder_id, $this->folder_types)) {
foreach ($list as $id => $folder) {
if ($folder['type'] == $type || ($folder_id == 'Inbox' && $folder['type'] == 12)) {
$result[] = $id;
}
}
}
if (empty($result)) {
throw new Syncroton_Exception_NotFound('Folder not found');
}
return $result;
}
/**
* Moves object into another location (folder)
*
* @param string $srcFolderId Source folder identifier
* @param string $serverId Object identifier
* @param string $dstFolderId Destination folder identifier
*
* @throws Syncroton_Exception_Status
* @return string New object identifier
*/
public function moveItem($srcFolderId, $serverId, $dstFolderId)
{
$msg = $this->parseMessageId($serverId);
$dest = $this->extractFolders($dstFolderId);
$dstname = $this->backend->folder_id2name(array_shift($dest), $this->device->deviceid);
if (empty($msg)) {
throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_SOURCE);
}
if ($dstname === null) {
throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_DESTINATION);
}
if (!$this->storage->move_message($msg['uid'], $dstname, $msg['foldername'])) {
throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_SOURCE);
}
// 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);
if (empty($message)) {
throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR);
}
$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);
if (empty($msg)) {
throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR);
}
// 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']);
}
}
/**
* Send an email
*
* @param mixed $message MIME message
* @param boolean $saveInSent Enables saving the sent message in Sent folder
*
* @param throws Syncroton_Exception_Status
*/
public function sendEmail($message, $saveInSent)
{
if (!($message instanceof kolab_sync_message)) {
$message = new kolab_sync_message($message);
}
$sent = $message->send($smtp_error);
if (!$sent) {
throw new Syncroton_Exception_Status(Syncroton_Exception_Status::MAIL_SUBMISSION_FAILED);
}
// Save sent message in Sent folder
if ($saveInSent) {
$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->source(), '', false, array('SEEN'));
}
}
}
/**
* Forward an email
*
* @param array|string $itemId A string LongId or an array with following properties:
* collectionId, itemId and instanceId
* @param resource|string $body MIME message
* @param boolean $saveInSent Enables saving the sent message in Sent folder
* @param boolean $replaceMime If enabled, original message would be appended
*
* @param throws Syncroton_Exception_Status
*/
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);
if (empty($msg)) {
throw new Syncroton_Exception_Status(Syncroton_Exception_Status::ITEM_NOT_FOUND);
}
// Parse message
$sync_msg = new kolab_sync_message($body);
// forward original message as attachment
if (!$replaceMime) {
$this->storage->set_folder($msg['foldername']);
$attachment = $this->storage->get_raw_body($msg['uid']);
if (empty($attachment)) {
throw new Syncroton_Exception_Status(Syncroton_Exception_Status::ITEM_NOT_FOUND);
}
$sync_msg->add_attachment($attachment, array(
'encoding' => '8bit',
'content_type' => 'message/rfc822',
'disposition' => 'inline',
//'name' => 'message.eml',
));
}
// Send message
$sent = $this->sendEmail($sync_msg, $saveInSent);
// Set FORWARDED flag on the replied message
if (empty($message->headers->flags['FORWARDED'])) {
$this->storage->set_flag($msg['uid'], 'FORWARDED', $msg['foldername']);
}
}
/**
* Reply to an email
*
* @param array|string $itemId A string LongId or an array with following properties:
* collectionId, itemId and instanceId
* @param resource|string $body MIME message
* @param boolean $saveInSent Enables saving the sent message in Sent folder
* @param boolean $replaceMime If enabled, original message would be appended
*
* @param throws Syncroton_Exception_Status
*/
public function replyEmail($itemId, $body, $saveInSent, $replaceMime)
{
$msg = $this->parseMessageId($itemId);
$message = $this->getObject($itemId);
if (!$message) {
throw new Syncroton_Exception_Status(Syncroton_Exception_Status::ITEM_NOT_FOUND);
}
$sync_msg = new kolab_sync_message($body);
$headers = $sync_msg->headers();
// Add References header
if (empty($headers['References'])) {
$sync_msg->set_header('References', trim($message->headers->references . ' ' . $message->headers->messageID));
}
// Get original message body
if (!$replaceMime) {
// @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
$sync_msg->append("\n" . ltrim($message_body));
}
// Send message
$sent = $this->sendEmail($sync_msg, $saveInSent);
// Set ANSWERED flag on the replied message
if (empty($message->headers->flags['ANSWERED'])) {
$this->storage->set_flag($msg['uid'], 'ANSWERED', $msg['foldername']);
}
}
/**
* 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)
{
$folders = $this->extractFolders($folderid);
$filter_str = 'ALL UNDELETED';
// convert filter into one IMAP search string
foreach ($filter as $idx => $filter_item) {
if (is_array($filter_item)) {
// This is a request for changes since list time
// we'll use HIGHESTMODSEQ value from the last Sync
if ($filter_item[0] == 'changed' && $filter_item[1] == '>') {
$modseq = (array) $this->backend->modseq_get($this->device->id, $folderid, $filter_item[2]);
$modseq_data = 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 $folder_id) {
$foldername = $this->backend->folder_id2name($folder_id, $this->device->deviceid);
if ($foldername === null) {
continue;
}
$this->storage->set_folder($foldername);
// Syncronize folder (if it wasn't synced in this request already)
if ($this->lastsync_folder != $foldername
|| $this->lastsync_time < time() - Syncroton_Registry::getPingTimeout()
) {
$this->storage->folder_sync($foldername);
}
$this->lastsync_folder = $foldername;
$this->lastsync_time = time();
// We're in "get changes" mode
if (isset($modseq_data)) {
$folder_data = $this->storage->folder_data($foldername);
if ($folder_data['HIGHESTMODSEQ']) {
$modseq_data[$foldername] = $folder_data['HIGHESTMODSEQ'];
if ($modseq_data[$foldername] != $modseq[$foldername]) {
$modseq_update = true;
}
}
// If previous HIGHESTMODSEQ doesn't exist we can't get changes
// We can only get folder's HIGHESTMODSEQ value and store it for the next try
if (empty($modseq) || empty($modseq[$foldername])) {
continue;
}
$filter_str .= " MODSEQ " . ($modseq[$foldername] + 1);
}
$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($folder_id, $uid);
}
$result = array_merge($result, $uids);
}
break;
/*
case self::RESULT_OBJECT:
default:
if ($objects = $folder->select($filter)) {
$result = array_merge($result, $objects);
}
*/
}
}
if (!empty($modseq_update)) {
$this->backend->modseq_set($this->device->id, $folderid,
$this->syncTimeStamp, $modseq_data);
}
return $result;
}
/**
* ActiveSync Search handler
*
* @param Syncroton_Model_StoreRequest $store Search query
*
* @return Syncroton_Model_StoreResponse Complete Search response
*/
public function search(Syncroton_Model_StoreRequest $store)
{
list($folders, $search_str) = $this->parse_search_query($store);
if (empty($search_str)) {
throw new Exception('Empty/invalid search request');
}
if (!is_array($folders)) {
throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR);
}
$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;
}
}
$result = array_values($result);
$response = new Syncroton_Model_StoreResponse();
// Calculate requested range
$start = (int) $store->options['range'][0];
$limit = (int) $store->options['range'][1] + 1;
$total = count($result);
$response->total = $total;
// Get requested chunk of data set
if ($total) {
if ($start > $total) {
$start = $total;
}
if ($limit > $total) {
$limit = max($start+1, $total);
}
if ($start > 0 || $limit < $total) {
$result = array_slice($result, $start, $limit-$start);
}
$response->range = array($start, $start + count($result) - 1);
}
// Build result array, convert to ActiveSync format
foreach ($result as $idx => $rec) {
$rec->properties = $this->getSearchEntry($rec->longId, $store->options);
$response->result[] = $rec;
unset($result[$idx]);
}
return $response;
}
/**
* Converts ActiveSync search parameters into IMAP search string
*/
protected function parse_search_query($store)
{
$options = $store->options;
$query = $store->query;
$search_str = '';
$folders = array();
if (empty($query) || !is_array($query)) {
return array();
}
if (isset($query['and']['freeText']) && strlen($query['and']['freeText'])) {
$search = $query['and']['freeText'];
}
if (!empty($query['and']['collections'])) {
foreach ($query['and']['collections'] as $collection) {
$folders = array_merge($folders, $this->extractFolders($collection));
}
}
if (!empty($query['and']['greaterThan'])
&& !empty($query['and']['greaterThan']['dateReceived'])
&& !empty($query['and']['greaterThan']['value'])
) {
$search_str .= ' SINCE ' . $query['and']['greaterThan']['value']->format('d-M-Y');
}
if (!empty($query['and']['lessThan'])
&& !empty($query['and']['lessThan']['dateReceived'])
&& !empty($query['and']['lessThan']['value'])
) {
$search_str .= ' BEFORE ' . $query['and']['lessThan']['value']->format('d-M-Y');
}
if ($search !== null) {
// @FIXME: should we use TEXT/BODY search?
// ActiveSync protocol specification says "indexed fields"
$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);
if (is_array($folders)) {
$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);
if (!$message) {
throw new Syncroton_Exception_NotFound('Message not found');
}
$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 if ($part->ctype_secondary == 'enriched') {
$body = rcube_enriched::to_html($body);
}
else {
$body = '<pre>' . $body . '</pre>';
}
}
else {
if ($part->ctype_secondary == 'enriched') {
$body = rcube_enriched::to_html($body);
$part->ctype_secondary = 'html';
}
if ($part->ctype_secondary == 'html') {
$txt = new rcube_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;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Mar 19, 8:46 AM (20 h, 31 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
457075
Default Alt Text
(103 KB)

Event Timeline