Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F256652
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
77 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/kolab_sync_data_calendar.php b/lib/kolab_sync_data_calendar.php
index 1c756d9..d441fde 100644
--- a/lib/kolab_sync_data_calendar.php
+++ b/lib/kolab_sync_data_calendar.php
@@ -1,526 +1,526 @@
<?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',
//'attendees' => 'attendees',
'body' => 'description',
//'bodyTruncated' => 'bodytruncated',
'busyStatus' => 'free_busy',
//'categories' => 'categories',
'dtStamp' => 'changed',
'endTime' => 'end',
//'exceptions' => 'exceptions',
'location' => 'location',
//'meetingStatus' => 'meetingstatus',
//'organizerEmail' => 'organizeremail',
//'organizerName' => 'organizername',
//'recurrence' => 'recurrence',
//'reminder' => 'reminder',
//'responseRequested' => 'responserequested',
//'responseType' => 'responsetype',
'sensitivity' => 'sensitivity',
'startTime' => 'start',
'subject' => 'title',
//'timezone' => 'timezone',
'uID' => 'uid',
);
/**
* 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;
/**
* 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, Syncroton_Model_EmailBody::TYPE_PLAINTEXT);
+ $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 = $attendee['name']) {
$result['organizerName'] = $name;
}
if ($email = $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, Syncroton_Model_EmailBody::TYPE_PLAINTEXT);
// 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 5a71da4..7a60c0e 100644
--- a/lib/kolab_sync_data_contacts.php
+++ b/lib/kolab_sync_data_contacts.php
@@ -1,237 +1,237 @@
<?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',
//'assistantPhoneNumber' => 'assistantphonenumber',
'birthday' => 'birthday',
'body' => 'notes',
'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',
'children' => 'children',
'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',
'picture' => 'photo',
//'radioPhoneNumber' => 'radiophonenumber',
//'rtf' => 'rtf',
'spouse' => 'spouse',
'suffix' => 'suffix',
'title' => 'prefix',
'webPage' => 'website.homepage.url',
//'yomiCompanyName' => 'yomicompanyname',
//'yomiFirstName' => 'yomifirstname',
//'yomiLastName' => 'yomilastname',
// Mapping from ActiveSync Contacts2 namespace fields
//'accountName' => 'accountname',
//'companyMainPhone' => 'companymainphone',
//'customerId' => 'customerid',
//'governmentId' => 'governmentid',
'iMAddress' => 'im:0',
'iMAddress2' => 'im:1',
'iMAddress3' => 'im:2',
'managerName' => 'manager:0',
//'mMS' => 'mms',
'nickName' => 'nickname',
);
/**
* 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;
/**
* 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, Syncroton_Model_EmailBody::TYPE_PLAINTEXT);
+ $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, Syncroton_Model_EmailBody::TYPE_PLAINTEXT);
// 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 3e97dae..22207a0 100644
--- a/lib/kolab_sync_data_email.php
+++ b/lib/kolab_sync_data_email.php
@@ -1,1145 +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> |
+--------------------------------------------------------------------------+
*/
/**
* 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();
}
/**
* 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'] == 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 if (!empty($prefs[Syncroton_Command_Sync::BODY_TYPE_HTML])) {
if (!empty($prefs[Syncroton_Command_Sync::BODY_TYPE_HTML]['truncationSize'])) {
$truncateAt = $prefs[Syncroton_Command_Sync::BODY_TYPE_HTML]['truncationSize'];
}
$airSyncBaseType = Syncroton_Command_Sync::BODY_TYPE_HTML;
}
else {
if (!empty($prefs[Syncroton_Command_Sync::BODY_TYPE_PLAIN_TEXT]) && !empty($prefs[Syncroton_Command_Sync::BODY_TYPE_PLAIN_TEXT]['truncationSize'])) {
$truncateAt = $prefs[Syncroton_Command_Sync::BODY_TYPE_PLAIN_TEXT]['truncationSize'];
}
$airSyncBaseType = Syncroton_Command_Sync::BODY_TYPE_PLAIN_TEXT;
}
// 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;
}
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);
}
if ($truncateAt !== null) {
if ($body_length > $truncateAt) {
$messageBody = substr($messageBody, 0, $truncateAt);
$body_length = $truncateAt;
}
$isTruncacted = 1;
}
else {
$isTruncacted = 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 == Syncroton_Command_Sync::MIMESUPPORT_SEND_MIME ? '.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()
+ * Return list of supported folders for this backend
+ *
+ * @return array
+ */
+ public function getAllFolders()
{
- // There's always INBOX
- $real_id = $this->backend->folder_id('INBOX', 'mail.inbox');
- $folder_id = $this->defaultRootFolder;
+ $list = $this->backend->folders_list($this->device->deviceid, $this->modelName);
+
+ // device doesn't support multiple folders
+ if (!in_array(strtolower($this->device->devicetype), array('iphone', 'ipad', 'thundertine', 'windowsphone'))) {
+ // We'll return max. one folder of supported type
+ $result = array();
+ $types = $this->folder_types;
+
+ foreach ($list as $idx => $folder) {
+ if (isset($types[$folder['type']])) {
+ $folder_id = $types[$folder['type']];
+ $result[$folder_id] = array(
+ 'displayName' => $folder_id,
+ 'serverId' => $folder_id,
+ 'parentId' => 0,
+ 'type' => $folder['type'],
+ );
+ unset($types[$folder['type']]);
+ }
+ }
- return array(
- 'displayName' => 'Inbox',
- 'realid' => $real_id,
- 'serverId' => $folder_id,
- 'parentId' => 0,
- 'type' => $this->defaultFolderType,
- );
+ $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();
+
+ // device supports multiple folders?
+ if (in_array(strtolower($this->device->devicetype), array('iphone', 'ipad', 'thundertine', 'windowsphone'))) {
+ 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);
- $dstname = $this->backend->folder_id2name($dstFolderId, $this->device->deviceid);
+ $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);
$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']);
}
}
/**
* Send an email
*
* @param resource|string $body MIME message
* @param boolean $saveInSent Enables saving the sent message in Sent folder
*
* @param throws Syncroton_Exception_Status
*/
public function sendEmail($body, $saveInSent)
{
$sent = $this->backend->send_message($body, $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, $body);
}
}
}
/**
* 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);
$message = $this->getObject($itemId);
// Parse message
list($headers, $body) = $this->backend->parse_mime($body, true);
// 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
// @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->sendEmail($body, $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);
// Parse message
// @TODO: messages with attachments
list($headers, $body) = $this->backend->parse_mime($body, true);
// 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
$body = rtrim($body) . "\n" . ltrim($message_body);
}
// Create complete message source
$body = $this->backend->build_mime($headers, $body);
// Send message
$sent = $this->sendEmail($body, $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)
{
- if ($folderid == $this->defaultRootFolder) {
- $folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
- $folders = array_keys($folders);
- }
- else {
- $folders = array($folderid);
- }
-
+ $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)) {
// @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'];
+ 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);
$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_tasks.php b/lib/kolab_sync_data_tasks.php
index 318dc5b..d94caf0 100644
--- a/lib/kolab_sync_data_tasks.php
+++ b/lib/kolab_sync_data_tasks.php
@@ -1,242 +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;
/**
* 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);
+ $value = $this->getBody($value, Syncroton_Model_EmailBody::TYPE_PLAINTEXT);
// 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
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Jun 9, 12:29 AM (1 d, 5 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
196726
Default Alt Text
(77 KB)
Attached To
Mode
R4 syncroton
Attached
Detach File
Event Timeline
Log In to Comment