Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117749312
kolab_sync_data_email.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
36 KB
Referenced Files
None
Subscribers
None
kolab_sync_data_email.php
View Options
<?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'
,
);
/**
* 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
==
4
)
{
$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
()
{
// There's always INBOX
$real_id
=
$this
->
backend
->
folder_id
(
'INBOX'
,
'mail.inbox'
);
$folder_id
=
$this
->
defaultRootFolder
;
return
array
(
'displayName'
=>
'Inbox'
,
'realid'
=>
$real_id
,
'serverId'
=>
$folder_id
,
'parentId'
=>
0
,
'type'
=>
$this
->
defaultFolderType
,
);
}
public
function
moveItem
(
$srcFolderId
,
$serverId
,
$dstFolderId
)
{
$msg
=
$this
->
parseMessageId
(
$serverId
);
$dstname
=
$this
->
backend
->
folder_id2name
(
$dstFolderId
,
$this
->
device
->
deviceid
);
if
(
$dstname
===
null
)
{
return
;
}
if
(!
$this
->
storage
->
move_message
(
$msg
[
'uid'
],
$dstname
,
$msg
[
'foldername'
]))
{
return
;
}
// Use COPYUID feature (RFC2359) to get the new UID of the copied message
$copyuid
=
$this
->
storage
->
conn
->
data
[
'COPYUID'
];
if
(
is_array
(
$copyuid
)
&&
(
$uid
=
$copyuid
[
1
]))
{
return
$uid
;
}
}
/**
* add entry from xml data
*
* @param string $folderId
* @param Syncroton_Model_IEntry $entry
*
* @return array
*/
public
function
createEntry
(
$folderId
,
Syncroton_Model_IEntry
$entry
)
{
}
/**
* update existing entry
*
* @param string $folderId
* @param string $serverId
* @param Syncroton_Model_IEntry $entry
* @return array
*/
public
function
updateEntry
(
$folderId
,
$serverId
,
Syncroton_Model_IEntry
$entry
)
{
$msg
=
$this
->
parseMessageId
(
$serverId
);
$message
=
$this
->
getObject
(
$serverId
);
$is_flagged
=
!
empty
(
$message
->
headers
->
flags
[
'FLAGGED'
]);
// Read status change
if
(
isset
(
$entry
->
read
))
{
// here we update only Read flag
$flag
=
(((
int
)
$entry
->
read
!=
1
)
?
'UN'
:
''
)
.
'SEEN'
;
$this
->
storage
->
set_flag
(
$msg
[
'uid'
],
$flag
,
$msg
[
'foldername'
]);
}
// Flag change
if
(
empty
(
$entry
->
flag
))
{
if
(
$is_flagged
)
{
$this
->
storage
->
set_flag
(
$msg
[
'uid'
],
'UNFLAGGED'
,
$msg
[
'foldername'
]);
}
}
else
if
(!
$is_flagged
&&
!
empty
(
$entry
->
flag
))
{
if
(
$entry
->
flag
->
flagType
&&
preg_match
(
'/^follow
\s
*up/i'
,
$entry
->
flag
->
flagType
))
{
$this
->
storage
->
set_flag
(
$msg
[
'uid'
],
'FLAGGED'
,
$msg
[
'foldername'
]);
}
}
}
/**
* delete entry
*
* @param string $folderId
* @param string $serverId
* @param Syncroton_Model_SyncCollection $collection
*/
public
function
deleteEntry
(
$folderId
,
$serverId
,
$collection
)
{
$trash
=
kolab_sync
::
get_instance
()->
config
->
get
(
'trash_mbox'
);
$msg
=
$this
->
parseMessageId
(
$serverId
);
// move message to trash folder
if
(
$collection
->
deletesAsMoves
&&
strlen
(
$trash
)
&&
$trash
!=
$msg
[
'foldername'
]
&&
$this
->
storage
->
folder_exists
(
$trash
)
)
{
$this
->
storage
->
move_message
(
$msg
[
'uid'
],
$trash
,
$msg
[
'foldername'
]);
}
// set delete flag
else
{
$this
->
storage
->
set_flag
(
$msg
[
'uid'
],
'DELETED'
,
$msg
[
'foldername'
]);
}
}
public
function
sendEmail
(
$body
,
$saveInSent
)
{
$sent
=
$this
->
backend
->
send_message
(
$body
,
$smtp_error
);
if
(
$sent
&&
$saveInSent
)
{
$this
->
saveInSent
(
$body
);
}
}
public
function
forwardEmail
(
$itemId
,
$body
,
$saveInSent
,
$replaceMime
)
{
/*
@TODO:
The SmartForward command can be applied to a meeting. When SmartForward is applied to a recurring meeting,
the InstanceId element (section 2.2.3.83.2) specifies the ID of a particular occurrence in the recurring meeting.
If SmartForward is applied to a recurring meeting and the InstanceId element is absent, the server SHOULD
forward the entire recurring meeting. If the value of the InstanceId element is invalid, the server responds
with Status element (section 2.2.3.162.15) value 104, as specified in section 2.2.4.
When the SmartForward command is used for an appointment, the original message is included by the server
as an attachment to the outgoing message. When the SmartForward command is used for a normal message
or a meeting, the behavior of the SmartForward command is the same as that of the SmartReply command (section 2.2.2.18).
*/
$msg
=
$this
->
parseMessageId
(
$itemId
);
$message
=
$this
->
getObject
(
$itemId
);
// Parse message
list
(
$headers
,
$body
)
=
$this
->
backend
->
parse_mime
(
$body
,
true
);
// Get original message body
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
->
backend
->
send_message
(
$body
,
$smtp_error
);
// Set FORWARDED flag on the replied message
if
(
$sent
&&
empty
(
$message
->
headers
->
flags
[
'FORWARDED'
]))
{
$this
->
storage
->
set_flag
(
$msg
[
'uid'
],
'FORWARDED'
,
$msg
[
'foldername'
]);
}
// Save sent message in Sent folder
if
(
$sent
&&
$saveInSent
)
{
$this
->
saveInSent
(
$body
);
}
}
public
function
replyEmail
(
$itemId
,
$body
,
$saveInSent
,
$replaceMime
)
{
$msg
=
$this
->
parseMessageId
(
$itemId
);
$message
=
$this
->
getObject
(
$itemId
);
// Parse message
// @TODO: messages with attachments
list
(
$headers
,
$body
)
=
$this
->
backend
->
parse_mime
(
$body
,
true
);
// Get original message body
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
->
backend
->
send_message
(
$body
,
$smtp_error
);
// Set ANSWERED flag on the replied message
if
(
$sent
&&
empty
(
$message
->
headers
->
flags
[
'ANSWERED'
]))
{
$this
->
storage
->
set_flag
(
$msg
[
'uid'
],
'ANSWERED'
,
$msg
[
'foldername'
]);
}
// Save sent message in Sent folder
if
(
$sent
&&
$saveInSent
)
{
$this
->
saveInSent
(
$body
);
}
}
/**
* Append the message into Sent folder
*
* @param string $message Complete message source
*
* @return int|bool Appended message UID or True on success, False on error
*/
protected
function
saveInSent
(
$message
)
{
$sent_folder
=
kolab_sync
::
get_instance
()->
config
->
get
(
'sent_mbox'
);
if
(
strlen
(
$sent_folder
)
&&
$this
->
storage
->
folder_exists
(
$sent_folder
))
{
return
$this
->
storage
->
save_message
(
$sent_folder
,
$message
);
}
return
false
;
}
/**
* Search for existing entries
*
* @param string $folderid
* @param array $filter
* @param int $result_type Type of the result (see RESULT_* constants)
*
* @return array|int Search result as count or array of uids/objects
*/
protected
function
searchEntries
(
$folderid
,
$filter
=
array
(),
$result_type
=
self
::
RESULT_UID
)
{
if
(
$folderid
==
$this
->
defaultRootFolder
)
{
$folders
=
$this
->
backend
->
folders_list
(
$this
->
device
->
deviceid
,
$this
->
modelName
);
$folders
=
array_keys
(
$folders
);
}
else
{
$folders
=
array
(
$folderid
);
}
$filter_str
=
'ALL UNDELETED'
;
// convert filter into one IMAP search string
foreach
(
$filter
as
$idx
=>
$filter_item
)
{
if
(
is_array
(
$filter_item
))
{
// @TODO
// convert 'changed' entries into IMAP search string
// for now we just return empty result
return
$result_type
==
self
::
RESULT_COUNT
?
0
:
array
();
}
else
{
$filter_str
.=
' '
.
$filter_item
;
}
}
$result
=
$result_type
==
self
::
RESULT_COUNT
?
0
:
array
();
// no sorting for best performance
$sort_by
=
null
;
foreach
(
$folders
as
$folderid
)
{
$foldername
=
$this
->
backend
->
folder_id2name
(
$folderid
,
$this
->
device
->
deviceid
);
if
(
$foldername
===
null
)
{
continue
;
}
// $this->storage->set_folder($foldername);
$this
->
storage
->
folder_sync
(
$foldername
);
$search
=
$this
->
storage
->
search_once
(
$foldername
,
$filter_str
);
if
(!(
$search
instanceof
rcube_result_index
))
{
continue
;
}
switch
(
$result_type
)
{
case
self
::
RESULT_COUNT
:
$result
+=
(
int
)
$search
->
count
();
break
;
case
self
::
RESULT_UID
:
if
(
$uids
=
$search
->
get
())
{
foreach
(
$uids
as
$idx
=>
$uid
)
{
$uids
[
$idx
]
=
$this
->
createMessageId
(
$folderid
,
$uid
);
}
$result
=
array_merge
(
$result
,
$uids
);
}
break
;
/*
case self::RESULT_OBJECT:
default:
if ($objects = $folder->select($filter)) {
$result = array_merge($result, $objects);
}
*/
}
}
return
$result
;
}
/**
* ActiveSync Search handler
*
* @param array $query Search query parameters
* @param array $options Search options
*
* @return array List of Syncroton_Model_StoreResponseResult objects
*/
public
function
search
(
$query
,
$options
)
{
list
(
$folders
,
$search_str
)
=
$this
->
parse_search_query
(
$query
,
$options
);
if
(
empty
(
$search_str
))
{
return
array
();
}
$result
=
array
();
// no sorting for best performance
$sort_by
=
null
;
// @TODO: caching with Options->RebuildResults support
foreach
(
$folders
as
$folderid
)
{
$foldername
=
$this
->
backend
->
folder_id2name
(
$folderid
,
$this
->
device
->
deviceid
);
if
(
$foldername
===
null
)
{
continue
;
}
// $this->storage->set_folder($foldername);
$this
->
storage
->
folder_sync
(
$foldername
);
$search
=
$this
->
storage
->
search_once
(
$foldername
,
$search_str
);
if
(!(
$search
instanceof
rcube_result_index
))
{
continue
;
}
$uids
=
$search
->
get
();
foreach
(
$uids
as
$idx
=>
$uid
)
{
$uids
[
$idx
]
=
new
Syncroton_Model_StoreResponseResult
(
array
(
'longId'
=>
$this
->
createMessageId
(
$folderid
,
$uid
),
'collectionId'
=>
$folderid
,
'class'
=>
'Email'
,
));
}
$result
=
array_merge
(
$result
,
$uids
);
// We don't want to search all folders if we've got already a lot messages
if
(
count
(
$result
)
>=
self
::
MAX_SEARCH_RESULT
)
{
break
;
}
}
return
array_values
(
$result
);
}
/**
* Converts ActiveSync search parameters into IMAP search string
*/
protected
function
parse_search_query
(
$query
,
$options
)
{
if
(
empty
(
$query
))
{
return
array
();
}
$search_str
=
''
;
$folders
=
array
();
if
(!
is_array
(
$query
))
{
$search
=
$query
;
}
else
{
if
(
isset
(
$query
[
'and'
][
'freeText'
])
&&
strlen
(
$query
[
'and'
][
'freeText'
]))
{
$search
=
$query
[
'and'
][
'freeText'
];
}
if
(!
empty
(
$query
[
'and'
][
'collections'
]))
{
$folders
[]
=
$query
[
'and'
][
'collections'
];
}
if
(!
empty
(
$query
[
'and'
][
'greaterThan'
])
&&
!
empty
(
$query
[
'and'
][
'greaterThan'
][
'dateReceived'
])
&&
!
empty
(
$query
[
'and'
][
'greaterThan'
][
'value'
])
)
{
$search_str
.=
' SINCE '
.
trim
(
$query
[
'and'
][
'greaterThan'
][
'value'
]);
}
if
(!
empty
(
$query
[
'and'
][
'lessThan'
])
&&
!
empty
(
$query
[
'and'
][
'lessThan'
][
'dateReceived'
])
&&
!
empty
(
$query
[
'and'
][
'lessThan'
][
'value'
])
)
{
$search_str
.=
' BEFORE '
.
trim
(
$query
[
'and'
][
'lessThan'
][
'value'
]);
}
}
if
(
$search
!==
null
)
{
// @FIXME: should we use TEXT/BODY search?
$search_keys
=
array
(
'SUBJECT'
,
'TO'
,
'FROM'
,
'CC'
);
$search_str
.=
str_repeat
(
' OR'
,
count
(
$search_keys
)-
1
);
foreach
(
$search_keys
as
$key
)
{
$search_str
.=
sprintf
(
" %s {%d}
\r\n
%s"
,
$key
,
strlen
(
$search
),
$search
);
}
}
if
(
empty
(
$search_str
))
{
return
array
();
}
$search_str
=
'ALL UNDELETED '
.
trim
(
$search_str
);
// @TODO: DeepTraversal
if
(
empty
(
$folders
))
{
$folders
=
$this
->
backend
->
folders_list
(
$this
->
device
->
deviceid
,
$this
->
modelName
);
$folders
=
array_keys
(
$folders
);
}
return
array
(
$folders
,
$search_str
);
}
/**
* Fetches the entry from the backend
*/
protected
function
getObject
(
$entryid
,
&
$folder
=
null
)
{
$message
=
$this
->
parseMessageId
(
$entryid
);
if
(
empty
(
$message
))
{
// @TODO: exception?
return
null
;
}
// set current folder
$this
->
storage
->
set_folder
(
$message
[
'foldername'
]);
// get message
$message
=
new
rcube_message
(
$message
[
'uid'
]);
return
$message
;
}
/**
* @return Syncroton_Model_FileReference
*/
public
function
getFileReference
(
$fileReference
)
{
list
(
$folderid
,
$uid
,
$part_id
)
=
explode
(
'::'
,
$fileReference
);
$message
=
$this
->
getObject
(
$fileReference
);
$part
=
$message
->
mime_parts
[
$part_id
];
$body
=
$message
->
get_part_content
(
$part_id
);
$content_type
=
$part
->
mimetype
;
return
new
Syncroton_Model_FileReference
(
array
(
'contentType'
=>
$content_type
,
'data'
=>
$body
,
));
}
/**
* Parses entry ID to get folder name and UID of the message
*/
protected
function
parseMessageId
(
$entryid
)
{
// replyEmail/forwardEmail
if
(
is_array
(
$entryid
))
{
$entryid
=
$entryid
[
'itemId'
];
}
list
(
$folderid
,
$uid
)
=
explode
(
'::'
,
$entryid
);
$foldername
=
$this
->
backend
->
folder_id2name
(
$folderid
,
$this
->
device
->
deviceid
);
if
(
$foldername
===
null
||
$foldername
===
false
)
{
// @TODO exception?
return
null
;
}
return
array
(
'uid'
=>
$uid
,
'folderid'
=>
$folderid
,
'foldername'
=>
$foldername
,
);
}
/**
* Creates entry ID of the message
*/
public
function
createMessageId
(
$folderid
,
$uid
)
{
return
$folderid
.
'::'
.
$uid
;
}
/**
* Returns body of the message in specified format
*/
protected
function
getMessageBody
(
$message
,
$html
=
false
)
{
if
(!
is_array
(
$message
->
parts
)
&&
empty
(
$message
->
body
))
{
return
''
;
}
if
(!
empty
(
$message
->
parts
))
{
foreach
(
$message
->
parts
as
$part
)
{
// skip no-content and attachment parts (#1488557)
if
(
$part
->
type
!=
'content'
||
!
$part
->
size
||
$message
->
is_attachment
(
$part
))
{
continue
;
}
return
$this
->
getMessagePartBody
(
$message
,
$part
,
$html
);
}
}
return
$this
->
getMessagePartBody
(
$message
,
$message
,
$html
);
}
/**
* Returns body of the message part in specified format
*/
protected
function
getMessagePartBody
(
$message
,
$part
,
$html
=
false
)
{
// Check if we have enough memory to handle the message in it
// @FIXME: we need up to 5x more memory than the body
if
(!
rcube_utils
::
mem_check
(
$part
->
size
*
5
))
{
return
''
;
}
if
(
empty
(
$part
->
ctype_parameters
)
||
empty
(
$part
->
ctype_parameters
[
'charset'
]))
{
$part
->
ctype_parameters
[
'charset'
]
=
$message
->
headers
->
charset
;
}
// fetch part if not available
if
(!
isset
(
$part
->
body
))
{
$part
->
body
=
$message
->
get_part_content
(
$part
->
mime_id
);
}
// message is cached but not exists, or other error
if
(
$part
->
body
===
false
)
{
return
''
;
}
$body
=
$part
->
body
;
if
(
$html
)
{
if
(
$part
->
ctype_secondary
==
'html'
)
{
}
else
{
$body
=
'<pre>'
.
$body
.
'</pre>'
;
}
}
else
{
if
(
$part
->
ctype_secondary
==
'html'
)
{
$txt
=
new
html2text
(
$body
,
false
,
true
);
$body
=
$txt
->
get_text
();
}
else
{
if
(
$part
->
ctype_secondary
==
'plain'
&&
$part
->
ctype_parameters
[
'format'
]
==
'flowed'
)
{
$body
=
rcube_mime
::
unfold_flowed
(
$body
);
}
}
}
return
$body
;
}
public
static
function
charset_to_cp
(
$charset
)
{
// @TODO: ?????
// The body is converted to utf-8 in get_part_content(), what about headers?
return
65001
;
// UTF-8
$aliases
=
array
(
'asmo708'
=>
708
,
'shiftjis'
=>
932
,
'gb2312'
=>
936
,
'ksc56011987'
=>
949
,
'big5'
=>
950
,
'utf16'
=>
1200
,
'utf16le'
=>
1200
,
'unicodefffe'
=>
1201
,
'utf16be'
=>
1201
,
'johab'
=>
1361
,
'macintosh'
=>
10000
,
'macjapanese'
=>
10001
,
'macchinesetrad'
=>
10002
,
'mackorean'
=>
10003
,
'macarabic'
=>
10004
,
'machebrew'
=>
10005
,
'macgreek'
=>
10006
,
'maccyrillic'
=>
10007
,
'macchinesesimp'
=>
10008
,
'macromanian'
=>
10010
,
'macukrainian'
=>
10017
,
'macthai'
=>
10021
,
'macce'
=>
10029
,
'macicelandic'
=>
10079
,
'macturkish'
=>
10081
,
'maccroatian'
=>
10082
,
'utf32'
=>
12000
,
'utf32be'
=>
12001
,
'chinesecns'
=>
20000
,
'chineseeten'
=>
20002
,
'ia5'
=>
20105
,
'ia5german'
=>
20106
,
'ia5swedish'
=>
20107
,
'ia5norwegian'
=>
20108
,
'usascii'
=>
20127
,
'ibm273'
=>
20273
,
'ibm277'
=>
20277
,
'ibm278'
=>
20278
,
'ibm280'
=>
20280
,
'ibm284'
=>
20284
,
'ibm285'
=>
20285
,
'ibm290'
=>
20290
,
'ibm297'
=>
20297
,
'ibm420'
=>
20420
,
'ibm423'
=>
20423
,
'ibm424'
=>
20424
,
'ebcdickoreanextended'
=>
20833
,
'ibmthai'
=>
20838
,
'koi8r'
=>
20866
,
'ibm871'
=>
20871
,
'ibm880'
=>
20880
,
'ibm905'
=>
20905
,
'ibm00924'
=>
20924
,
'cp1025'
=>
21025
,
'koi8u'
=>
21866
,
'iso88591'
=>
28591
,
'iso88592'
=>
28592
,
'iso88593'
=>
28593
,
'iso88594'
=>
28594
,
'iso88595'
=>
28595
,
'iso88596'
=>
28596
,
'iso88597'
=>
28597
,
'iso88598'
=>
28598
,
'iso88599'
=>
28599
,
'iso885913'
=>
28603
,
'iso885915'
=>
28605
,
'xeuropa'
=>
29001
,
'iso88598i'
=>
38598
,
'iso2022jp'
=>
50220
,
'csiso2022jp'
=>
50221
,
'iso2022jp'
=>
50222
,
'iso2022kr'
=>
50225
,
'eucjp'
=>
51932
,
'euccn'
=>
51936
,
'euckr'
=>
51949
,
'hzgb2312'
=>
52936
,
'gb18030'
=>
54936
,
'isciide'
=>
57002
,
'isciibe'
=>
57003
,
'isciita'
=>
57004
,
'isciite'
=>
57005
,
'isciias'
=>
57006
,
'isciior'
=>
57007
,
'isciika'
=>
57008
,
'isciima'
=>
57009
,
'isciigu'
=>
57010
,
'isciipa'
=>
57011
,
'utf7'
=>
65000
,
'utf8'
=>
65001
,
);
$charset
=
strtolower
(
$charset
);
$charset
=
preg_replace
(
array
(
'/^x-/'
,
'/[^a-z0-9]/'
),
''
,
$charset
);
if
(
isset
(
$aliases
[
$charset
]))
{
return
$aliases
[
$charset
];
}
if
(
preg_match
(
'/^(ibm|dos|cp|windows|win)[0-9]+/'
,
$charset
,
$m
))
{
return
substr
(
$charset
,
strlen
(
$m
[
1
])
+
1
);
}
}
/**
* Wrap text to a given number of characters per line
* but respect the mail quotation of replies messages (>).
* Finally add another quotation level by prepending the lines
* with >
*
* @param string $text Text to wrap
* @param int $length The line width
*
* @return string The wrapped text
*/
protected
static
function
wrap_and_quote
(
$text
,
$length
=
72
)
{
// Function stolen from Roundcube ;)
// Rebuild the message body with a maximum of $max chars, while keeping quoted message.
$max
=
min
(
77
,
$length
+
8
);
$lines
=
preg_split
(
'/
\r
?
\n
/'
,
trim
(
$text
));
$out
=
''
;
foreach
(
$lines
as
$line
)
{
// don't wrap already quoted lines
if
(
$line
[
0
]
==
'>'
)
{
$line
=
'>'
.
rtrim
(
$line
);
}
else
if
(
mb_strlen
(
$line
)
>
$max
)
{
$newline
=
''
;
foreach
(
explode
(
"
\n
"
,
rcube_mime
::
wordwrap
(
$line
,
$length
-
2
))
as
$l
)
{
if
(
strlen
(
$l
))
{
$newline
.=
'> '
.
$l
.
"
\n
"
;
}
else
{
$newline
.=
">
\n
"
;
}
}
$line
=
rtrim
(
$newline
);
}
else
{
$line
=
'> '
.
$line
;
}
// Append the line
$out
.=
$line
.
"
\n
"
;
}
return
$out
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, Apr 4, 1:32 AM (1 w, 6 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
b9/15/3f865cb3dd5691ff435784b9ac4d
Default Alt Text
kolab_sync_data_email.php (36 KB)
Attached To
Mode
rS syncroton
Attached
Detach File
Event Timeline