|
1
|
|
|
<?php |
|
2
|
|
|
/* |
|
3
|
|
|
* SPDX-License-Identifier: AGPL-3.0-only |
|
4
|
|
|
* SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH |
|
5
|
|
|
* SPDX-FileCopyrightText: Copyright 2020-2022 grommunio GmbH |
|
6
|
|
|
*/ |
|
7
|
|
|
|
|
8
|
|
|
class MAPIProvider { |
|
9
|
|
|
private $session; |
|
10
|
|
|
private $store; |
|
11
|
|
|
private $zRFC822; |
|
12
|
|
|
private $addressbook; |
|
13
|
|
|
private $storeProps; |
|
14
|
|
|
private $inboxProps; |
|
15
|
|
|
private $rootProps; |
|
16
|
|
|
private $specialFoldersData; |
|
17
|
|
|
|
|
18
|
|
|
/** |
|
19
|
|
|
* Constructor of the MAPI Provider |
|
20
|
|
|
* Almost all methods of this class require a MAPI session and/or store. |
|
21
|
|
|
* |
|
22
|
|
|
* @param resource $session |
|
23
|
|
|
* @param resource $store |
|
24
|
|
|
*/ |
|
25
|
|
|
public function __construct($session, $store) { |
|
26
|
|
|
$this->session = $session; |
|
27
|
|
|
$this->store = $store; |
|
28
|
|
|
} |
|
29
|
|
|
|
|
30
|
|
|
/*---------------------------------------------------------------------------------------------------------- |
|
31
|
|
|
* GETTER |
|
32
|
|
|
*/ |
|
33
|
|
|
|
|
34
|
|
|
/** |
|
35
|
|
|
* Reads a message from MAPI |
|
36
|
|
|
* Depending on the message class, a contact, appointment, task or email is read. |
|
37
|
|
|
* |
|
38
|
|
|
* @param mixed $mapimessage |
|
39
|
|
|
* @param ContentParameters $contentparameters |
|
40
|
|
|
* |
|
41
|
|
|
* @return SyncObject |
|
42
|
|
|
*/ |
|
43
|
|
|
public function GetMessage($mapimessage, $contentparameters) { |
|
44
|
|
|
// Gets the Sync object from a MAPI object according to its message class |
|
45
|
|
|
|
|
46
|
|
|
$props = mapi_getprops($mapimessage, [PR_MESSAGE_CLASS]); |
|
|
|
|
|
|
47
|
|
|
if (isset($props[PR_MESSAGE_CLASS])) { |
|
48
|
|
|
$messageclass = $props[PR_MESSAGE_CLASS]; |
|
49
|
|
|
} |
|
50
|
|
|
else { |
|
51
|
|
|
$messageclass = "IPM"; |
|
52
|
|
|
} |
|
53
|
|
|
|
|
54
|
|
|
if (strpos($messageclass, "IPM.Contact") === 0) { |
|
55
|
|
|
return $this->getContact($mapimessage, $contentparameters); |
|
56
|
|
|
} |
|
57
|
|
|
if (strpos($messageclass, "IPM.Appointment") === 0) { |
|
58
|
|
|
return $this->getAppointment($mapimessage, $contentparameters); |
|
59
|
|
|
} |
|
60
|
|
|
if (strpos($messageclass, "IPM.Task") === 0 && strpos($messageclass, "IPM.TaskRequest") === false) { |
|
61
|
|
|
return $this->getTask($mapimessage, $contentparameters); |
|
62
|
|
|
} |
|
63
|
|
|
if (strpos($messageclass, "IPM.StickyNote") === 0) { |
|
64
|
|
|
return $this->getNote($mapimessage, $contentparameters); |
|
65
|
|
|
} |
|
66
|
|
|
|
|
67
|
|
|
return $this->getEmail($mapimessage, $contentparameters); |
|
68
|
|
|
} |
|
69
|
|
|
|
|
70
|
|
|
/** |
|
71
|
|
|
* Reads a contact object from MAPI. |
|
72
|
|
|
* |
|
73
|
|
|
* @param mixed $mapimessage |
|
74
|
|
|
* @param ContentParameters $contentparameters |
|
75
|
|
|
* |
|
76
|
|
|
* @return SyncContact |
|
77
|
|
|
*/ |
|
78
|
|
|
private function getContact($mapimessage, $contentparameters) { |
|
79
|
|
|
$message = new SyncContact(); |
|
80
|
|
|
|
|
81
|
|
|
// Standard one-to-one mappings first |
|
82
|
|
|
$this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetContactMapping()); |
|
83
|
|
|
|
|
84
|
|
|
// Contact specific props |
|
85
|
|
|
$contactproperties = MAPIMapping::GetContactProperties(); |
|
86
|
|
|
$messageprops = $this->getProps($mapimessage, $contactproperties); |
|
87
|
|
|
|
|
88
|
|
|
// set the body according to contentparameters and supported AS version |
|
89
|
|
|
$this->setMessageBody($mapimessage, $contentparameters, $message); |
|
90
|
|
|
|
|
91
|
|
|
// check the picture |
|
92
|
|
|
if (isset($messageprops[$contactproperties["haspic"]]) && $messageprops[$contactproperties["haspic"]]) { |
|
93
|
|
|
// Add attachments |
|
94
|
|
|
$attachtable = mapi_message_getattachmenttable($mapimessage); |
|
|
|
|
|
|
95
|
|
|
mapi_table_restrict($attachtable, MAPIUtils::GetContactPicRestriction()); |
|
|
|
|
|
|
96
|
|
|
$rows = mapi_table_queryallrows($attachtable, [PR_ATTACH_NUM, PR_ATTACH_SIZE]); |
|
|
|
|
|
|
97
|
|
|
|
|
98
|
|
|
foreach ($rows as $row) { |
|
99
|
|
|
if (isset($row[PR_ATTACH_NUM])) { |
|
100
|
|
|
$mapiattach = mapi_message_openattach($mapimessage, $row[PR_ATTACH_NUM]); |
|
|
|
|
|
|
101
|
|
|
$message->picture = base64_encode(mapi_attach_openbin($mapiattach, PR_ATTACH_DATA_BIN)); |
|
|
|
|
|
|
102
|
|
|
} |
|
103
|
|
|
} |
|
104
|
|
|
} |
|
105
|
|
|
|
|
106
|
|
|
return $message; |
|
107
|
|
|
} |
|
108
|
|
|
|
|
109
|
|
|
/** |
|
110
|
|
|
* Reads a task object from MAPI. |
|
111
|
|
|
* |
|
112
|
|
|
* @param mixed $mapimessage |
|
113
|
|
|
* @param ContentParameters $contentparameters |
|
114
|
|
|
* |
|
115
|
|
|
* @return SyncTask |
|
116
|
|
|
*/ |
|
117
|
|
|
private function getTask($mapimessage, $contentparameters) { |
|
118
|
|
|
$message = new SyncTask(); |
|
119
|
|
|
|
|
120
|
|
|
// Standard one-to-one mappings first |
|
121
|
|
|
$this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetTaskMapping()); |
|
122
|
|
|
|
|
123
|
|
|
// Task specific props |
|
124
|
|
|
$taskproperties = MAPIMapping::GetTaskProperties(); |
|
125
|
|
|
$messageprops = $this->getProps($mapimessage, $taskproperties); |
|
126
|
|
|
|
|
127
|
|
|
// set the body according to contentparameters and supported AS version |
|
128
|
|
|
$this->setMessageBody($mapimessage, $contentparameters, $message); |
|
129
|
|
|
|
|
130
|
|
|
// task with deadoccur is an occurrence of a recurring task and does not need to be handled as recurring |
|
131
|
|
|
// webaccess does not set deadoccur for the initial recurring task |
|
132
|
|
|
if (isset($messageprops[$taskproperties["isrecurringtag"]]) && |
|
133
|
|
|
$messageprops[$taskproperties["isrecurringtag"]] && |
|
134
|
|
|
(!isset($messageprops[$taskproperties["deadoccur"]]) || |
|
135
|
|
|
(isset($messageprops[$taskproperties["deadoccur"]]) && |
|
136
|
|
|
!$messageprops[$taskproperties["deadoccur"]]))) { |
|
137
|
|
|
// Process recurrence |
|
138
|
|
|
$message->recurrence = new SyncTaskRecurrence(); |
|
139
|
|
|
$this->getRecurrence($mapimessage, $messageprops, $message, $message->recurrence, false); |
|
|
|
|
|
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
|
|
// when set the task to complete using the WebAccess, the dateComplete property is not set correctly |
|
143
|
|
|
if ($message->complete == 1 && !isset($message->datecompleted)) { |
|
144
|
|
|
$message->datecompleted = time(); |
|
145
|
|
|
} |
|
146
|
|
|
|
|
147
|
|
|
// if no reminder is set, announce that to the mobile |
|
148
|
|
|
if (!isset($message->reminderset)) { |
|
149
|
|
|
$message->reminderset = 0; |
|
150
|
|
|
} |
|
151
|
|
|
|
|
152
|
|
|
return $message; |
|
153
|
|
|
} |
|
154
|
|
|
|
|
155
|
|
|
/** |
|
156
|
|
|
* Reads an appointment object from MAPI. |
|
157
|
|
|
* |
|
158
|
|
|
* @param mixed $mapimessage |
|
159
|
|
|
* @param ContentParameters $contentparameters |
|
160
|
|
|
* |
|
161
|
|
|
* @return SyncAppointment |
|
162
|
|
|
*/ |
|
163
|
|
|
private function getAppointment($mapimessage, $contentparameters) { |
|
164
|
|
|
$message = new SyncAppointment(); |
|
165
|
|
|
|
|
166
|
|
|
// Standard one-to-one mappings first |
|
167
|
|
|
$this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetAppointmentMapping()); |
|
168
|
|
|
|
|
169
|
|
|
// Appointment specific props |
|
170
|
|
|
$appointmentprops = MAPIMapping::GetAppointmentProperties(); |
|
171
|
|
|
$messageprops = $this->getProps($mapimessage, $appointmentprops); |
|
172
|
|
|
|
|
173
|
|
|
// set the body according to contentparameters and supported AS version |
|
174
|
|
|
$this->setMessageBody($mapimessage, $contentparameters, $message); |
|
175
|
|
|
|
|
176
|
|
|
// Set reminder time if reminderset is true |
|
177
|
|
|
if (isset($messageprops[$appointmentprops["reminderset"]]) && $messageprops[$appointmentprops["reminderset"]] == true) { |
|
178
|
|
|
if ($messageprops[$appointmentprops["remindertime"]] == 0x5AE980E1) { |
|
179
|
|
|
$message->reminder = 15; |
|
180
|
|
|
} |
|
181
|
|
|
else { |
|
182
|
|
|
$message->reminder = $messageprops[$appointmentprops["remindertime"]]; |
|
183
|
|
|
} |
|
184
|
|
|
} |
|
185
|
|
|
|
|
186
|
|
|
if (!isset($message->uid)) { |
|
187
|
|
|
$message->uid = bin2hex($messageprops[$appointmentprops["sourcekey"]]); |
|
188
|
|
|
} |
|
189
|
|
|
else { |
|
190
|
|
|
$message->uid = Utils::GetICalUidFromOLUid($message->uid); |
|
191
|
|
|
} |
|
192
|
|
|
|
|
193
|
|
|
// Always set organizer information because some devices do not work properly without it |
|
194
|
|
|
if (isset($messageprops[$appointmentprops["representingentryid"]], $messageprops[$appointmentprops["representingname"]]) |
|
195
|
|
|
) { |
|
196
|
|
|
$message->organizeremail = w2u($this->getSMTPAddressFromEntryID($messageprops[$appointmentprops["representingentryid"]])); |
|
197
|
|
|
// if the email address can't be resolved, fall back to PR_SENT_REPRESENTING_SEARCH_KEY |
|
198
|
|
|
if ($message->organizeremail == "" && isset($messageprops[$appointmentprops["sentrepresentinsrchk"]])) { |
|
199
|
|
|
$message->organizeremail = $this->getEmailAddressFromSearchKey($messageprops[$appointmentprops["sentrepresentinsrchk"]]); |
|
200
|
|
|
} |
|
201
|
|
|
$message->organizername = w2u($messageprops[$appointmentprops["representingname"]]); |
|
202
|
|
|
} |
|
203
|
|
|
|
|
204
|
|
|
$appTz = false; // if the appointment has some timezone information saved on the server |
|
205
|
|
|
if (!empty($messageprops[$appointmentprops["timezonetag"]])) { |
|
206
|
|
|
$tz = $this->getTZFromMAPIBlob($messageprops[$appointmentprops["timezonetag"]]); |
|
207
|
|
|
$appTz = true; |
|
208
|
|
|
} |
|
209
|
|
|
elseif (!empty($messageprops[$appointmentprops["timezonedesc"]])) { |
|
210
|
|
|
// Windows uses UTC in timezone description in opposite to mstzones in TimezoneUtil which uses GMT |
|
211
|
|
|
$wintz = str_replace("UTC", "GMT", $messageprops[$appointmentprops["timezonedesc"]]); |
|
212
|
|
|
$tz = TimezoneUtil::GetFullTZFromTZName(TimezoneUtil::GetTZNameFromWinTZ($wintz)); |
|
213
|
|
|
$appTz = true; |
|
214
|
|
|
} |
|
215
|
|
|
else { |
|
216
|
|
|
// set server default timezone (correct timezone should be configured!) |
|
217
|
|
|
$tz = TimezoneUtil::GetFullTZ(); |
|
218
|
|
|
} |
|
219
|
|
|
|
|
220
|
|
|
if (isset($messageprops[$appointmentprops["isrecurring"]]) && $messageprops[$appointmentprops["isrecurring"]]) { |
|
221
|
|
|
// Process recurrence |
|
222
|
|
|
$message->recurrence = new SyncRecurrence(); |
|
223
|
|
|
$this->getRecurrence($mapimessage, $messageprops, $message, $message->recurrence, $tz); |
|
224
|
|
|
|
|
225
|
|
|
if (empty($message->alldayevent)) { |
|
226
|
|
|
$message->timezone = base64_encode(TimezoneUtil::GetSyncBlobFromTZ($tz)); |
|
227
|
|
|
} |
|
228
|
|
|
} |
|
229
|
|
|
|
|
230
|
|
|
// Do attendees |
|
231
|
|
|
$reciptable = mapi_message_getrecipienttable($mapimessage); |
|
|
|
|
|
|
232
|
|
|
// Only get first 256 recipients, to prevent possible load issues. |
|
233
|
|
|
$rows = mapi_table_queryrows($reciptable, [PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS, PR_ADDRTYPE, PR_RECIPIENT_TRACKSTATUS, PR_RECIPIENT_TYPE, PR_SEARCH_KEY], 0, 256); |
|
|
|
|
|
|
234
|
|
|
|
|
235
|
|
|
// Exception: we do not synchronize appointments with more than 250 attendees |
|
236
|
|
|
if (count($rows) > 250) { |
|
237
|
|
|
$message->id = bin2hex($messageprops[$appointmentprops["sourcekey"]]); |
|
238
|
|
|
$mbe = new SyncObjectBrokenException("Appointment has too many attendees"); |
|
239
|
|
|
$mbe->SetSyncObject($message); |
|
240
|
|
|
|
|
241
|
|
|
throw $mbe; |
|
242
|
|
|
} |
|
243
|
|
|
|
|
244
|
|
|
if (count($rows) > 0) { |
|
245
|
|
|
$message->attendees = []; |
|
246
|
|
|
} |
|
247
|
|
|
|
|
248
|
|
|
foreach ($rows as $row) { |
|
249
|
|
|
$attendee = new SyncAttendee(); |
|
250
|
|
|
|
|
251
|
|
|
$attendee->name = w2u($row[PR_DISPLAY_NAME]); |
|
252
|
|
|
// smtp address is always a proper email address |
|
253
|
|
|
if (isset($row[PR_SMTP_ADDRESS])) { |
|
254
|
|
|
$attendee->email = w2u($row[PR_SMTP_ADDRESS]); |
|
255
|
|
|
} |
|
256
|
|
|
elseif (isset($row[PR_ADDRTYPE], $row[PR_EMAIL_ADDRESS])) { |
|
257
|
|
|
// if address type is SMTP, it's also a proper email address |
|
258
|
|
|
if ($row[PR_ADDRTYPE] == "SMTP") { |
|
259
|
|
|
$attendee->email = w2u($row[PR_EMAIL_ADDRESS]); |
|
260
|
|
|
} |
|
261
|
|
|
// if address type is ZARAFA, the PR_EMAIL_ADDRESS contains username |
|
262
|
|
|
elseif ($row[PR_ADDRTYPE] == "ZARAFA") { |
|
263
|
|
|
$userinfo = @nsp_getuserinfo($row[PR_EMAIL_ADDRESS]); |
|
|
|
|
|
|
264
|
|
|
if (is_array($userinfo) && isset($userinfo["primary_email"])) { |
|
265
|
|
|
$attendee->email = w2u($userinfo["primary_email"]); |
|
266
|
|
|
} |
|
267
|
|
|
// if the user was not found, do a fallback to PR_SEARCH_KEY |
|
268
|
|
|
// @see https://jira.z-hub.io/browse/ZP-1178 |
|
269
|
|
|
elseif (isset($row[PR_SEARCH_KEY])) { |
|
270
|
|
|
$attendee->email = w2u($this->getEmailAddressFromSearchKey($row[PR_SEARCH_KEY])); |
|
271
|
|
|
} |
|
272
|
|
|
else { |
|
273
|
|
|
SLog::Write(LOGLEVEL_WARN, sprintf("MAPIProvider->getAppointment: The attendee '%s' of type ZARAFA can not be resolved. Code: 0x%X", $row[PR_EMAIL_ADDRESS], mapi_last_hresult())); |
|
|
|
|
|
|
274
|
|
|
} |
|
275
|
|
|
} |
|
276
|
|
|
} |
|
277
|
|
|
|
|
278
|
|
|
// set attendee's status and type if they're available and if we are the organizer |
|
279
|
|
|
$storeprops = $this->GetStoreProps(); |
|
280
|
|
|
if (isset($row[PR_RECIPIENT_TRACKSTATUS], $messageprops[$appointmentprops["representingentryid"]], $storeprops[PR_MAILBOX_OWNER_ENTRYID]) && |
|
281
|
|
|
$messageprops[$appointmentprops["representingentryid"]] == $storeprops[PR_MAILBOX_OWNER_ENTRYID]) { |
|
282
|
|
|
$attendee->attendeestatus = $row[PR_RECIPIENT_TRACKSTATUS]; |
|
283
|
|
|
} |
|
284
|
|
|
if (isset($row[PR_RECIPIENT_TYPE])) { |
|
285
|
|
|
$attendee->attendeetype = $row[PR_RECIPIENT_TYPE]; |
|
286
|
|
|
} |
|
287
|
|
|
// Some attendees have no email or name (eg resources), and if you |
|
288
|
|
|
// don't send one of those fields, the phone will give an error ... so |
|
289
|
|
|
// we don't send it in that case. |
|
290
|
|
|
// also ignore the "attendee" if the email is equal to the organizers' email |
|
291
|
|
|
if (isset($attendee->name, $attendee->email) && $attendee->email != "" && (!isset($message->organizeremail) || (isset($message->organizeremail) && $attendee->email != $message->organizeremail))) { |
|
292
|
|
|
array_push($message->attendees, $attendee); |
|
293
|
|
|
} |
|
294
|
|
|
} |
|
295
|
|
|
|
|
296
|
|
|
// Status 0 = no meeting, status 1 = organizer, status 2/3/4/5 = tentative/accepted/declined/notresponded |
|
297
|
|
|
if (isset($messageprops[$appointmentprops["meetingstatus"]]) && $messageprops[$appointmentprops["meetingstatus"]] > 1) { |
|
298
|
|
|
if (!isset($message->attendees) || !is_array($message->attendees)) { |
|
299
|
|
|
$message->attendees = []; |
|
300
|
|
|
} |
|
301
|
|
|
// Work around iOS6 cancellation issue when there are no attendees for this meeting. Just add ourselves as the sole attendee. |
|
302
|
|
|
if (count($message->attendees) == 0) { |
|
303
|
|
|
SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->getAppointment: adding ourself as an attendee for iOS6 workaround")); |
|
304
|
|
|
$attendee = new SyncAttendee(); |
|
305
|
|
|
|
|
306
|
|
|
$meinfo = nsp_getuserinfo(Request::GetUser()); |
|
307
|
|
|
|
|
308
|
|
|
if (is_array($meinfo)) { |
|
309
|
|
|
$attendee->email = w2u($meinfo["primary_email"]); |
|
310
|
|
|
$attendee->name = w2u($meinfo["fullname"]); |
|
311
|
|
|
$attendee->attendeetype = MAPI_TO; |
|
312
|
|
|
|
|
313
|
|
|
array_push($message->attendees, $attendee); |
|
314
|
|
|
} |
|
315
|
|
|
} |
|
316
|
|
|
$message->responsetype = $messageprops[$appointmentprops["responsestatus"]]; |
|
317
|
|
|
} |
|
318
|
|
|
|
|
319
|
|
|
// If it's an appointment which doesn't have any attendees, we have to make sure that |
|
320
|
|
|
// the user is the owner or it will not work properly with android devices |
|
321
|
|
|
// @see https://jira.z-hub.io/browse/ZP-1020 |
|
322
|
|
|
if (isset($messageprops[$appointmentprops["meetingstatus"]]) && $messageprops[$appointmentprops["meetingstatus"]] == olNonMeeting && empty($message->attendees)) { |
|
323
|
|
|
$meinfo = nsp_getuserinfo(Request::GetUser()); |
|
324
|
|
|
|
|
325
|
|
|
if (is_array($meinfo)) { |
|
326
|
|
|
$message->organizeremail = w2u($meinfo["primary_email"]); |
|
327
|
|
|
$message->organizername = w2u($meinfo["fullname"]); |
|
328
|
|
|
SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->getAppointment(): setting ourself as the organizer for an appointment without attendees."); |
|
329
|
|
|
} |
|
330
|
|
|
} |
|
331
|
|
|
|
|
332
|
|
|
if (!isset($message->nativebodytype)) { |
|
333
|
|
|
$message->nativebodytype = MAPIUtils::GetNativeBodyType($messageprops); |
|
334
|
|
|
} |
|
335
|
|
|
elseif ($message->nativebodytype == SYNC_BODYPREFERENCE_UNDEFINED) { |
|
336
|
|
|
$nbt = MAPIUtils::GetNativeBodyType($messageprops); |
|
337
|
|
|
SLog::Write(LOGLEVEL_INFO, sprintf("MAPIProvider->getAppointment(): native body type is undefined. Set it to %d.", $nbt)); |
|
338
|
|
|
$message->nativebodytype = $nbt; |
|
339
|
|
|
} |
|
340
|
|
|
|
|
341
|
|
|
// If the user is working from a location other than the office the busystatus should be interpreted as free. |
|
342
|
|
|
if (isset($message->busystatus) && $message->busystatus == fbWorkingElsewhere) { |
|
343
|
|
|
$message->busystatus = fbFree; |
|
344
|
|
|
} |
|
345
|
|
|
|
|
346
|
|
|
// If the busystatus has the value of -1, we should be interpreted as tentative (1) / ZP-581 |
|
347
|
|
|
if (isset($message->busystatus) && $message->busystatus == -1) { |
|
348
|
|
|
$message->busystatus = fbTentative; |
|
349
|
|
|
} |
|
350
|
|
|
|
|
351
|
|
|
// All-day events might appear as 24h (or multiple of it) long when they start not exactly at midnight (+/- bias of the timezone) |
|
352
|
|
|
if (isset($message->alldayevent) && $message->alldayevent) { |
|
353
|
|
|
$localStartTime = localtime($message->starttime, 1); |
|
354
|
|
|
|
|
355
|
|
|
// The appointment is all-day but doesn't start at midnight. |
|
356
|
|
|
// If it was created in another timezone and we have that information, |
|
357
|
|
|
// set the startime to the midnight of the current timezone. |
|
358
|
|
|
if ($appTz && ($localStartTime['tm_hour'] || $localStartTime['tm_min'])) { |
|
359
|
|
|
SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->getAppointment(): all-day event starting not midnight."); |
|
360
|
|
|
$duration = $message->endtime - $message->starttime; |
|
361
|
|
|
$serverTz = TimezoneUtil::GetFullTZ(); |
|
362
|
|
|
$message->starttime = $this->getGMTTimeByTZ($this->getLocaltimeByTZ($message->starttime, $tz), $serverTz); |
|
363
|
|
|
$message->endtime = $message->starttime + $duration; |
|
364
|
|
|
} |
|
365
|
|
|
} |
|
366
|
|
|
|
|
367
|
|
|
return $message; |
|
368
|
|
|
} |
|
369
|
|
|
|
|
370
|
|
|
/** |
|
371
|
|
|
* Reads recurrence information from MAPI. |
|
372
|
|
|
* |
|
373
|
|
|
* @param mixed $mapimessage |
|
374
|
|
|
* @param array $recurprops |
|
375
|
|
|
* @param SyncObject &$syncMessage the message |
|
376
|
|
|
* @param SyncObject &$syncRecurrence the recurrence message |
|
377
|
|
|
* @param array $tz timezone information |
|
378
|
|
|
* |
|
379
|
|
|
* @return |
|
380
|
|
|
*/ |
|
381
|
|
|
private function getRecurrence($mapimessage, $recurprops, &$syncMessage, &$syncRecurrence, $tz) { |
|
382
|
|
|
if ($syncRecurrence instanceof SyncTaskRecurrence) { |
|
383
|
|
|
$recurrence = new TaskRecurrence($this->store, $mapimessage); |
|
384
|
|
|
} |
|
385
|
|
|
else { |
|
386
|
|
|
$recurrence = new Recurrence($this->store, $mapimessage); |
|
387
|
|
|
} |
|
388
|
|
|
|
|
389
|
|
|
switch ($recurrence->recur["type"]) { |
|
390
|
|
|
case 10: // daily |
|
391
|
|
|
switch ($recurrence->recur["subtype"]) { |
|
392
|
|
|
default: |
|
393
|
|
|
$syncRecurrence->type = 0; |
|
394
|
|
|
break; |
|
395
|
|
|
|
|
396
|
|
|
case 1: |
|
397
|
|
|
$syncRecurrence->type = 0; |
|
398
|
|
|
$syncRecurrence->dayofweek = 62; // mon-fri |
|
399
|
|
|
$syncRecurrence->interval = 1; |
|
400
|
|
|
break; |
|
401
|
|
|
} |
|
402
|
|
|
break; |
|
403
|
|
|
|
|
404
|
|
|
case 11: // weekly |
|
405
|
|
|
$syncRecurrence->type = 1; |
|
406
|
|
|
break; |
|
407
|
|
|
|
|
408
|
|
|
case 12: // monthly |
|
409
|
|
|
switch ($recurrence->recur["subtype"]) { |
|
410
|
|
|
default: |
|
411
|
|
|
$syncRecurrence->type = 2; |
|
412
|
|
|
break; |
|
413
|
|
|
|
|
414
|
|
|
case 3: |
|
415
|
|
|
$syncRecurrence->type = 3; |
|
416
|
|
|
break; |
|
417
|
|
|
} |
|
418
|
|
|
break; |
|
419
|
|
|
|
|
420
|
|
|
case 13: // yearly |
|
421
|
|
|
switch ($recurrence->recur["subtype"]) { |
|
422
|
|
|
default: |
|
423
|
|
|
$syncRecurrence->type = 4; |
|
424
|
|
|
break; |
|
425
|
|
|
|
|
426
|
|
|
case 2: |
|
427
|
|
|
$syncRecurrence->type = 5; |
|
428
|
|
|
break; |
|
429
|
|
|
|
|
430
|
|
|
case 3: |
|
431
|
|
|
$syncRecurrence->type = 6; |
|
432
|
|
|
break; |
|
433
|
|
|
} |
|
434
|
|
|
} |
|
435
|
|
|
// Termination |
|
436
|
|
|
switch ($recurrence->recur["term"]) { |
|
437
|
|
|
case 0x21: |
|
438
|
|
|
$syncRecurrence->until = $recurrence->recur["end"]; |
|
439
|
|
|
// fixes Mantis #350 : recur-end does not consider timezones - use ClipEnd if available |
|
440
|
|
|
if (isset($recurprops[$recurrence->proptags["enddate_recurring"]])) { |
|
441
|
|
|
$syncRecurrence->until = $recurprops[$recurrence->proptags["enddate_recurring"]]; |
|
442
|
|
|
} |
|
443
|
|
|
// add one day (minus 1 sec) to the end time to make sure the last occurrence is covered |
|
444
|
|
|
$syncRecurrence->until += 86399; |
|
445
|
|
|
break; |
|
446
|
|
|
|
|
447
|
|
|
case 0x22: |
|
448
|
|
|
$syncRecurrence->occurrences = $recurrence->recur["numoccur"]; |
|
449
|
|
|
break; |
|
450
|
|
|
|
|
451
|
|
|
case 0x23: |
|
452
|
|
|
// never ends |
|
453
|
|
|
break; |
|
454
|
|
|
} |
|
455
|
|
|
|
|
456
|
|
|
// Correct 'alldayevent' because outlook fails to set it on recurring items of 24 hours or longer |
|
457
|
|
|
if (isset($recurrence->recur["endocc"], $recurrence->recur["startocc"]) && ($recurrence->recur["endocc"] - $recurrence->recur["startocc"] >= 1440)) { |
|
458
|
|
|
$syncMessage->alldayevent = true; |
|
459
|
|
|
} |
|
460
|
|
|
|
|
461
|
|
|
// Interval is different according to the type/subtype |
|
462
|
|
|
switch ($recurrence->recur["type"]) { |
|
463
|
|
|
case 10: |
|
464
|
|
|
if ($recurrence->recur["subtype"] == 0) { |
|
465
|
|
|
$syncRecurrence->interval = (int) ($recurrence->recur["everyn"] / 1440); |
|
466
|
|
|
} // minutes |
|
467
|
|
|
break; |
|
468
|
|
|
|
|
469
|
|
|
case 11: |
|
470
|
|
|
case 12: |
|
471
|
|
|
$syncRecurrence->interval = $recurrence->recur["everyn"]; |
|
472
|
|
|
break; // months / weeks |
|
473
|
|
|
|
|
474
|
|
|
case 13: |
|
475
|
|
|
$syncRecurrence->interval = (int) ($recurrence->recur["everyn"] / 12); |
|
476
|
|
|
break; // months |
|
477
|
|
|
} |
|
478
|
|
|
|
|
479
|
|
|
if (isset($recurrence->recur["weekdays"])) { |
|
480
|
|
|
$syncRecurrence->dayofweek = $recurrence->recur["weekdays"]; |
|
481
|
|
|
} // bitmask of days (1 == sunday, 128 == saturday |
|
482
|
|
|
if (isset($recurrence->recur["nday"])) { |
|
483
|
|
|
$syncRecurrence->weekofmonth = $recurrence->recur["nday"]; |
|
484
|
|
|
} // N'th {DAY} of {X} (0-5) |
|
485
|
|
|
if (isset($recurrence->recur["month"])) { |
|
486
|
|
|
$syncRecurrence->monthofyear = (int) ($recurrence->recur["month"] / (60 * 24 * 29)) + 1; |
|
487
|
|
|
} // works ok due to rounding. see also $monthminutes below (1-12) |
|
488
|
|
|
if (isset($recurrence->recur["monthday"])) { |
|
489
|
|
|
$syncRecurrence->dayofmonth = $recurrence->recur["monthday"]; |
|
490
|
|
|
} // day of month (1-31) |
|
491
|
|
|
|
|
492
|
|
|
// All changed exceptions are appointments within the 'exceptions' array. They contain the same items as a normal appointment |
|
493
|
|
|
foreach ($recurrence->recur["changed_occurrences"] as $change) { |
|
494
|
|
|
$exception = new SyncAppointmentException(); |
|
495
|
|
|
|
|
496
|
|
|
// start, end, basedate, subject, remind_before, reminderset, location, busystatus, alldayevent, label |
|
497
|
|
|
if (isset($change["start"])) { |
|
498
|
|
|
$exception->starttime = $this->getGMTTimeByTZ($change["start"], $tz); |
|
499
|
|
|
} |
|
500
|
|
|
if (isset($change["end"])) { |
|
501
|
|
|
$exception->endtime = $this->getGMTTimeByTZ($change["end"], $tz); |
|
502
|
|
|
} |
|
503
|
|
|
if (isset($change["basedate"])) { |
|
504
|
|
|
$exception->exceptionstarttime = $this->getGMTTimeByTZ($this->getDayStartOfTimestamp($change["basedate"]) + $recurrence->recur["startocc"] * 60, $tz); |
|
|
|
|
|
|
505
|
|
|
|
|
506
|
|
|
// open body because getting only property might not work because of memory limit |
|
507
|
|
|
$exceptionatt = $recurrence->getExceptionAttachment($change["basedate"]); |
|
|
|
|
|
|
508
|
|
|
if ($exceptionatt) { |
|
509
|
|
|
$exceptionobj = mapi_attach_openobj($exceptionatt, 0); |
|
|
|
|
|
|
510
|
|
|
$this->setMessageBodyForType($exceptionobj, SYNC_BODYPREFERENCE_PLAIN, $exception); |
|
511
|
|
|
} |
|
512
|
|
|
} |
|
513
|
|
|
if (isset($change["subject"])) { |
|
514
|
|
|
$exception->subject = w2u($change["subject"]); |
|
515
|
|
|
} |
|
516
|
|
|
if (isset($change["reminder_before"]) && $change["reminder_before"]) { |
|
517
|
|
|
$exception->reminder = $change["remind_before"]; |
|
518
|
|
|
} |
|
519
|
|
|
if (isset($change["location"])) { |
|
520
|
|
|
$exception->location = w2u($change["location"]); |
|
521
|
|
|
} |
|
522
|
|
|
if (isset($change["busystatus"])) { |
|
523
|
|
|
$exception->busystatus = $change["busystatus"]; |
|
524
|
|
|
} |
|
525
|
|
|
if (isset($change["alldayevent"])) { |
|
526
|
|
|
$exception->alldayevent = $change["alldayevent"]; |
|
527
|
|
|
} |
|
528
|
|
|
|
|
529
|
|
|
// set some data from the original appointment |
|
530
|
|
|
if (isset($syncMessage->uid)) { |
|
531
|
|
|
$exception->uid = $syncMessage->uid; |
|
532
|
|
|
} |
|
533
|
|
|
if (isset($syncMessage->organizername)) { |
|
534
|
|
|
$exception->organizername = $syncMessage->organizername; |
|
535
|
|
|
} |
|
536
|
|
|
if (isset($syncMessage->organizeremail)) { |
|
537
|
|
|
$exception->organizeremail = $syncMessage->organizeremail; |
|
538
|
|
|
} |
|
539
|
|
|
|
|
540
|
|
|
if (!isset($syncMessage->exceptions)) { |
|
541
|
|
|
$syncMessage->exceptions = []; |
|
542
|
|
|
} |
|
543
|
|
|
|
|
544
|
|
|
// If the user is working from a location other than the office the busystatus should be interpreted as free. |
|
545
|
|
|
if (isset($exception->busystatus) && $exception->busystatus == fbWorkingElsewhere) { |
|
546
|
|
|
$exception->busystatus = fbFree; |
|
547
|
|
|
} |
|
548
|
|
|
|
|
549
|
|
|
// If the busystatus has the value of -1, we should be interpreted as tentative (1) / ZP-581 |
|
550
|
|
|
if (isset($exception->busystatus) && $exception->busystatus == -1) { |
|
551
|
|
|
$exception->busystatus = fbTentative; |
|
552
|
|
|
} |
|
553
|
|
|
|
|
554
|
|
|
// if an exception lasts 24 hours and the series are an allday events, set also the exception to allday event, |
|
555
|
|
|
// otherwise it will be a 24 hour long event on some mobiles. |
|
556
|
|
|
// @see https://jira.z-hub.io/browse/ZP-980 |
|
557
|
|
|
if (isset($exception->starttime, $exception->endtime) && ($exception->endtime - $exception->starttime == 86400) && $syncMessage->alldayevent) { |
|
558
|
|
|
$exception->alldayevent = 1; |
|
559
|
|
|
} |
|
560
|
|
|
array_push($syncMessage->exceptions, $exception); |
|
561
|
|
|
} |
|
562
|
|
|
|
|
563
|
|
|
// Deleted appointments contain only the original date (basedate) and a 'deleted' tag |
|
564
|
|
|
foreach ($recurrence->recur["deleted_occurrences"] as $deleted) { |
|
565
|
|
|
$exception = new SyncAppointmentException(); |
|
566
|
|
|
|
|
567
|
|
|
$exception->exceptionstarttime = $this->getGMTTimeByTZ($this->getDayStartOfTimestamp($deleted) + $recurrence->recur["startocc"] * 60, $tz); |
|
568
|
|
|
$exception->deleted = "1"; |
|
569
|
|
|
|
|
570
|
|
|
if (!isset($syncMessage->exceptions)) { |
|
571
|
|
|
$syncMessage->exceptions = []; |
|
572
|
|
|
} |
|
573
|
|
|
|
|
574
|
|
|
array_push($syncMessage->exceptions, $exception); |
|
575
|
|
|
} |
|
576
|
|
|
|
|
577
|
|
|
if (isset($syncMessage->complete) && $syncMessage->complete) { |
|
578
|
|
|
$syncRecurrence->complete = $syncMessage->complete; |
|
579
|
|
|
} |
|
580
|
|
|
} |
|
581
|
|
|
|
|
582
|
|
|
/** |
|
583
|
|
|
* Reads an email object from MAPI. |
|
584
|
|
|
* |
|
585
|
|
|
* @param mixed $mapimessage |
|
586
|
|
|
* @param ContentParameters $contentparameters |
|
587
|
|
|
* |
|
588
|
|
|
* @return SyncEmail |
|
589
|
|
|
*/ |
|
590
|
|
|
private function getEmail($mapimessage, $contentparameters) { |
|
591
|
|
|
// This workaround fixes ZP-729 and still works with Outlook. |
|
592
|
|
|
// FIXME: It should be properly fixed when refactoring. |
|
593
|
|
|
$bpReturnType = Utils::GetBodyPreferenceBestMatch($contentparameters->GetBodyPreference()); |
|
|
|
|
|
|
594
|
|
|
if (($contentparameters->GetMimeSupport() == SYNC_MIMESUPPORT_NEVER) || |
|
|
|
|
|
|
595
|
|
|
($key = array_search(SYNC_BODYPREFERENCE_MIME, $contentparameters->GetBodyPreference()) === false) || |
|
|
|
|
|
|
596
|
|
|
$bpReturnType != SYNC_BODYPREFERENCE_MIME) { |
|
597
|
|
|
MAPIUtils::ParseSmime($this->session, $this->store, $this->getAddressbook(), $mapimessage); |
|
|
|
|
|
|
598
|
|
|
} |
|
599
|
|
|
|
|
600
|
|
|
$message = new SyncMail(); |
|
601
|
|
|
|
|
602
|
|
|
$this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetEmailMapping()); |
|
603
|
|
|
|
|
604
|
|
|
$emailproperties = MAPIMapping::GetEmailProperties(); |
|
605
|
|
|
$messageprops = $this->getProps($mapimessage, $emailproperties); |
|
606
|
|
|
|
|
607
|
|
|
if (isset($messageprops[PR_SOURCE_KEY])) { |
|
608
|
|
|
$sourcekey = $messageprops[PR_SOURCE_KEY]; |
|
|
|
|
|
|
609
|
|
|
} |
|
610
|
|
|
else { |
|
611
|
|
|
$mbe = new SyncObjectBrokenException("The message doesn't have a sourcekey"); |
|
612
|
|
|
$mbe->SetSyncObject($message); |
|
613
|
|
|
|
|
614
|
|
|
throw $mbe; |
|
615
|
|
|
} |
|
616
|
|
|
|
|
617
|
|
|
// set the body according to contentparameters and supported AS version |
|
618
|
|
|
$this->setMessageBody($mapimessage, $contentparameters, $message); |
|
619
|
|
|
|
|
620
|
|
|
$fromname = $fromaddr = ""; |
|
621
|
|
|
|
|
622
|
|
|
if (isset($messageprops[$emailproperties["representingname"]])) { |
|
623
|
|
|
// remove encapsulating double quotes from the representingname |
|
624
|
|
|
$fromname = preg_replace('/^\"(.*)\"$/', "\${1}", $messageprops[$emailproperties["representingname"]]); |
|
625
|
|
|
} |
|
626
|
|
|
if (isset($messageprops[$emailproperties["representingentryid"]])) { |
|
627
|
|
|
$fromaddr = $this->getSMTPAddressFromEntryID($messageprops[$emailproperties["representingentryid"]]); |
|
628
|
|
|
} |
|
629
|
|
|
|
|
630
|
|
|
// if the email address can't be resolved, fall back to PR_SENT_REPRESENTING_SEARCH_KEY |
|
631
|
|
|
if ($fromaddr == "" && isset($messageprops[$emailproperties["representingsearchkey"]])) { |
|
632
|
|
|
$fromaddr = $this->getEmailAddressFromSearchKey($messageprops[$emailproperties["representingsearchkey"]]); |
|
633
|
|
|
} |
|
634
|
|
|
|
|
635
|
|
|
if ($fromname == $fromaddr) { |
|
636
|
|
|
$fromname = ""; |
|
637
|
|
|
} |
|
638
|
|
|
|
|
639
|
|
|
if ($fromname) { |
|
640
|
|
|
$from = "\"" . w2u($fromname) . "\" <" . w2u($fromaddr) . ">"; |
|
|
|
|
|
|
641
|
|
|
} |
|
642
|
|
|
else { // START CHANGED dw2412 HTC shows "error" if sender name is unknown |
|
643
|
|
|
$from = "\"" . w2u($fromaddr) . "\" <" . w2u($fromaddr) . ">"; |
|
644
|
|
|
} |
|
645
|
|
|
// END CHANGED dw2412 HTC shows "error" if sender name is unknown |
|
646
|
|
|
|
|
647
|
|
|
$message->from = $from; |
|
648
|
|
|
|
|
649
|
|
|
// process Meeting Requests |
|
650
|
|
|
if (isset($message->messageclass) && strpos($message->messageclass, "IPM.Schedule.Meeting") === 0) { |
|
651
|
|
|
$message->meetingrequest = new SyncMeetingRequest(); |
|
652
|
|
|
$this->getPropsFromMAPI($message->meetingrequest, $mapimessage, MAPIMapping::GetMeetingRequestMapping()); |
|
653
|
|
|
|
|
654
|
|
|
$meetingrequestproperties = MAPIMapping::GetMeetingRequestProperties(); |
|
655
|
|
|
$props = $this->getProps($mapimessage, $meetingrequestproperties); |
|
656
|
|
|
|
|
657
|
|
|
// Get the GOID |
|
658
|
|
|
if (isset($props[$meetingrequestproperties["goidtag"]])) { |
|
659
|
|
|
$message->meetingrequest->globalobjid = base64_encode($props[$meetingrequestproperties["goidtag"]]); |
|
660
|
|
|
} |
|
661
|
|
|
|
|
662
|
|
|
// Set Timezone |
|
663
|
|
|
if (isset($props[$meetingrequestproperties["timezonetag"]])) { |
|
664
|
|
|
$tz = $this->getTZFromMAPIBlob($props[$meetingrequestproperties["timezonetag"]]); |
|
665
|
|
|
} |
|
666
|
|
|
else { |
|
667
|
|
|
$tz = TimezoneUtil::GetFullTZ(); |
|
668
|
|
|
} |
|
669
|
|
|
|
|
670
|
|
|
$message->meetingrequest->timezone = base64_encode(TimezoneUtil::GetSyncBlobFromTZ($tz)); |
|
671
|
|
|
|
|
672
|
|
|
// send basedate if exception |
|
673
|
|
|
if (isset($props[$meetingrequestproperties["recReplTime"]]) || |
|
674
|
|
|
(isset($props[$meetingrequestproperties["lidIsException"]]) && $props[$meetingrequestproperties["lidIsException"]] == true)) { |
|
675
|
|
|
if (isset($props[$meetingrequestproperties["recReplTime"]])) { |
|
676
|
|
|
$basedate = $props[$meetingrequestproperties["recReplTime"]]; |
|
677
|
|
|
$message->meetingrequest->recurrenceid = $this->getGMTTimeByTZ($basedate, $this->getGMTTZ()); |
|
678
|
|
|
} |
|
679
|
|
|
else { |
|
680
|
|
|
if (!isset($props[$meetingrequestproperties["goidtag"]]) || !isset($props[$meetingrequestproperties["recurStartTime"]]) || !isset($props[$meetingrequestproperties["timezonetag"]])) { |
|
681
|
|
|
SLog::Write(LOGLEVEL_WARN, "Missing property to set correct basedate for exception"); |
|
682
|
|
|
} |
|
683
|
|
|
else { |
|
684
|
|
|
$basedate = Utils::ExtractBaseDate($props[$meetingrequestproperties["goidtag"]], $props[$meetingrequestproperties["recurStartTime"]]); |
|
685
|
|
|
$message->meetingrequest->recurrenceid = $this->getGMTTimeByTZ($basedate, $tz); |
|
686
|
|
|
} |
|
687
|
|
|
} |
|
688
|
|
|
} |
|
689
|
|
|
|
|
690
|
|
|
// Organizer is the sender |
|
691
|
|
|
if (strpos($message->messageclass, "IPM.Schedule.Meeting.Resp") === 0) { |
|
692
|
|
|
$message->meetingrequest->organizer = $message->to; |
|
693
|
|
|
} |
|
694
|
|
|
else { |
|
695
|
|
|
$message->meetingrequest->organizer = $message->from; |
|
696
|
|
|
} |
|
697
|
|
|
|
|
698
|
|
|
// Process recurrence |
|
699
|
|
|
if (isset($props[$meetingrequestproperties["isrecurringtag"]]) && $props[$meetingrequestproperties["isrecurringtag"]]) { |
|
700
|
|
|
$myrec = new SyncMeetingRequestRecurrence(); |
|
701
|
|
|
// get recurrence -> put $message->meetingrequest as message so the 'alldayevent' is set correctly |
|
702
|
|
|
$this->getRecurrence($mapimessage, $props, $message->meetingrequest, $myrec, $tz); |
|
703
|
|
|
$message->meetingrequest->recurrences = [$myrec]; |
|
704
|
|
|
} |
|
705
|
|
|
|
|
706
|
|
|
// Force the 'alldayevent' in the object at all times. (non-existent == 0) |
|
707
|
|
|
if (!isset($message->meetingrequest->alldayevent) || $message->meetingrequest->alldayevent == "") { |
|
708
|
|
|
$message->meetingrequest->alldayevent = 0; |
|
709
|
|
|
} |
|
710
|
|
|
|
|
711
|
|
|
// Instancetype |
|
712
|
|
|
// 0 = single appointment |
|
713
|
|
|
// 1 = master recurring appointment |
|
714
|
|
|
// 2 = single instance of recurring appointment |
|
715
|
|
|
// 3 = exception of recurring appointment |
|
716
|
|
|
$message->meetingrequest->instancetype = 0; |
|
717
|
|
|
if (isset($props[$meetingrequestproperties["isrecurringtag"]]) && $props[$meetingrequestproperties["isrecurringtag"]] == 1) { |
|
718
|
|
|
$message->meetingrequest->instancetype = 1; |
|
719
|
|
|
} |
|
720
|
|
|
elseif ((!isset($props[$meetingrequestproperties["isrecurringtag"]]) || $props[$meetingrequestproperties["isrecurringtag"]] == 0) && isset($message->meetingrequest->recurrenceid)) { |
|
721
|
|
|
if (isset($props[$meetingrequestproperties["appSeqNr"]]) && $props[$meetingrequestproperties["appSeqNr"]] == 0) { |
|
722
|
|
|
$message->meetingrequest->instancetype = 2; |
|
723
|
|
|
} |
|
724
|
|
|
else { |
|
725
|
|
|
$message->meetingrequest->instancetype = 3; |
|
726
|
|
|
} |
|
727
|
|
|
} |
|
728
|
|
|
|
|
729
|
|
|
// Disable reminder if it is off |
|
730
|
|
|
if (!isset($props[$meetingrequestproperties["reminderset"]]) || $props[$meetingrequestproperties["reminderset"]] == false) { |
|
731
|
|
|
$message->meetingrequest->reminder = ""; |
|
732
|
|
|
} |
|
733
|
|
|
// the property saves reminder in minutes, but we need it in secs |
|
734
|
|
|
else { |
|
735
|
|
|
// /set the default reminder time to seconds |
|
736
|
|
|
if ($props[$meetingrequestproperties["remindertime"]] == 0x5AE980E1) { |
|
737
|
|
|
$message->meetingrequest->reminder = 900; |
|
738
|
|
|
} |
|
739
|
|
|
else { |
|
740
|
|
|
$message->meetingrequest->reminder = $props[$meetingrequestproperties["remindertime"]] * 60; |
|
741
|
|
|
} |
|
742
|
|
|
} |
|
743
|
|
|
|
|
744
|
|
|
// Set sensitivity to 0 if missing |
|
745
|
|
|
if (!isset($message->meetingrequest->sensitivity)) { |
|
746
|
|
|
$message->meetingrequest->sensitivity = 0; |
|
747
|
|
|
} |
|
748
|
|
|
|
|
749
|
|
|
// If the user is working from a location other than the office the busystatus should be interpreted as free. |
|
750
|
|
|
if (isset($message->meetingrequest->busystatus) && $message->meetingrequest->busystatus == fbWorkingElsewhere) { |
|
751
|
|
|
$message->meetingrequest->busystatus = fbFree; |
|
752
|
|
|
} |
|
753
|
|
|
|
|
754
|
|
|
// If the busystatus has the value of -1, we should be interpreted as tentative (1) / ZP-581 |
|
755
|
|
|
if (isset($message->meetingrequest->busystatus) && $message->meetingrequest->busystatus == -1) { |
|
756
|
|
|
$message->meetingrequest->busystatus = fbTentative; |
|
757
|
|
|
} |
|
758
|
|
|
|
|
759
|
|
|
// if a meeting request response hasn't been processed yet, |
|
760
|
|
|
// do it so that the attendee status is updated on the mobile |
|
761
|
|
|
if (!isset($messageprops[$emailproperties["processed"]])) { |
|
762
|
|
|
// check if we are not sending the MR so we can process it - ZP-581 |
|
763
|
|
|
$cuser = GSync::GetBackend()->GetUserDetails(GSync::GetBackend()->GetCurrentUsername()); |
|
764
|
|
|
if (isset($cuser["emailaddress"]) && $cuser["emailaddress"] != $fromaddr) { |
|
765
|
|
|
if (!isset($req)) { |
|
|
|
|
|
|
766
|
|
|
$req = new Meetingrequest($this->store, $mapimessage, $this->session); |
|
767
|
|
|
} |
|
768
|
|
|
if ($req->isMeetingRequestResponse()) { |
|
769
|
|
|
$req->processMeetingRequestResponse(); |
|
770
|
|
|
} |
|
771
|
|
|
if ($req->isMeetingCancellation()) { |
|
772
|
|
|
$req->processMeetingCancellation(); |
|
773
|
|
|
} |
|
774
|
|
|
} |
|
775
|
|
|
} |
|
776
|
|
|
$message->contentclass = DEFAULT_CALENDAR_CONTENTCLASS; |
|
777
|
|
|
|
|
778
|
|
|
// MeetingMessageType values |
|
779
|
|
|
// 0 = A silent update was performed, or the message type is unspecified. |
|
780
|
|
|
// 1 = Initial meeting request. |
|
781
|
|
|
// 2 = Full update. |
|
782
|
|
|
// 3 = Informational update. |
|
783
|
|
|
// 4 = Outdated. A newer meeting request or meeting update was received after this message. |
|
784
|
|
|
// 5 = Identifies the delegator's copy of the meeting request. |
|
785
|
|
|
// 6 = Identifies that the meeting request has been delegated and the meeting request cannot be responded to. |
|
786
|
|
|
$message->meetingrequest->meetingmessagetype = mtgEmpty; |
|
787
|
|
|
|
|
788
|
|
|
if (isset($props[$meetingrequestproperties["meetingType"]])) { |
|
789
|
|
|
switch ($props[$meetingrequestproperties["meetingType"]]) { |
|
790
|
|
|
case mtgRequest: |
|
791
|
|
|
$message->meetingrequest->meetingmessagetype = 1; |
|
792
|
|
|
break; |
|
793
|
|
|
|
|
794
|
|
|
case mtgFull: |
|
795
|
|
|
$message->meetingrequest->meetingmessagetype = 2; |
|
796
|
|
|
break; |
|
797
|
|
|
|
|
798
|
|
|
case mtgInfo: |
|
799
|
|
|
$message->meetingrequest->meetingmessagetype = 3; |
|
800
|
|
|
break; |
|
801
|
|
|
|
|
802
|
|
|
case mtgOutOfDate: |
|
803
|
|
|
$message->meetingrequest->meetingmessagetype = 4; |
|
804
|
|
|
break; |
|
805
|
|
|
|
|
806
|
|
|
case mtgDelegatorCopy: |
|
807
|
|
|
$message->meetingrequest->meetingmessagetype = 5; |
|
808
|
|
|
break; |
|
809
|
|
|
} |
|
810
|
|
|
} |
|
811
|
|
|
} |
|
812
|
|
|
|
|
813
|
|
|
// Add attachments |
|
814
|
|
|
$attachtable = mapi_message_getattachmenttable($mapimessage); |
|
|
|
|
|
|
815
|
|
|
$rows = mapi_table_queryallrows($attachtable, [PR_ATTACH_NUM]); |
|
|
|
|
|
|
816
|
|
|
$entryid = bin2hex($messageprops[$emailproperties["entryid"]]); |
|
817
|
|
|
$parentSourcekey = bin2hex($messageprops[$emailproperties["parentsourcekey"]]); |
|
818
|
|
|
|
|
819
|
|
|
foreach ($rows as $row) { |
|
820
|
|
|
if (isset($row[PR_ATTACH_NUM])) { |
|
821
|
|
|
if (Request::GetProtocolVersion() >= 12.0) { |
|
822
|
|
|
$attach = new SyncBaseAttachment(); |
|
823
|
|
|
} |
|
824
|
|
|
else { |
|
825
|
|
|
$attach = new SyncAttachment(); |
|
826
|
|
|
} |
|
827
|
|
|
|
|
828
|
|
|
$mapiattach = mapi_message_openattach($mapimessage, $row[PR_ATTACH_NUM]); |
|
|
|
|
|
|
829
|
|
|
$attachprops = mapi_getprops($mapiattach, [PR_ATTACH_LONG_FILENAME, PR_ATTACH_FILENAME, PR_ATTACHMENT_HIDDEN, PR_ATTACH_CONTENT_ID, PR_ATTACH_CONTENT_ID_W, PR_ATTACH_MIME_TAG, PR_ATTACH_MIME_TAG_W, PR_ATTACH_METHOD, PR_DISPLAY_NAME, PR_DISPLAY_NAME_W, PR_ATTACH_SIZE, PR_ATTACH_FLAGS]); |
|
|
|
|
|
|
830
|
|
|
if ((isset($attachprops[PR_ATTACH_MIME_TAG]) && strpos(strtolower($attachprops[PR_ATTACH_MIME_TAG]), 'signed') !== false) || |
|
831
|
|
|
(isset($attachprops[PR_ATTACH_MIME_TAG_W]) && strpos(strtolower($attachprops[PR_ATTACH_MIME_TAG_W]), 'signed') !== false)) { |
|
832
|
|
|
continue; |
|
833
|
|
|
} |
|
834
|
|
|
|
|
835
|
|
|
// the displayname is handled equally for all AS versions |
|
836
|
|
|
$attach->displayname = w2u((isset($attachprops[PR_ATTACH_LONG_FILENAME])) ? $attachprops[PR_ATTACH_LONG_FILENAME] : ((isset($attachprops[PR_ATTACH_FILENAME])) ? $attachprops[PR_ATTACH_FILENAME] : ((isset($attachprops[PR_DISPLAY_NAME])) ? $attachprops[PR_DISPLAY_NAME] : "attachment.bin"))); |
|
837
|
|
|
// fix attachment name in case of inline images |
|
838
|
|
|
if (($attach->displayname == "inline.txt" && (isset($attachprops[PR_ATTACH_MIME_TAG]) || $attachprops[PR_ATTACH_MIME_TAG_W])) || |
|
|
|
|
|
|
839
|
|
|
(substr_compare($attach->displayname, "attachment", 0, 10, true) === 0 && substr_compare($attach->displayname, ".dat", -4, 4, true) === 0)) { |
|
|
|
|
|
|
840
|
|
|
$mimetype = (isset($attachprops[PR_ATTACH_MIME_TAG])) ? $attachprops[PR_ATTACH_MIME_TAG] : $attachprops[PR_ATTACH_MIME_TAG_W]; |
|
841
|
|
|
$mime = explode("/", $mimetype); |
|
842
|
|
|
|
|
843
|
|
|
if (count($mime) == 2 && $mime[0] == "image") { |
|
844
|
|
|
$attach->displayname = "inline." . $mime[1]; |
|
845
|
|
|
} |
|
846
|
|
|
} |
|
847
|
|
|
|
|
848
|
|
|
// set AS version specific parameters |
|
849
|
|
|
if (Request::GetProtocolVersion() >= 12.0) { |
|
850
|
|
|
$attach->filereference = sprintf("%s:%s:%s", $entryid, $row[PR_ATTACH_NUM], $parentSourcekey); |
|
|
|
|
|
|
851
|
|
|
$attach->method = (isset($attachprops[PR_ATTACH_METHOD])) ? $attachprops[PR_ATTACH_METHOD] : ATTACH_BY_VALUE; |
|
|
|
|
|
|
852
|
|
|
|
|
853
|
|
|
// if displayname does not have the eml extension for embedde messages, android and WP devices won't open it |
|
854
|
|
|
if ($attach->method == ATTACH_EMBEDDED_MSG) { |
|
855
|
|
|
if (strtolower(substr($attach->displayname, -4)) != '.eml') { |
|
856
|
|
|
$attach->displayname .= '.eml'; |
|
857
|
|
|
} |
|
858
|
|
|
} |
|
859
|
|
|
// android devices require attachment size in order to display an attachment properly |
|
860
|
|
|
if (!isset($attachprops[PR_ATTACH_SIZE])) { |
|
861
|
|
|
$stream = mapi_openproperty($mapiattach, PR_ATTACH_DATA_BIN, IID_IStream, 0, 0); |
|
|
|
|
|
|
862
|
|
|
// It's not possible to open some (embedded only?) messages, so we need to open the attachment object itself to get the data |
|
863
|
|
|
if (mapi_last_hresult()) { |
|
|
|
|
|
|
864
|
|
|
$embMessage = mapi_attach_openobj($mapiattach); |
|
|
|
|
|
|
865
|
|
|
$addrbook = $this->getAddressbook(); |
|
866
|
|
|
$stream = mapi_inetmapi_imtoinet($this->session, $addrbook, $embMessage, ['use_tnef' => -1]); |
|
|
|
|
|
|
867
|
|
|
} |
|
868
|
|
|
$stat = mapi_stream_stat($stream); |
|
|
|
|
|
|
869
|
|
|
$attach->estimatedDataSize = $stat['cb']; |
|
|
|
|
|
|
870
|
|
|
} |
|
871
|
|
|
else { |
|
872
|
|
|
$attach->estimatedDataSize = $attachprops[PR_ATTACH_SIZE]; |
|
873
|
|
|
} |
|
874
|
|
|
|
|
875
|
|
|
if (isset($attachprops[PR_ATTACH_CONTENT_ID]) && $attachprops[PR_ATTACH_CONTENT_ID]) { |
|
876
|
|
|
$attach->contentid = $attachprops[PR_ATTACH_CONTENT_ID]; |
|
|
|
|
|
|
877
|
|
|
} |
|
878
|
|
|
|
|
879
|
|
|
if (!isset($attach->contentid) && isset($attachprops[PR_ATTACH_CONTENT_ID_W]) && $attachprops[PR_ATTACH_CONTENT_ID_W]) { |
|
880
|
|
|
$attach->contentid = $attachprops[PR_ATTACH_CONTENT_ID_W]; |
|
881
|
|
|
} |
|
882
|
|
|
|
|
883
|
|
|
if (isset($attachprops[PR_ATTACHMENT_HIDDEN]) && $attachprops[PR_ATTACHMENT_HIDDEN]) { |
|
884
|
|
|
$attach->isinline = 1; |
|
|
|
|
|
|
885
|
|
|
} |
|
886
|
|
|
|
|
887
|
|
|
if (isset($attach->contentid, $attachprops[PR_ATTACH_FLAGS]) && $attachprops[PR_ATTACH_FLAGS] & 4) { |
|
888
|
|
|
$attach->isinline = 1; |
|
889
|
|
|
} |
|
890
|
|
|
|
|
891
|
|
|
if (!isset($message->asattachments)) { |
|
892
|
|
|
$message->asattachments = []; |
|
893
|
|
|
} |
|
894
|
|
|
|
|
895
|
|
|
array_push($message->asattachments, $attach); |
|
896
|
|
|
} |
|
897
|
|
|
else { |
|
898
|
|
|
$attach->attsize = $attachprops[PR_ATTACH_SIZE]; |
|
|
|
|
|
|
899
|
|
|
$attach->attname = sprintf("%s:%s:%s", $entryid, $row[PR_ATTACH_NUM], $parentSourcekey); |
|
|
|
|
|
|
900
|
|
|
if (!isset($message->attachments)) { |
|
901
|
|
|
$message->attachments = []; |
|
902
|
|
|
} |
|
903
|
|
|
|
|
904
|
|
|
array_push($message->attachments, $attach); |
|
905
|
|
|
} |
|
906
|
|
|
} |
|
907
|
|
|
} |
|
908
|
|
|
|
|
909
|
|
|
// Get To/Cc as SMTP addresses (this is different from displayto and displaycc because we are putting |
|
910
|
|
|
// in the SMTP addresses as well, while displayto and displaycc could just contain the display names |
|
911
|
|
|
$message->to = []; |
|
912
|
|
|
$message->cc = []; |
|
913
|
|
|
|
|
914
|
|
|
$reciptable = mapi_message_getrecipienttable($mapimessage); |
|
|
|
|
|
|
915
|
|
|
$rows = mapi_table_queryallrows($reciptable, [PR_RECIPIENT_TYPE, PR_DISPLAY_NAME, PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS, PR_ENTRYID, PR_SEARCH_KEY]); |
|
916
|
|
|
|
|
917
|
|
|
foreach ($rows as $row) { |
|
918
|
|
|
$address = ""; |
|
919
|
|
|
$fulladdr = ""; |
|
920
|
|
|
|
|
921
|
|
|
$addrtype = isset($row[PR_ADDRTYPE]) ? $row[PR_ADDRTYPE] : ""; |
|
922
|
|
|
|
|
923
|
|
|
if (isset($row[PR_SMTP_ADDRESS])) { |
|
924
|
|
|
$address = $row[PR_SMTP_ADDRESS]; |
|
925
|
|
|
} |
|
926
|
|
|
elseif ($addrtype == "SMTP" && isset($row[PR_EMAIL_ADDRESS])) { |
|
927
|
|
|
$address = $row[PR_EMAIL_ADDRESS]; |
|
928
|
|
|
} |
|
929
|
|
|
elseif ($addrtype == "ZARAFA" && isset($row[PR_ENTRYID])) { |
|
930
|
|
|
$address = $this->getSMTPAddressFromEntryID($row[PR_ENTRYID]); |
|
931
|
|
|
} |
|
932
|
|
|
|
|
933
|
|
|
// if the user was not found, do a fallback to PR_SEARCH_KEY |
|
934
|
|
|
// @see https://jira.z-hub.io/browse/ZP-1178 |
|
935
|
|
|
if (empty($address) && isset($row[PR_SEARCH_KEY])) { |
|
936
|
|
|
$address = $this->getEmailAddressFromSearchKey($row[PR_SEARCH_KEY]); |
|
937
|
|
|
} |
|
938
|
|
|
|
|
939
|
|
|
$name = isset($row[PR_DISPLAY_NAME]) ? $row[PR_DISPLAY_NAME] : ""; |
|
940
|
|
|
|
|
941
|
|
|
if ($name == "" || $name == $address) { |
|
942
|
|
|
$fulladdr = w2u($address); |
|
943
|
|
|
} |
|
944
|
|
|
else { |
|
945
|
|
|
if (substr($name, 0, 1) != '"' && substr($name, -1) != '"') { |
|
946
|
|
|
$fulladdr = "\"" . w2u($name) . "\" <" . w2u($address) . ">"; |
|
|
|
|
|
|
947
|
|
|
} |
|
948
|
|
|
else { |
|
949
|
|
|
$fulladdr = w2u($name) . "<" . w2u($address) . ">"; |
|
950
|
|
|
} |
|
951
|
|
|
} |
|
952
|
|
|
|
|
953
|
|
|
if ($row[PR_RECIPIENT_TYPE] == MAPI_TO) { |
|
954
|
|
|
array_push($message->to, $fulladdr); |
|
955
|
|
|
} |
|
956
|
|
|
elseif ($row[PR_RECIPIENT_TYPE] == MAPI_CC) { |
|
957
|
|
|
array_push($message->cc, $fulladdr); |
|
958
|
|
|
} |
|
959
|
|
|
} |
|
960
|
|
|
|
|
961
|
|
|
if (is_array($message->to) && !empty($message->to)) { |
|
962
|
|
|
$message->to = implode(", ", $message->to); |
|
963
|
|
|
} |
|
964
|
|
|
if (is_array($message->cc) && !empty($message->cc)) { |
|
965
|
|
|
$message->cc = implode(", ", $message->cc); |
|
966
|
|
|
} |
|
967
|
|
|
|
|
968
|
|
|
// without importance some mobiles assume "0" (low) - Mantis #439 |
|
969
|
|
|
if (!isset($message->importance)) { |
|
970
|
|
|
$message->importance = IMPORTANCE_NORMAL; |
|
971
|
|
|
} |
|
972
|
|
|
|
|
973
|
|
|
if (!isset($message->internetcpid)) { |
|
974
|
|
|
$message->internetcpid = (defined('STORE_INTERNET_CPID')) ? constant('STORE_INTERNET_CPID') : INTERNET_CPID_WINDOWS1252; |
|
975
|
|
|
} |
|
976
|
|
|
$this->setFlag($mapimessage, $message); |
|
977
|
|
|
// TODO checkcontentclass |
|
978
|
|
|
if (!isset($message->contentclass)) { |
|
979
|
|
|
$message->contentclass = DEFAULT_EMAIL_CONTENTCLASS; |
|
980
|
|
|
} |
|
981
|
|
|
|
|
982
|
|
|
if (!isset($message->nativebodytype)) { |
|
983
|
|
|
$message->nativebodytype = MAPIUtils::GetNativeBodyType($messageprops); |
|
984
|
|
|
} |
|
985
|
|
|
elseif ($message->nativebodytype == SYNC_BODYPREFERENCE_UNDEFINED) { |
|
986
|
|
|
$nbt = MAPIUtils::GetNativeBodyType($messageprops); |
|
987
|
|
|
SLog::Write(LOGLEVEL_INFO, sprintf("MAPIProvider->getEmail(): native body type is undefined. Set it to %d.", $nbt)); |
|
988
|
|
|
$message->nativebodytype = $nbt; |
|
989
|
|
|
} |
|
990
|
|
|
|
|
991
|
|
|
// reply, reply to all, forward flags |
|
992
|
|
|
if (isset($message->lastverbexecuted) && $message->lastverbexecuted) { |
|
993
|
|
|
$message->lastverbexecuted = Utils::GetLastVerbExecuted($message->lastverbexecuted); |
|
994
|
|
|
} |
|
995
|
|
|
|
|
996
|
|
|
return $message; |
|
997
|
|
|
} |
|
998
|
|
|
|
|
999
|
|
|
/** |
|
1000
|
|
|
* Reads a note object from MAPI. |
|
1001
|
|
|
* |
|
1002
|
|
|
* @param mixed $mapimessage |
|
1003
|
|
|
* @param ContentParameters $contentparameters |
|
1004
|
|
|
* |
|
1005
|
|
|
* @return SyncNote |
|
1006
|
|
|
*/ |
|
1007
|
|
|
private function getNote($mapimessage, $contentparameters) { |
|
1008
|
|
|
$message = new SyncNote(); |
|
1009
|
|
|
|
|
1010
|
|
|
// Standard one-to-one mappings first |
|
1011
|
|
|
$this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetNoteMapping()); |
|
1012
|
|
|
|
|
1013
|
|
|
// set the body according to contentparameters and supported AS version |
|
1014
|
|
|
$this->setMessageBody($mapimessage, $contentparameters, $message); |
|
1015
|
|
|
|
|
1016
|
|
|
return $message; |
|
1017
|
|
|
} |
|
1018
|
|
|
|
|
1019
|
|
|
/** |
|
1020
|
|
|
* Creates a SyncFolder from MAPI properties. |
|
1021
|
|
|
* |
|
1022
|
|
|
* @param mixed $folderprops |
|
1023
|
|
|
* |
|
1024
|
|
|
* @return SyncFolder |
|
1025
|
|
|
*/ |
|
1026
|
|
|
public function GetFolder($folderprops) { |
|
1027
|
|
|
$folder = new SyncFolder(); |
|
1028
|
|
|
|
|
1029
|
|
|
$storeprops = $this->GetStoreProps(); |
|
1030
|
|
|
|
|
1031
|
|
|
// For ZCP 7.0.x we need to retrieve more properties explicitly, see ZP-780 |
|
1032
|
|
|
if (isset($folderprops[PR_SOURCE_KEY]) && !isset($folderprops[PR_ENTRYID]) && !isset($folderprops[PR_CONTAINER_CLASS])) { |
|
1033
|
|
|
$entryid = mapi_msgstore_entryidfromsourcekey($this->store, $folderprops[PR_SOURCE_KEY]); |
|
|
|
|
|
|
1034
|
|
|
$mapifolder = mapi_msgstore_openentry($this->store, $entryid); |
|
|
|
|
|
|
1035
|
|
|
$folderprops = mapi_getprops($mapifolder, [PR_DISPLAY_NAME, PR_PARENT_ENTRYID, PR_ENTRYID, PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY, PR_CONTAINER_CLASS, PR_ATTR_HIDDEN, PR_EXTENDED_FOLDER_FLAGS]); |
|
|
|
|
|
|
1036
|
|
|
SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->GetFolder(): received insufficient of data from ICS. Fetching required data."); |
|
1037
|
|
|
} |
|
1038
|
|
|
|
|
1039
|
|
|
if (!isset( |
|
1040
|
|
|
$folderprops[PR_DISPLAY_NAME], |
|
1041
|
|
|
$folderprops[PR_PARENT_ENTRYID], |
|
1042
|
|
|
$folderprops[PR_SOURCE_KEY], |
|
1043
|
|
|
$folderprops[PR_ENTRYID], |
|
1044
|
|
|
$folderprops[PR_PARENT_SOURCE_KEY], |
|
1045
|
|
|
$storeprops[PR_IPM_SUBTREE_ENTRYID])) { |
|
1046
|
|
|
SLog::Write(LOGLEVEL_ERROR, "MAPIProvider->GetFolder(): invalid folder. Missing properties"); |
|
1047
|
|
|
|
|
1048
|
|
|
return false; |
|
|
|
|
|
|
1049
|
|
|
} |
|
1050
|
|
|
|
|
1051
|
|
|
// ignore hidden folders |
|
1052
|
|
|
if (isset($folderprops[PR_ATTR_HIDDEN]) && $folderprops[PR_ATTR_HIDDEN] != false) { |
|
1053
|
|
|
SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->GetFolder(): invalid folder '%s' as it is a hidden folder (PR_ATTR_HIDDEN)", $folderprops[PR_DISPLAY_NAME])); |
|
1054
|
|
|
|
|
1055
|
|
|
return false; |
|
|
|
|
|
|
1056
|
|
|
} |
|
1057
|
|
|
|
|
1058
|
|
|
// ignore certain undesired folders, like "RSS Feeds" and "Suggested contacts" |
|
1059
|
|
|
if ((isset($folderprops[PR_CONTAINER_CLASS]) && $folderprops[PR_CONTAINER_CLASS] == "IPF.Note.OutlookHomepage") || |
|
1060
|
|
|
in_array($folderprops[PR_ENTRYID], $this->getSpecialFoldersData())) { |
|
1061
|
|
|
SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->GetFolder(): folder '%s' should not be synchronized", $folderprops[PR_DISPLAY_NAME])); |
|
1062
|
|
|
|
|
1063
|
|
|
return false; |
|
|
|
|
|
|
1064
|
|
|
} |
|
1065
|
|
|
|
|
1066
|
|
|
$folder->BackendId = bin2hex($folderprops[PR_SOURCE_KEY]); |
|
1067
|
|
|
$folderOrigin = DeviceManager::FLD_ORIGIN_USER; |
|
1068
|
|
|
if (GSync::GetBackend()->GetImpersonatedUser()) { |
|
1069
|
|
|
$folderOrigin = DeviceManager::FLD_ORIGIN_IMPERSONATED; |
|
1070
|
|
|
} |
|
1071
|
|
|
$folder->serverid = GSync::GetDeviceManager()->GetFolderIdForBackendId($folder->BackendId, true, $folderOrigin, $folderprops[PR_DISPLAY_NAME]); |
|
1072
|
|
|
if ($folderprops[PR_PARENT_ENTRYID] == $storeprops[PR_IPM_SUBTREE_ENTRYID] || $folderprops[PR_PARENT_ENTRYID] == $storeprops[PR_IPM_PUBLIC_FOLDERS_ENTRYID]) { |
|
1073
|
|
|
$folder->parentid = "0"; |
|
1074
|
|
|
} |
|
1075
|
|
|
else { |
|
1076
|
|
|
$folder->parentid = GSync::GetDeviceManager()->GetFolderIdForBackendId(bin2hex($folderprops[PR_PARENT_SOURCE_KEY])); |
|
1077
|
|
|
} |
|
1078
|
|
|
$folder->displayname = w2u($folderprops[PR_DISPLAY_NAME]); |
|
1079
|
|
|
$folder->type = $this->GetFolderType($folderprops[PR_ENTRYID], isset($folderprops[PR_CONTAINER_CLASS]) ? $folderprops[PR_CONTAINER_CLASS] : false); |
|
|
|
|
|
|
1080
|
|
|
|
|
1081
|
|
|
return $folder; |
|
1082
|
|
|
} |
|
1083
|
|
|
|
|
1084
|
|
|
/** |
|
1085
|
|
|
* Returns the foldertype for an entryid |
|
1086
|
|
|
* Gets the folder type by checking the default folders in MAPI. |
|
1087
|
|
|
* |
|
1088
|
|
|
* @param string $entryid |
|
1089
|
|
|
* @param string $class (opt) |
|
1090
|
|
|
* |
|
1091
|
|
|
* @return long |
|
|
|
|
|
|
1092
|
|
|
*/ |
|
1093
|
|
|
public function GetFolderType($entryid, $class = false) { |
|
1094
|
|
|
$storeprops = $this->GetStoreProps(); |
|
1095
|
|
|
$inboxprops = $this->GetInboxProps(); |
|
1096
|
|
|
|
|
1097
|
|
|
if ($entryid == $storeprops[PR_IPM_WASTEBASKET_ENTRYID]) { |
|
1098
|
|
|
return SYNC_FOLDER_TYPE_WASTEBASKET; |
|
|
|
|
|
|
1099
|
|
|
} |
|
1100
|
|
|
if ($entryid == $storeprops[PR_IPM_SENTMAIL_ENTRYID]) { |
|
1101
|
|
|
return SYNC_FOLDER_TYPE_SENTMAIL; |
|
|
|
|
|
|
1102
|
|
|
} |
|
1103
|
|
|
if ($entryid == $storeprops[PR_IPM_OUTBOX_ENTRYID]) { |
|
1104
|
|
|
return SYNC_FOLDER_TYPE_OUTBOX; |
|
|
|
|
|
|
1105
|
|
|
} |
|
1106
|
|
|
|
|
1107
|
|
|
// Public folders do not have inboxprops |
|
1108
|
|
|
// @see https://jira.z-hub.io/browse/ZP-995 |
|
1109
|
|
|
if (!empty($inboxprops)) { |
|
1110
|
|
|
if ($entryid == $inboxprops[PR_ENTRYID]) { |
|
1111
|
|
|
return SYNC_FOLDER_TYPE_INBOX; |
|
|
|
|
|
|
1112
|
|
|
} |
|
1113
|
|
|
if ($entryid == $inboxprops[PR_IPM_DRAFTS_ENTRYID]) { |
|
1114
|
|
|
return SYNC_FOLDER_TYPE_DRAFTS; |
|
|
|
|
|
|
1115
|
|
|
} |
|
1116
|
|
|
if ($entryid == $inboxprops[PR_IPM_TASK_ENTRYID]) { |
|
1117
|
|
|
return SYNC_FOLDER_TYPE_TASK; |
|
|
|
|
|
|
1118
|
|
|
} |
|
1119
|
|
|
if ($entryid == $inboxprops[PR_IPM_APPOINTMENT_ENTRYID]) { |
|
1120
|
|
|
return SYNC_FOLDER_TYPE_APPOINTMENT; |
|
|
|
|
|
|
1121
|
|
|
} |
|
1122
|
|
|
if ($entryid == $inboxprops[PR_IPM_CONTACT_ENTRYID]) { |
|
1123
|
|
|
return SYNC_FOLDER_TYPE_CONTACT; |
|
|
|
|
|
|
1124
|
|
|
} |
|
1125
|
|
|
if ($entryid == $inboxprops[PR_IPM_NOTE_ENTRYID]) { |
|
1126
|
|
|
return SYNC_FOLDER_TYPE_NOTE; |
|
|
|
|
|
|
1127
|
|
|
} |
|
1128
|
|
|
if ($entryid == $inboxprops[PR_IPM_JOURNAL_ENTRYID]) { |
|
1129
|
|
|
return SYNC_FOLDER_TYPE_JOURNAL; |
|
|
|
|
|
|
1130
|
|
|
} |
|
1131
|
|
|
} |
|
1132
|
|
|
|
|
1133
|
|
|
// user created folders |
|
1134
|
|
|
if ($class == "IPF.Note") { |
|
1135
|
|
|
return SYNC_FOLDER_TYPE_USER_MAIL; |
|
|
|
|
|
|
1136
|
|
|
} |
|
1137
|
|
|
if ($class == "IPF.Task") { |
|
1138
|
|
|
return SYNC_FOLDER_TYPE_USER_TASK; |
|
|
|
|
|
|
1139
|
|
|
} |
|
1140
|
|
|
if ($class == "IPF.Appointment") { |
|
1141
|
|
|
return SYNC_FOLDER_TYPE_USER_APPOINTMENT; |
|
|
|
|
|
|
1142
|
|
|
} |
|
1143
|
|
|
if ($class == "IPF.Contact") { |
|
1144
|
|
|
return SYNC_FOLDER_TYPE_USER_CONTACT; |
|
|
|
|
|
|
1145
|
|
|
} |
|
1146
|
|
|
if ($class == "IPF.StickyNote") { |
|
1147
|
|
|
return SYNC_FOLDER_TYPE_USER_NOTE; |
|
|
|
|
|
|
1148
|
|
|
} |
|
1149
|
|
|
if ($class == "IPF.Journal") { |
|
1150
|
|
|
return SYNC_FOLDER_TYPE_USER_JOURNAL; |
|
|
|
|
|
|
1151
|
|
|
} |
|
1152
|
|
|
|
|
1153
|
|
|
return SYNC_FOLDER_TYPE_OTHER; |
|
|
|
|
|
|
1154
|
|
|
} |
|
1155
|
|
|
|
|
1156
|
|
|
/** |
|
1157
|
|
|
* Indicates if the entry id is a default MAPI folder. |
|
1158
|
|
|
* |
|
1159
|
|
|
* @param string $entryid |
|
1160
|
|
|
* |
|
1161
|
|
|
* @return bool |
|
1162
|
|
|
*/ |
|
1163
|
|
|
public function IsMAPIDefaultFolder($entryid) { |
|
1164
|
|
|
$msgstore_props = mapi_getprops($this->store, [PR_ENTRYID, PR_DISPLAY_NAME, PR_IPM_SUBTREE_ENTRYID, PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID, PR_MDB_PROVIDER, PR_IPM_PUBLIC_FOLDERS_ENTRYID, PR_IPM_FAVORITES_ENTRYID, PR_MAILBOX_OWNER_ENTRYID]); |
|
|
|
|
|
|
1165
|
|
|
|
|
1166
|
|
|
$inboxProps = []; |
|
1167
|
|
|
$inbox = mapi_msgstore_getreceivefolder($this->store); |
|
|
|
|
|
|
1168
|
|
|
if (!mapi_last_hresult()) { |
|
|
|
|
|
|
1169
|
|
|
$inboxProps = mapi_getprops($inbox, [PR_ENTRYID]); |
|
1170
|
|
|
} |
|
1171
|
|
|
|
|
1172
|
|
|
$root = mapi_msgstore_openentry($this->store, null); // TODO use getRootProps() |
|
|
|
|
|
|
1173
|
|
|
$rootProps = mapi_getprops($root, [PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_JOURNAL_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_TASK_ENTRYID, PR_ADDITIONAL_REN_ENTRYIDS]); |
|
1174
|
|
|
|
|
1175
|
|
|
$additional_ren_entryids = []; |
|
1176
|
|
|
if (isset($rootProps[PR_ADDITIONAL_REN_ENTRYIDS])) { |
|
1177
|
|
|
$additional_ren_entryids = $rootProps[PR_ADDITIONAL_REN_ENTRYIDS]; |
|
1178
|
|
|
} |
|
1179
|
|
|
|
|
1180
|
|
|
$defaultfolders = [ |
|
1181
|
|
|
"inbox" => ["inbox" => PR_ENTRYID], |
|
1182
|
|
|
"outbox" => ["store" => PR_IPM_OUTBOX_ENTRYID], |
|
1183
|
|
|
"sent" => ["store" => PR_IPM_SENTMAIL_ENTRYID], |
|
1184
|
|
|
"wastebasket" => ["store" => PR_IPM_WASTEBASKET_ENTRYID], |
|
1185
|
|
|
"favorites" => ["store" => PR_IPM_FAVORITES_ENTRYID], |
|
1186
|
|
|
"publicfolders" => ["store" => PR_IPM_PUBLIC_FOLDERS_ENTRYID], |
|
1187
|
|
|
"calendar" => ["root" => PR_IPM_APPOINTMENT_ENTRYID], |
|
1188
|
|
|
"contact" => ["root" => PR_IPM_CONTACT_ENTRYID], |
|
1189
|
|
|
"drafts" => ["root" => PR_IPM_DRAFTS_ENTRYID], |
|
1190
|
|
|
"journal" => ["root" => PR_IPM_JOURNAL_ENTRYID], |
|
1191
|
|
|
"note" => ["root" => PR_IPM_NOTE_ENTRYID], |
|
1192
|
|
|
"task" => ["root" => PR_IPM_TASK_ENTRYID], |
|
1193
|
|
|
"junk" => ["additional" => 4], |
|
1194
|
|
|
"syncissues" => ["additional" => 1], |
|
1195
|
|
|
"conflicts" => ["additional" => 0], |
|
1196
|
|
|
"localfailures" => ["additional" => 2], |
|
1197
|
|
|
"serverfailures" => ["additional" => 3], |
|
1198
|
|
|
]; |
|
1199
|
|
|
|
|
1200
|
|
|
foreach ($defaultfolders as $key => $prop) { |
|
1201
|
|
|
$tag = reset($prop); |
|
1202
|
|
|
$from = key($prop); |
|
1203
|
|
|
|
|
1204
|
|
|
switch ($from) { |
|
1205
|
|
|
case "inbox": |
|
1206
|
|
|
if (isset($inboxProps[$tag]) && $entryid == $inboxProps[$tag]) { |
|
1207
|
|
|
SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Inbox found, key '%s'", $key)); |
|
1208
|
|
|
|
|
1209
|
|
|
return true; |
|
1210
|
|
|
} |
|
1211
|
|
|
break; |
|
1212
|
|
|
|
|
1213
|
|
|
case "store": |
|
1214
|
|
|
if (isset($msgstore_props[$tag]) && $entryid == $msgstore_props[$tag]) { |
|
1215
|
|
|
SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Store folder found, key '%s'", $key)); |
|
1216
|
|
|
|
|
1217
|
|
|
return true; |
|
1218
|
|
|
} |
|
1219
|
|
|
break; |
|
1220
|
|
|
|
|
1221
|
|
|
case "root": |
|
1222
|
|
|
if (isset($rootProps[$tag]) && $entryid == $rootProps[$tag]) { |
|
1223
|
|
|
SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Root folder found, key '%s'", $key)); |
|
1224
|
|
|
|
|
1225
|
|
|
return true; |
|
1226
|
|
|
} |
|
1227
|
|
|
break; |
|
1228
|
|
|
|
|
1229
|
|
|
case "additional": |
|
1230
|
|
|
if (isset($additional_ren_entryids[$tag]) && $entryid == $additional_ren_entryids[$tag]) { |
|
1231
|
|
|
SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Additional folder found, key '%s'", $key)); |
|
1232
|
|
|
|
|
1233
|
|
|
return true; |
|
1234
|
|
|
} |
|
1235
|
|
|
break; |
|
1236
|
|
|
} |
|
1237
|
|
|
} |
|
1238
|
|
|
|
|
1239
|
|
|
return false; |
|
1240
|
|
|
} |
|
1241
|
|
|
|
|
1242
|
|
|
/*---------------------------------------------------------------------------------------------------------- |
|
1243
|
|
|
* SETTER |
|
1244
|
|
|
*/ |
|
1245
|
|
|
|
|
1246
|
|
|
/** |
|
1247
|
|
|
* Writes a SyncObject to MAPI |
|
1248
|
|
|
* Depending on the message class, a contact, appointment, task or email is written. |
|
1249
|
|
|
* |
|
1250
|
|
|
* @param mixed $mapimessage |
|
1251
|
|
|
* @param SyncObject $message |
|
1252
|
|
|
* |
|
1253
|
|
|
* @return bool |
|
1254
|
|
|
*/ |
|
1255
|
|
|
public function SetMessage($mapimessage, $message) { |
|
1256
|
|
|
// TODO check with instanceof |
|
1257
|
|
|
switch (strtolower(get_class($message))) { |
|
1258
|
|
|
case "synccontact": |
|
1259
|
|
|
return $this->setContact($mapimessage, $message); |
|
1260
|
|
|
|
|
1261
|
|
|
case "syncappointment": |
|
1262
|
|
|
return $this->setAppointment($mapimessage, $message); |
|
1263
|
|
|
|
|
1264
|
|
|
case "synctask": |
|
1265
|
|
|
return $this->setTask($mapimessage, $message); |
|
1266
|
|
|
|
|
1267
|
|
|
case "syncnote": |
|
1268
|
|
|
return $this->setNote($mapimessage, $message); |
|
1269
|
|
|
|
|
1270
|
|
|
default: |
|
1271
|
|
|
// for emails only flag (read and todo) changes are possible |
|
1272
|
|
|
return $this->setEmail($mapimessage, $message); |
|
|
|
|
|
|
1273
|
|
|
} |
|
1274
|
|
|
} |
|
1275
|
|
|
|
|
1276
|
|
|
/** |
|
1277
|
|
|
* Writes SyncMail to MAPI (actually flags only). |
|
1278
|
|
|
* |
|
1279
|
|
|
* @param mixed $mapimessage |
|
1280
|
|
|
* @param SyncMail $message |
|
1281
|
|
|
*/ |
|
1282
|
|
|
private function setEmail($mapimessage, $message) { |
|
1283
|
|
|
// update categories |
|
1284
|
|
|
if (!isset($message->categories)) { |
|
1285
|
|
|
$message->categories = []; |
|
1286
|
|
|
} |
|
1287
|
|
|
$emailmap = MAPIMapping::GetEmailMapping(); |
|
1288
|
|
|
$this->setPropsInMAPI($mapimessage, $message, ["categories" => $emailmap["categories"]]); |
|
1289
|
|
|
|
|
1290
|
|
|
$flagmapping = MAPIMapping::GetMailFlagsMapping(); |
|
1291
|
|
|
$flagprops = MAPIMapping::GetMailFlagsProperties(); |
|
1292
|
|
|
$flagprops = array_merge($this->getPropIdsFromStrings($flagmapping), $this->getPropIdsFromStrings($flagprops)); |
|
1293
|
|
|
// flag specific properties to be set |
|
1294
|
|
|
$props = $delprops = []; |
|
1295
|
|
|
// unset message flags if: |
|
1296
|
|
|
// flag is not set |
|
1297
|
|
|
if (empty($message->flag) || |
|
1298
|
|
|
// flag status is not set |
|
1299
|
|
|
!isset($message->flag->flagstatus) || |
|
1300
|
|
|
// flag status is 0 or empty |
|
1301
|
|
|
(isset($message->flag->flagstatus) && ($message->flag->flagstatus == 0 || $message->flag->flagstatus == ""))) { |
|
1302
|
|
|
// if message flag is empty, some properties need to be deleted |
|
1303
|
|
|
// and some set to 0 or false |
|
1304
|
|
|
|
|
1305
|
|
|
$props[$flagprops["todoitemsflags"]] = 0; |
|
1306
|
|
|
$props[$flagprops["status"]] = 0; |
|
1307
|
|
|
$props[$flagprops["completion"]] = 0.0; |
|
1308
|
|
|
$props[$flagprops["flagtype"]] = ""; |
|
1309
|
|
|
$props[$flagprops["ordinaldate"]] = 0x7FFFFFFF; // ordinal date is 12am 1.1.4501, set it to max possible value |
|
1310
|
|
|
$props[$flagprops["subordinaldate"]] = ""; |
|
1311
|
|
|
$props[$flagprops["replyrequested"]] = false; |
|
1312
|
|
|
$props[$flagprops["responserequested"]] = false; |
|
1313
|
|
|
$props[$flagprops["reminderset"]] = false; |
|
1314
|
|
|
$props[$flagprops["complete"]] = false; |
|
1315
|
|
|
|
|
1316
|
|
|
$delprops[] = $flagprops["todotitle"]; |
|
1317
|
|
|
$delprops[] = $flagprops["duedate"]; |
|
1318
|
|
|
$delprops[] = $flagprops["startdate"]; |
|
1319
|
|
|
$delprops[] = $flagprops["datecompleted"]; |
|
1320
|
|
|
$delprops[] = $flagprops["utcstartdate"]; |
|
1321
|
|
|
$delprops[] = $flagprops["utcduedate"]; |
|
1322
|
|
|
$delprops[] = $flagprops["completetime"]; |
|
1323
|
|
|
$delprops[] = $flagprops["flagstatus"]; |
|
1324
|
|
|
$delprops[] = $flagprops["flagicon"]; |
|
1325
|
|
|
} |
|
1326
|
|
|
else { |
|
1327
|
|
|
$this->setPropsInMAPI($mapimessage, $message->flag, $flagmapping); |
|
1328
|
|
|
$props[$flagprops["todoitemsflags"]] = 1; |
|
1329
|
|
|
if (isset($message->subject) && strlen($message->subject) > 0) { |
|
1330
|
|
|
$props[$flagprops["todotitle"]] = $message->subject; |
|
1331
|
|
|
} |
|
1332
|
|
|
// ordinal date is utc current time |
|
1333
|
|
|
if (!isset($message->flag->ordinaldate) || empty($message->flag->ordinaldate)) { |
|
1334
|
|
|
$props[$flagprops["ordinaldate"]] = time(); |
|
1335
|
|
|
} |
|
1336
|
|
|
// the default value |
|
1337
|
|
|
if (!isset($message->flag->subordinaldate) || empty($message->flag->subordinaldate)) { |
|
1338
|
|
|
$props[$flagprops["subordinaldate"]] = "5555555"; |
|
1339
|
|
|
} |
|
1340
|
|
|
$props[$flagprops["flagicon"]] = 6; // red flag icon |
|
1341
|
|
|
$props[$flagprops["replyrequested"]] = true; |
|
1342
|
|
|
$props[$flagprops["responserequested"]] = true; |
|
1343
|
|
|
|
|
1344
|
|
|
if ($message->flag->flagstatus == SYNC_FLAGSTATUS_COMPLETE) { |
|
1345
|
|
|
$props[$flagprops["status"]] = olTaskComplete; |
|
1346
|
|
|
$props[$flagprops["completion"]] = 1.0; |
|
1347
|
|
|
$props[$flagprops["complete"]] = true; |
|
1348
|
|
|
$props[$flagprops["replyrequested"]] = false; |
|
1349
|
|
|
$props[$flagprops["responserequested"]] = false; |
|
1350
|
|
|
unset($props[$flagprops["flagicon"]]); |
|
1351
|
|
|
$delprops[] = $flagprops["flagicon"]; |
|
1352
|
|
|
} |
|
1353
|
|
|
} |
|
1354
|
|
|
|
|
1355
|
|
|
if (!empty($props)) { |
|
1356
|
|
|
mapi_setprops($mapimessage, $props); |
|
|
|
|
|
|
1357
|
|
|
} |
|
1358
|
|
|
if (!empty($delprops)) { |
|
1359
|
|
|
mapi_deleteprops($mapimessage, $delprops); |
|
|
|
|
|
|
1360
|
|
|
} |
|
1361
|
|
|
} |
|
1362
|
|
|
|
|
1363
|
|
|
/** |
|
1364
|
|
|
* Writes a SyncAppointment to MAPI. |
|
1365
|
|
|
* |
|
1366
|
|
|
* @param mixed $mapimessage |
|
1367
|
|
|
* @param SyncAppointment $message |
|
1368
|
|
|
* @param mixed $appointment |
|
1369
|
|
|
* |
|
1370
|
|
|
* @return bool |
|
1371
|
|
|
*/ |
|
1372
|
|
|
private function setAppointment($mapimessage, $appointment) { |
|
1373
|
|
|
// Get timezone info |
|
1374
|
|
|
if (isset($appointment->timezone)) { |
|
1375
|
|
|
$tz = $this->getTZFromSyncBlob(base64_decode($appointment->timezone)); |
|
1376
|
|
|
} |
|
1377
|
|
|
else { |
|
1378
|
|
|
$tz = false; |
|
1379
|
|
|
} |
|
1380
|
|
|
|
|
1381
|
|
|
// start and end time may not be set - try to get them from the existing appointment for further calculation - see https://jira.z-hub.io/browse/ZP-983 |
|
1382
|
|
|
if (!isset($appointment->starttime) || !isset($appointment->endtime)) { |
|
1383
|
|
|
$amapping = MAPIMapping::GetAppointmentMapping(); |
|
1384
|
|
|
$amapping = $this->getPropIdsFromStrings($amapping); |
|
1385
|
|
|
$existingstartendpropsmap = [$amapping["starttime"], $amapping["endtime"]]; |
|
1386
|
|
|
$existingstartendprops = $this->getProps($mapimessage, $existingstartendpropsmap); |
|
1387
|
|
|
|
|
1388
|
|
|
if (isset($existingstartendprops[$amapping["starttime"]]) && !isset($appointment->starttime)) { |
|
1389
|
|
|
$appointment->starttime = $existingstartendprops[$amapping["starttime"]]; |
|
1390
|
|
|
SLog::Write(LOGLEVEL_WBXML, sprintf("MAPIProvider->setAppointment(): Parameter 'starttime' was not set, using value from MAPI %d (%s).", $appointment->starttime, gmstrftime("%Y%m%dT%H%M%SZ", $appointment->starttime))); |
|
1391
|
|
|
} |
|
1392
|
|
|
if (isset($existingstartendprops[$amapping["endtime"]]) && !isset($appointment->endtime)) { |
|
1393
|
|
|
$appointment->endtime = $existingstartendprops[$amapping["endtime"]]; |
|
1394
|
|
|
SLog::Write(LOGLEVEL_WBXML, sprintf("MAPIProvider->setAppointment(): Parameter 'endtime' was not set, using value from MAPI %d (%s).", $appointment->endtime, gmstrftime("%Y%m%dT%H%M%SZ", $appointment->endtime))); |
|
1395
|
|
|
} |
|
1396
|
|
|
} |
|
1397
|
|
|
if (!isset($appointment->starttime) || !isset($appointment->endtime)) { |
|
1398
|
|
|
throw new StatusException("MAPIProvider->setAppointment(): Error, start and/or end time not set and can not be retrieved from MAPI.", SYNC_STATUS_SYNCCANNOTBECOMPLETED); |
|
1399
|
|
|
} |
|
1400
|
|
|
|
|
1401
|
|
|
// calculate duration because without it some webaccess views are broken. duration is in min |
|
1402
|
|
|
$localstart = $this->getLocaltimeByTZ($appointment->starttime, $tz); |
|
|
|
|
|
|
1403
|
|
|
$localend = $this->getLocaltimeByTZ($appointment->endtime, $tz); |
|
1404
|
|
|
$duration = ($localend - $localstart) / 60; |
|
1405
|
|
|
|
|
1406
|
|
|
// nokia sends an yearly event with 0 mins duration but as all day event, |
|
1407
|
|
|
// so make it end next day |
|
1408
|
|
|
if ($appointment->starttime == $appointment->endtime && isset($appointment->alldayevent) && $appointment->alldayevent) { |
|
1409
|
|
|
$duration = 1440; |
|
1410
|
|
|
$appointment->endtime = $appointment->starttime + 24 * 60 * 60; |
|
1411
|
|
|
$localend = $localstart + 24 * 60 * 60; |
|
1412
|
|
|
} |
|
1413
|
|
|
|
|
1414
|
|
|
// is the transmitted UID OL compatible? |
|
1415
|
|
|
// if not, encapsulate the transmitted uid |
|
1416
|
|
|
$appointment->uid = Utils::GetOLUidFromICalUid($appointment->uid); |
|
1417
|
|
|
|
|
1418
|
|
|
mapi_setprops($mapimessage, [PR_MESSAGE_CLASS => "IPM.Appointment"]); |
|
|
|
|
|
|
1419
|
|
|
|
|
1420
|
|
|
$appointmentmapping = MAPIMapping::GetAppointmentMapping(); |
|
1421
|
|
|
$this->setPropsInMAPI($mapimessage, $appointment, $appointmentmapping); |
|
1422
|
|
|
$appointmentprops = MAPIMapping::GetAppointmentProperties(); |
|
1423
|
|
|
$appointmentprops = array_merge($this->getPropIdsFromStrings($appointmentmapping), $this->getPropIdsFromStrings($appointmentprops)); |
|
1424
|
|
|
// appointment specific properties to be set |
|
1425
|
|
|
$props = []; |
|
1426
|
|
|
|
|
1427
|
|
|
// sensitivity is not enough to mark an appointment as private, so we use another mapi tag |
|
1428
|
|
|
$private = (isset($appointment->sensitivity) && $appointment->sensitivity >= SENSITIVITY_PRIVATE) ? true : false; |
|
1429
|
|
|
|
|
1430
|
|
|
// Set commonstart/commonend to start/end and remindertime to start, duration, private and cleanGlobalObjectId |
|
1431
|
|
|
$props[$appointmentprops["commonstart"]] = $appointment->starttime; |
|
1432
|
|
|
$props[$appointmentprops["commonend"]] = $appointment->endtime; |
|
1433
|
|
|
$props[$appointmentprops["reminderstart"]] = $appointment->starttime; |
|
1434
|
|
|
// Set reminder boolean to 'true' if reminder is set |
|
1435
|
|
|
$props[$appointmentprops["reminderset"]] = isset($appointment->reminder) ? true : false; |
|
1436
|
|
|
$props[$appointmentprops["duration"]] = $duration; |
|
1437
|
|
|
$props[$appointmentprops["private"]] = $private; |
|
1438
|
|
|
$props[$appointmentprops["uid"]] = $appointment->uid; |
|
1439
|
|
|
// Set named prop 8510, unknown property, but enables deleting a single occurrence of a recurring |
|
1440
|
|
|
// type in OLK2003. |
|
1441
|
|
|
$props[$appointmentprops["sideeffects"]] = 369; |
|
1442
|
|
|
|
|
1443
|
|
|
if (isset($appointment->reminder) && $appointment->reminder >= 0) { |
|
1444
|
|
|
// Set 'flagdueby' to correct value (start - reminderminutes) |
|
1445
|
|
|
$props[$appointmentprops["flagdueby"]] = $appointment->starttime - $appointment->reminder * 60; |
|
1446
|
|
|
$props[$appointmentprops["remindertime"]] = $appointment->reminder; |
|
1447
|
|
|
} |
|
1448
|
|
|
// unset the reminder |
|
1449
|
|
|
else { |
|
1450
|
|
|
$props[$appointmentprops["reminderset"]] = false; |
|
1451
|
|
|
} |
|
1452
|
|
|
|
|
1453
|
|
|
if (isset($appointment->asbody)) { |
|
1454
|
|
|
$this->setASbody($appointment->asbody, $props, $appointmentprops); |
|
1455
|
|
|
} |
|
1456
|
|
|
|
|
1457
|
|
|
if ($tz !== false) { |
|
1458
|
|
|
$props[$appointmentprops["timezonetag"]] = $this->getMAPIBlobFromTZ($tz); |
|
1459
|
|
|
} |
|
1460
|
|
|
|
|
1461
|
|
|
if (isset($appointment->recurrence)) { |
|
1462
|
|
|
// Set PR_ICON_INDEX to 1025 to show correct icon in category view |
|
1463
|
|
|
$props[$appointmentprops["icon"]] = 1025; |
|
1464
|
|
|
|
|
1465
|
|
|
// if there aren't any exceptions, use the 'old style' set recurrence |
|
1466
|
|
|
$noexceptions = true; |
|
1467
|
|
|
|
|
1468
|
|
|
$recurrence = new Recurrence($this->store, $mapimessage); |
|
1469
|
|
|
$recur = []; |
|
1470
|
|
|
$this->setRecurrence($appointment, $recur); |
|
1471
|
|
|
|
|
1472
|
|
|
// set the recurrence type to that of the MAPI |
|
1473
|
|
|
$props[$appointmentprops["recurrencetype"]] = $recur["recurrencetype"]; |
|
1474
|
|
|
|
|
1475
|
|
|
$starttime = $this->gmtime($localstart); |
|
1476
|
|
|
$endtime = $this->gmtime($localend); |
|
|
|
|
|
|
1477
|
|
|
|
|
1478
|
|
|
// set recurrence start here because it's calculated differently for tasks and appointments |
|
1479
|
|
|
$recur["start"] = $this->getDayStartOfTimestamp($this->getGMTTimeByTZ($localstart, $tz)); |
|
|
|
|
|
|
1480
|
|
|
|
|
1481
|
|
|
$recur["startocc"] = $starttime["tm_hour"] * 60 + $starttime["tm_min"]; |
|
1482
|
|
|
$recur["endocc"] = $recur["startocc"] + $duration; // Note that this may be > 24*60 if multi-day |
|
1483
|
|
|
|
|
1484
|
|
|
// only tasks can regenerate |
|
1485
|
|
|
$recur["regen"] = false; |
|
1486
|
|
|
|
|
1487
|
|
|
// Process exceptions. The PDA will send all exceptions for this recurring item. |
|
1488
|
|
|
if (isset($appointment->exceptions)) { |
|
1489
|
|
|
foreach ($appointment->exceptions as $exception) { |
|
1490
|
|
|
// we always need the base date |
|
1491
|
|
|
if (!isset($exception->exceptionstarttime)) { |
|
1492
|
|
|
continue; |
|
1493
|
|
|
} |
|
1494
|
|
|
|
|
1495
|
|
|
$basedate = $this->getDayStartOfTimestamp($exception->exceptionstarttime); |
|
1496
|
|
|
if (isset($exception->deleted) && $exception->deleted) { |
|
1497
|
|
|
$noexceptions = false; |
|
1498
|
|
|
// Delete exception |
|
1499
|
|
|
$recurrence->createException([], $basedate, true); |
|
1500
|
|
|
} |
|
1501
|
|
|
else { |
|
1502
|
|
|
// Change exception |
|
1503
|
|
|
$mapiexception = ["basedate" => $basedate]; |
|
1504
|
|
|
// other exception properties which are not handled in recurrence |
|
1505
|
|
|
$exceptionprops = []; |
|
1506
|
|
|
|
|
1507
|
|
|
if (isset($exception->starttime)) { |
|
1508
|
|
|
$mapiexception["start"] = $this->getLocaltimeByTZ($exception->starttime, $tz); |
|
1509
|
|
|
$exceptionprops[$appointmentprops["starttime"]] = $exception->starttime; |
|
1510
|
|
|
} |
|
1511
|
|
|
if (isset($exception->endtime)) { |
|
1512
|
|
|
$mapiexception["end"] = $this->getLocaltimeByTZ($exception->endtime, $tz); |
|
1513
|
|
|
$exceptionprops[$appointmentprops["endtime"]] = $exception->endtime; |
|
1514
|
|
|
} |
|
1515
|
|
|
if (isset($exception->subject)) { |
|
1516
|
|
|
$exceptionprops[$appointmentprops["subject"]] = $mapiexception["subject"] = u2w($exception->subject); |
|
1517
|
|
|
} |
|
1518
|
|
|
if (isset($exception->location)) { |
|
1519
|
|
|
$exceptionprops[$appointmentprops["location"]] = $mapiexception["location"] = u2w($exception->location); |
|
1520
|
|
|
} |
|
1521
|
|
|
if (isset($exception->busystatus)) { |
|
1522
|
|
|
$exceptionprops[$appointmentprops["busystatus"]] = $mapiexception["busystatus"] = $exception->busystatus; |
|
1523
|
|
|
} |
|
1524
|
|
|
if (isset($exception->reminder)) { |
|
1525
|
|
|
$exceptionprops[$appointmentprops["reminderset"]] = $mapiexception["reminder_set"] = 1; |
|
1526
|
|
|
$exceptionprops[$appointmentprops["remindertime"]] = $mapiexception["remind_before"] = $exception->reminder; |
|
1527
|
|
|
} |
|
1528
|
|
|
if (isset($exception->alldayevent)) { |
|
1529
|
|
|
$exceptionprops[$appointmentprops["alldayevent"]] = $mapiexception["alldayevent"] = $exception->alldayevent; |
|
1530
|
|
|
} |
|
1531
|
|
|
|
|
1532
|
|
|
if (!isset($recur["changed_occurrences"])) { |
|
1533
|
|
|
$recur["changed_occurrences"] = []; |
|
1534
|
|
|
} |
|
1535
|
|
|
|
|
1536
|
|
|
if (isset($exception->body)) { |
|
1537
|
|
|
$exceptionprops[$appointmentprops["body"]] = u2w($exception->body); |
|
1538
|
|
|
} |
|
1539
|
|
|
|
|
1540
|
|
|
if (isset($exception->asbody)) { |
|
1541
|
|
|
$this->setASbody($exception->asbody, $exceptionprops, $appointmentprops); |
|
1542
|
|
|
$mapiexception["body"] = $exceptionprops[$appointmentprops["body"]] = |
|
1543
|
|
|
(isset($exceptionprops[$appointmentprops["body"]])) ? $exceptionprops[$appointmentprops["body"]] : |
|
1544
|
|
|
((isset($exceptionprops[$appointmentprops["html"]])) ? $exceptionprops[$appointmentprops["html"]] : ""); |
|
1545
|
|
|
} |
|
1546
|
|
|
|
|
1547
|
|
|
array_push($recur["changed_occurrences"], $mapiexception); |
|
1548
|
|
|
|
|
1549
|
|
|
if (!empty($exceptionprops)) { |
|
1550
|
|
|
$noexceptions = false; |
|
1551
|
|
|
if ($recurrence->isException($basedate)) { |
|
1552
|
|
|
$recurrence->modifyException($exceptionprops, $basedate); |
|
1553
|
|
|
} |
|
1554
|
|
|
else { |
|
1555
|
|
|
$recurrence->createException($exceptionprops, $basedate); |
|
1556
|
|
|
} |
|
1557
|
|
|
} |
|
1558
|
|
|
} |
|
1559
|
|
|
} |
|
1560
|
|
|
} |
|
1561
|
|
|
|
|
1562
|
|
|
// setRecurrence deletes the attachments from an appointment |
|
1563
|
|
|
if ($noexceptions) { |
|
1564
|
|
|
$recurrence->setRecurrence($tz, $recur); |
|
1565
|
|
|
} |
|
1566
|
|
|
} |
|
1567
|
|
|
else { |
|
1568
|
|
|
$props[$appointmentprops["isrecurring"]] = false; |
|
1569
|
|
|
} |
|
1570
|
|
|
|
|
1571
|
|
|
// always set the PR_SENT_REPRESENTING_* props so that the attendee status update also works with the webaccess |
|
1572
|
|
|
$p = [$appointmentprops["representingentryid"], $appointmentprops["representingname"], $appointmentprops["sentrepresentingaddt"], |
|
1573
|
|
|
$appointmentprops["sentrepresentingemail"], $appointmentprops["sentrepresentinsrchk"], $appointmentprops["responsestatus"], ]; |
|
1574
|
|
|
$representingprops = $this->getProps($mapimessage, $p); |
|
1575
|
|
|
|
|
1576
|
|
|
if (!isset($representingprops[$appointmentprops["representingentryid"]])) { |
|
1577
|
|
|
// TODO use GetStoreProps |
|
1578
|
|
|
$storeProps = mapi_getprops($this->store, [PR_MAILBOX_OWNER_ENTRYID]); |
|
|
|
|
|
|
1579
|
|
|
$props[$appointmentprops["representingentryid"]] = $storeProps[PR_MAILBOX_OWNER_ENTRYID]; |
|
1580
|
|
|
$displayname = $this->getFullnameFromEntryID($storeProps[PR_MAILBOX_OWNER_ENTRYID]); |
|
1581
|
|
|
|
|
1582
|
|
|
$props[$appointmentprops["representingname"]] = ($displayname !== false) ? $displayname : Request::GetUser(); |
|
|
|
|
|
|
1583
|
|
|
$props[$appointmentprops["sentrepresentingemail"]] = Request::GetUser(); |
|
1584
|
|
|
$props[$appointmentprops["sentrepresentingaddt"]] = "ZARAFA"; |
|
1585
|
|
|
$props[$appointmentprops["sentrepresentinsrchk"]] = $props[$appointmentprops["sentrepresentingaddt"]] . ":" . $props[$appointmentprops["sentrepresentingemail"]]; |
|
1586
|
|
|
|
|
1587
|
|
|
if (isset($appointment->attendees) && is_array($appointment->attendees) && !empty($appointment->attendees)) { |
|
1588
|
|
|
$props[$appointmentprops["icon"]] = 1026; |
|
1589
|
|
|
// the user is the organizer |
|
1590
|
|
|
// set these properties to show tracking tab in webapp |
|
1591
|
|
|
|
|
1592
|
|
|
$props[$appointmentprops["mrwassent"]] = true; |
|
1593
|
|
|
$props[$appointmentprops["responsestatus"]] = olResponseOrganized; |
|
1594
|
|
|
$props[$appointmentprops["meetingstatus"]] = olMeeting; |
|
1595
|
|
|
} |
|
1596
|
|
|
} |
|
1597
|
|
|
// we also have to set the responsestatus and not only meetingstatus, so we use another mapi tag |
|
1598
|
|
|
if (!isset($props[$appointmentprops["responsestatus"]])) { |
|
1599
|
|
|
if (isset($appointment->responsetype)) { |
|
1600
|
|
|
$props[$appointmentprops["responsestatus"]] = $appointment->responsetype; |
|
1601
|
|
|
} |
|
1602
|
|
|
// only set responsestatus to none if it is not set on the server |
|
1603
|
|
|
elseif (!isset($representingprops[$appointmentprops["responsestatus"]])) { |
|
1604
|
|
|
$props[$appointmentprops["responsestatus"]] = olResponseNone; |
|
1605
|
|
|
} |
|
1606
|
|
|
} |
|
1607
|
|
|
|
|
1608
|
|
|
// Do attendees |
|
1609
|
|
|
if (isset($appointment->attendees) && is_array($appointment->attendees)) { |
|
1610
|
|
|
$recips = []; |
|
1611
|
|
|
|
|
1612
|
|
|
// Outlook XP requires organizer in the attendee list as well |
|
1613
|
|
|
$org = []; |
|
1614
|
|
|
$org[PR_ENTRYID] = isset($representingprops[$appointmentprops["representingentryid"]]) ? $representingprops[$appointmentprops["representingentryid"]] : $props[$appointmentprops["representingentryid"]]; |
|
1615
|
|
|
$org[PR_DISPLAY_NAME] = isset($representingprops[$appointmentprops["representingname"]]) ? $representingprops[$appointmentprops["representingname"]] : $props[$appointmentprops["representingname"]]; |
|
1616
|
|
|
$org[PR_ADDRTYPE] = isset($representingprops[$appointmentprops["sentrepresentingaddt"]]) ? $representingprops[$appointmentprops["sentrepresentingaddt"]] : $props[$appointmentprops["sentrepresentingaddt"]]; |
|
1617
|
|
|
$org[PR_SMTP_ADDRESS] = $org[PR_EMAIL_ADDRESS] = isset($representingprops[$appointmentprops["sentrepresentingemail"]]) ? $representingprops[$appointmentprops["sentrepresentingemail"]] : $props[$appointmentprops["sentrepresentingemail"]]; |
|
1618
|
|
|
$org[PR_SEARCH_KEY] = isset($representingprops[$appointmentprops["sentrepresentinsrchk"]]) ? $representingprops[$appointmentprops["sentrepresentinsrchk"]] : $props[$appointmentprops["sentrepresentinsrchk"]]; |
|
1619
|
|
|
$org[PR_RECIPIENT_FLAGS] = recipOrganizer | recipSendable; |
|
1620
|
|
|
$org[PR_RECIPIENT_TYPE] = MAPI_ORIG; |
|
1621
|
|
|
|
|
1622
|
|
|
array_push($recips, $org); |
|
1623
|
|
|
|
|
1624
|
|
|
// Open address book for user resolve |
|
1625
|
|
|
$addrbook = $this->getAddressbook(); |
|
1626
|
|
|
foreach ($appointment->attendees as $attendee) { |
|
1627
|
|
|
$recip = []; |
|
1628
|
|
|
$recip[PR_EMAIL_ADDRESS] = u2w($attendee->email); |
|
1629
|
|
|
$recip[PR_SMTP_ADDRESS] = u2w($attendee->email); |
|
1630
|
|
|
|
|
1631
|
|
|
// lookup information in GAB if possible so we have up-to-date name for given address |
|
1632
|
|
|
$userinfo = [[PR_DISPLAY_NAME => $recip[PR_EMAIL_ADDRESS]]]; |
|
1633
|
|
|
$userinfo = mapi_ab_resolvename($addrbook, $userinfo, EMS_AB_ADDRESS_LOOKUP); |
|
|
|
|
|
|
1634
|
|
|
if (mapi_last_hresult() == NOERROR) { |
|
|
|
|
|
|
1635
|
|
|
$recip[PR_DISPLAY_NAME] = $userinfo[0][PR_DISPLAY_NAME]; |
|
1636
|
|
|
$recip[PR_EMAIL_ADDRESS] = $userinfo[0][PR_EMAIL_ADDRESS]; |
|
1637
|
|
|
$recip[PR_SEARCH_KEY] = $userinfo[0][PR_SEARCH_KEY]; |
|
1638
|
|
|
$recip[PR_ADDRTYPE] = $userinfo[0][PR_ADDRTYPE]; |
|
1639
|
|
|
$recip[PR_ENTRYID] = $userinfo[0][PR_ENTRYID]; |
|
1640
|
|
|
$recip[PR_RECIPIENT_TYPE] = isset($attendee->attendeetype) ? $attendee->attendeetype : MAPI_TO; |
|
1641
|
|
|
$recip[PR_RECIPIENT_FLAGS] = recipSendable; |
|
1642
|
|
|
$recip[PR_RECIPIENT_TRACKSTATUS] = isset($attendee->attendeestatus) ? $attendee->attendeestatus : olResponseNone; |
|
1643
|
|
|
} |
|
1644
|
|
|
else { |
|
1645
|
|
|
$recip[PR_DISPLAY_NAME] = u2w($attendee->name); |
|
1646
|
|
|
$recip[PR_SEARCH_KEY] = "SMTP:" . $recip[PR_EMAIL_ADDRESS] . "\0"; |
|
1647
|
|
|
$recip[PR_ADDRTYPE] = "SMTP"; |
|
1648
|
|
|
$recip[PR_RECIPIENT_TYPE] = isset($attendee->attendeetype) ? $attendee->attendeetype : MAPI_TO; |
|
1649
|
|
|
$recip[PR_ENTRYID] = mapi_createoneoff($recip[PR_DISPLAY_NAME], $recip[PR_ADDRTYPE], $recip[PR_EMAIL_ADDRESS]); |
|
|
|
|
|
|
1650
|
|
|
} |
|
1651
|
|
|
|
|
1652
|
|
|
array_push($recips, $recip); |
|
1653
|
|
|
} |
|
1654
|
|
|
|
|
1655
|
|
|
mapi_message_modifyrecipients($mapimessage, 0, $recips); |
|
|
|
|
|
|
1656
|
|
|
} |
|
1657
|
|
|
mapi_setprops($mapimessage, $props); |
|
1658
|
|
|
|
|
1659
|
|
|
return true; |
|
1660
|
|
|
} |
|
1661
|
|
|
|
|
1662
|
|
|
/** |
|
1663
|
|
|
* Writes a SyncContact to MAPI. |
|
1664
|
|
|
* |
|
1665
|
|
|
* @param mixed $mapimessage |
|
1666
|
|
|
* @param SyncContact $contact |
|
1667
|
|
|
* |
|
1668
|
|
|
* @return bool |
|
1669
|
|
|
*/ |
|
1670
|
|
|
private function setContact($mapimessage, $contact) { |
|
1671
|
|
|
mapi_setprops($mapimessage, [PR_MESSAGE_CLASS => "IPM.Contact"]); |
|
|
|
|
|
|
1672
|
|
|
|
|
1673
|
|
|
// normalize email addresses |
|
1674
|
|
|
if (isset($contact->email1address) && (($contact->email1address = $this->extractEmailAddress($contact->email1address)) === false)) { |
|
|
|
|
|
|
1675
|
|
|
unset($contact->email1address); |
|
1676
|
|
|
} |
|
1677
|
|
|
|
|
1678
|
|
|
if (isset($contact->email2address) && (($contact->email2address = $this->extractEmailAddress($contact->email2address)) === false)) { |
|
|
|
|
|
|
1679
|
|
|
unset($contact->email2address); |
|
1680
|
|
|
} |
|
1681
|
|
|
|
|
1682
|
|
|
if (isset($contact->email3address) && (($contact->email3address = $this->extractEmailAddress($contact->email3address)) === false)) { |
|
|
|
|
|
|
1683
|
|
|
unset($contact->email3address); |
|
1684
|
|
|
} |
|
1685
|
|
|
|
|
1686
|
|
|
$contactmapping = MAPIMapping::GetContactMapping(); |
|
1687
|
|
|
$contactprops = MAPIMapping::GetContactProperties(); |
|
1688
|
|
|
$this->setPropsInMAPI($mapimessage, $contact, $contactmapping); |
|
1689
|
|
|
|
|
1690
|
|
|
// /set display name from contact's properties |
|
1691
|
|
|
$cname = $this->composeDisplayName($contact); |
|
1692
|
|
|
|
|
1693
|
|
|
// get contact specific mapi properties and merge them with the AS properties |
|
1694
|
|
|
$contactprops = array_merge($this->getPropIdsFromStrings($contactmapping), $this->getPropIdsFromStrings($contactprops)); |
|
1695
|
|
|
|
|
1696
|
|
|
// contact specific properties to be set |
|
1697
|
|
|
$props = []; |
|
1698
|
|
|
|
|
1699
|
|
|
// need to be set in order to show contacts properly in outlook and wa |
|
1700
|
|
|
$nremails = []; |
|
1701
|
|
|
$abprovidertype = 0; |
|
1702
|
|
|
|
|
1703
|
|
|
if (isset($contact->email1address)) { |
|
1704
|
|
|
$this->setEmailAddress($contact->email1address, $cname, 1, $props, $contactprops, $nremails, $abprovidertype); |
|
1705
|
|
|
} |
|
1706
|
|
|
if (isset($contact->email2address)) { |
|
1707
|
|
|
$this->setEmailAddress($contact->email2address, $cname, 2, $props, $contactprops, $nremails, $abprovidertype); |
|
1708
|
|
|
} |
|
1709
|
|
|
if (isset($contact->email3address)) { |
|
1710
|
|
|
$this->setEmailAddress($contact->email3address, $cname, 3, $props, $contactprops, $nremails, $abprovidertype); |
|
1711
|
|
|
} |
|
1712
|
|
|
|
|
1713
|
|
|
$props[$contactprops["addressbooklong"]] = $abprovidertype; |
|
1714
|
|
|
$props[$contactprops["displayname"]] = $props[$contactprops["subject"]] = $cname; |
|
1715
|
|
|
|
|
1716
|
|
|
// pda multiple e-mail addresses bug fix for the contact |
|
1717
|
|
|
if (!empty($nremails)) { |
|
1718
|
|
|
$props[$contactprops["addressbookmv"]] = $nremails; |
|
1719
|
|
|
} |
|
1720
|
|
|
|
|
1721
|
|
|
// set addresses |
|
1722
|
|
|
$this->setAddress("home", $contact->homecity, $contact->homecountry, $contact->homepostalcode, $contact->homestate, $contact->homestreet, $props, $contactprops); |
|
1723
|
|
|
$this->setAddress("business", $contact->businesscity, $contact->businesscountry, $contact->businesspostalcode, $contact->businessstate, $contact->businessstreet, $props, $contactprops); |
|
1724
|
|
|
$this->setAddress("other", $contact->othercity, $contact->othercountry, $contact->otherpostalcode, $contact->otherstate, $contact->otherstreet, $props, $contactprops); |
|
1725
|
|
|
|
|
1726
|
|
|
// set the mailing address and its type |
|
1727
|
|
|
if (isset($props[$contactprops["businessaddress"]])) { |
|
1728
|
|
|
$props[$contactprops["mailingaddress"]] = 2; |
|
1729
|
|
|
$this->setMailingAddress($contact->businesscity, $contact->businesscountry, $contact->businesspostalcode, $contact->businessstate, $contact->businessstreet, $props[$contactprops["businessaddress"]], $props, $contactprops); |
|
1730
|
|
|
} |
|
1731
|
|
|
elseif (isset($props[$contactprops["homeaddress"]])) { |
|
1732
|
|
|
$props[$contactprops["mailingaddress"]] = 1; |
|
1733
|
|
|
$this->setMailingAddress($contact->homecity, $contact->homecountry, $contact->homepostalcode, $contact->homestate, $contact->homestreet, $props[$contactprops["homeaddress"]], $props, $contactprops); |
|
1734
|
|
|
} |
|
1735
|
|
|
elseif (isset($props[$contactprops["otheraddress"]])) { |
|
1736
|
|
|
$props[$contactprops["mailingaddress"]] = 3; |
|
1737
|
|
|
$this->setMailingAddress($contact->othercity, $contact->othercountry, $contact->otherpostalcode, $contact->otherstate, $contact->otherstreet, $props[$contactprops["otheraddress"]], $props, $contactprops); |
|
1738
|
|
|
} |
|
1739
|
|
|
|
|
1740
|
|
|
if (isset($contact->picture)) { |
|
1741
|
|
|
$picbinary = base64_decode($contact->picture); |
|
1742
|
|
|
$picsize = strlen($picbinary); |
|
1743
|
|
|
$props[$contactprops["haspic"]] = false; |
|
1744
|
|
|
|
|
1745
|
|
|
// TODO contact picture handling |
|
1746
|
|
|
// check if contact has already got a picture. delete it first in that case |
|
1747
|
|
|
// delete it also if it was removed on a mobile |
|
1748
|
|
|
$picprops = mapi_getprops($mapimessage, [$contactprops["haspic"]]); |
|
|
|
|
|
|
1749
|
|
|
if (isset($picprops[$contactprops["haspic"]]) && $picprops[$contactprops["haspic"]]) { |
|
1750
|
|
|
SLog::Write(LOGLEVEL_DEBUG, "Contact already has a picture. Delete it"); |
|
1751
|
|
|
|
|
1752
|
|
|
$attachtable = mapi_message_getattachmenttable($mapimessage); |
|
|
|
|
|
|
1753
|
|
|
mapi_table_restrict($attachtable, MAPIUtils::GetContactPicRestriction()); |
|
|
|
|
|
|
1754
|
|
|
$rows = mapi_table_queryallrows($attachtable, [PR_ATTACH_NUM]); |
|
|
|
|
|
|
1755
|
|
|
if (isset($rows) && is_array($rows)) { |
|
1756
|
|
|
foreach ($rows as $row) { |
|
1757
|
|
|
mapi_message_deleteattach($mapimessage, $row[PR_ATTACH_NUM]); |
|
|
|
|
|
|
1758
|
|
|
} |
|
1759
|
|
|
} |
|
1760
|
|
|
} |
|
1761
|
|
|
|
|
1762
|
|
|
// only set picture if there's data in the request |
|
1763
|
|
|
if ($picbinary !== false && $picsize > 0) { |
|
1764
|
|
|
$props[$contactprops["haspic"]] = true; |
|
1765
|
|
|
$pic = mapi_message_createattach($mapimessage); |
|
|
|
|
|
|
1766
|
|
|
// Set properties of the attachment |
|
1767
|
|
|
$picprops = [ |
|
1768
|
|
|
PR_ATTACH_LONG_FILENAME => "ContactPicture.jpg", |
|
1769
|
|
|
PR_DISPLAY_NAME => "ContactPicture.jpg", |
|
1770
|
|
|
0x7FFF000B => true, |
|
1771
|
|
|
PR_ATTACHMENT_HIDDEN => false, |
|
1772
|
|
|
PR_ATTACHMENT_FLAGS => 1, |
|
1773
|
|
|
PR_ATTACH_METHOD => ATTACH_BY_VALUE, |
|
1774
|
|
|
PR_ATTACH_EXTENSION => ".jpg", |
|
1775
|
|
|
PR_ATTACH_NUM => 1, |
|
1776
|
|
|
PR_ATTACH_SIZE => $picsize, |
|
1777
|
|
|
PR_ATTACH_DATA_BIN => $picbinary, |
|
1778
|
|
|
]; |
|
1779
|
|
|
|
|
1780
|
|
|
mapi_setprops($pic, $picprops); |
|
1781
|
|
|
mapi_savechanges($pic); |
|
|
|
|
|
|
1782
|
|
|
} |
|
1783
|
|
|
} |
|
1784
|
|
|
|
|
1785
|
|
|
if (isset($contact->asbody)) { |
|
1786
|
|
|
$this->setASbody($contact->asbody, $props, $contactprops); |
|
1787
|
|
|
} |
|
1788
|
|
|
|
|
1789
|
|
|
// set fileas |
|
1790
|
|
|
if (defined('FILEAS_ORDER')) { |
|
1791
|
|
|
$lastname = (isset($contact->lastname)) ? $contact->lastname : ""; |
|
1792
|
|
|
$firstname = (isset($contact->firstname)) ? $contact->firstname : ""; |
|
1793
|
|
|
$middlename = (isset($contact->middlename)) ? $contact->middlename : ""; |
|
1794
|
|
|
$company = (isset($contact->companyname)) ? $contact->companyname : ""; |
|
1795
|
|
|
$props[$contactprops["fileas"]] = Utils::BuildFileAs($lastname, $firstname, $middlename, $company); |
|
1796
|
|
|
} |
|
1797
|
|
|
else { |
|
1798
|
|
|
SLog::Write(LOGLEVEL_DEBUG, "FILEAS_ORDER not defined"); |
|
1799
|
|
|
} |
|
1800
|
|
|
|
|
1801
|
|
|
mapi_setprops($mapimessage, $props); |
|
1802
|
|
|
|
|
1803
|
|
|
return true; |
|
1804
|
|
|
} |
|
1805
|
|
|
|
|
1806
|
|
|
/** |
|
1807
|
|
|
* Writes a SyncTask to MAPI. |
|
1808
|
|
|
* |
|
1809
|
|
|
* @param mixed $mapimessage |
|
1810
|
|
|
* @param SyncTask $task |
|
1811
|
|
|
* |
|
1812
|
|
|
* @return bool |
|
1813
|
|
|
*/ |
|
1814
|
|
|
private function setTask($mapimessage, $task) { |
|
1815
|
|
|
mapi_setprops($mapimessage, [PR_MESSAGE_CLASS => "IPM.Task"]); |
|
|
|
|
|
|
1816
|
|
|
|
|
1817
|
|
|
$taskmapping = MAPIMapping::GetTaskMapping(); |
|
1818
|
|
|
$taskprops = MAPIMapping::GetTaskProperties(); |
|
1819
|
|
|
$this->setPropsInMAPI($mapimessage, $task, $taskmapping); |
|
1820
|
|
|
$taskprops = array_merge($this->getPropIdsFromStrings($taskmapping), $this->getPropIdsFromStrings($taskprops)); |
|
1821
|
|
|
|
|
1822
|
|
|
// task specific properties to be set |
|
1823
|
|
|
$props = []; |
|
1824
|
|
|
|
|
1825
|
|
|
if (isset($task->asbody)) { |
|
1826
|
|
|
$this->setASbody($task->asbody, $props, $taskprops); |
|
1827
|
|
|
} |
|
1828
|
|
|
|
|
1829
|
|
|
if (isset($task->complete)) { |
|
1830
|
|
|
if ($task->complete) { |
|
1831
|
|
|
// Set completion to 100% |
|
1832
|
|
|
// Set status to 'complete' |
|
1833
|
|
|
$props[$taskprops["completion"]] = 1.0; |
|
1834
|
|
|
$props[$taskprops["status"]] = 2; |
|
1835
|
|
|
$props[$taskprops["reminderset"]] = false; |
|
1836
|
|
|
} |
|
1837
|
|
|
else { |
|
1838
|
|
|
// Set completion to 0% |
|
1839
|
|
|
// Set status to 'not started' |
|
1840
|
|
|
$props[$taskprops["completion"]] = 0.0; |
|
1841
|
|
|
$props[$taskprops["status"]] = 0; |
|
1842
|
|
|
} |
|
1843
|
|
|
} |
|
1844
|
|
|
if (isset($task->recurrence) && class_exists('TaskRecurrence')) { |
|
1845
|
|
|
$deadoccur = false; |
|
1846
|
|
|
if ((isset($task->recurrence->occurrences) && $task->recurrence->occurrences == 1) || |
|
1847
|
|
|
(isset($task->recurrence->deadoccur) && $task->recurrence->deadoccur == 1)) { // ios5 sends deadoccur inside the recurrence |
|
1848
|
|
|
$deadoccur = true; |
|
1849
|
|
|
} |
|
1850
|
|
|
|
|
1851
|
|
|
// Set PR_ICON_INDEX to 1281 to show correct icon in category view |
|
1852
|
|
|
$props[$taskprops["icon"]] = 1281; |
|
1853
|
|
|
// dead occur - false if new occurrences should be generated from the task |
|
1854
|
|
|
// true - if it is the last occurrence of the task |
|
1855
|
|
|
$props[$taskprops["deadoccur"]] = $deadoccur; |
|
1856
|
|
|
$props[$taskprops["isrecurringtag"]] = true; |
|
1857
|
|
|
|
|
1858
|
|
|
$recurrence = new TaskRecurrence($this->store, $mapimessage); |
|
1859
|
|
|
$recur = []; |
|
1860
|
|
|
$this->setRecurrence($task, $recur); |
|
1861
|
|
|
|
|
1862
|
|
|
// task specific recurrence properties which we need to set here |
|
1863
|
|
|
// "start" and "end" are in GMT when passing to class.recurrence |
|
1864
|
|
|
// set recurrence start here because it's calculated differently for tasks and appointments |
|
1865
|
|
|
$recur["start"] = $task->recurrence->start; |
|
1866
|
|
|
$recur["regen"] = (isset($task->recurrence->regenerate) && $task->recurrence->regenerate) ? 1 : 0; |
|
1867
|
|
|
// OL regenerates recurring task itself, but setting deleteOccurrence is required so that PHP-MAPI doesn't regenerate |
|
1868
|
|
|
// completed occurrence of a task. |
|
1869
|
|
|
if ($recur["regen"] == 0) { |
|
1870
|
|
|
$recur["deleteOccurrence"] = 0; |
|
1871
|
|
|
} |
|
1872
|
|
|
// Also add dates to $recur |
|
1873
|
|
|
$recur["duedate"] = $task->duedate; |
|
1874
|
|
|
$recur["complete"] = (isset($task->complete) && $task->complete) ? 1 : 0; |
|
1875
|
|
|
if (isset($task->datecompleted)) { |
|
1876
|
|
|
$recur["datecompleted"] = $task->datecompleted; |
|
1877
|
|
|
} |
|
1878
|
|
|
$recurrence->setRecurrence($recur); |
|
1879
|
|
|
} |
|
1880
|
|
|
|
|
1881
|
|
|
$props[$taskprops["private"]] = (isset($task->sensitivity) && $task->sensitivity >= SENSITIVITY_PRIVATE) ? true : false; |
|
1882
|
|
|
|
|
1883
|
|
|
// Open address book for user resolve to set the owner |
|
1884
|
|
|
$addrbook = $this->getAddressbook(); |
|
|
|
|
|
|
1885
|
|
|
|
|
1886
|
|
|
// check if there is already an owner for the task, set current user if not |
|
1887
|
|
|
$p = [$taskprops["owner"]]; |
|
1888
|
|
|
$owner = $this->getProps($mapimessage, $p); |
|
1889
|
|
|
if (!isset($owner[$taskprops["owner"]])) { |
|
1890
|
|
|
$userinfo = nsp_getuserinfo(Request::GetUser()); |
|
|
|
|
|
|
1891
|
|
|
if (mapi_last_hresult() == NOERROR && isset($userinfo["fullname"])) { |
|
|
|
|
|
|
1892
|
|
|
$props[$taskprops["owner"]] = $userinfo["fullname"]; |
|
1893
|
|
|
} |
|
1894
|
|
|
} |
|
1895
|
|
|
mapi_setprops($mapimessage, $props); |
|
1896
|
|
|
|
|
1897
|
|
|
return true; |
|
1898
|
|
|
} |
|
1899
|
|
|
|
|
1900
|
|
|
/** |
|
1901
|
|
|
* Writes a SyncNote to MAPI. |
|
1902
|
|
|
* |
|
1903
|
|
|
* @param mixed $mapimessage |
|
1904
|
|
|
* @param SyncNote $note |
|
1905
|
|
|
* |
|
1906
|
|
|
* @return bool |
|
1907
|
|
|
*/ |
|
1908
|
|
|
private function setNote($mapimessage, $note) { |
|
1909
|
|
|
// Touchdown does not send categories if all are unset or there is none. |
|
1910
|
|
|
// Setting it to an empty array will unset the property in KC as well |
|
1911
|
|
|
if (!isset($note->categories)) { |
|
1912
|
|
|
$note->categories = []; |
|
1913
|
|
|
} |
|
1914
|
|
|
|
|
1915
|
|
|
// update icon index to correspond to the color |
|
1916
|
|
|
if (isset($note->Color) && $note->Color > -1 && $note->Color < 5) { |
|
1917
|
|
|
$note->Iconindex = 768 + $note->Color; |
|
|
|
|
|
|
1918
|
|
|
} |
|
1919
|
|
|
|
|
1920
|
|
|
$this->setPropsInMAPI($mapimessage, $note, MAPIMapping::GetNoteMapping()); |
|
1921
|
|
|
|
|
1922
|
|
|
$noteprops = MAPIMapping::GetNoteProperties(); |
|
1923
|
|
|
$noteprops = $this->getPropIdsFromStrings($noteprops); |
|
1924
|
|
|
|
|
1925
|
|
|
// note specific properties to be set |
|
1926
|
|
|
$props = []; |
|
1927
|
|
|
$props[$noteprops["messageclass"]] = "IPM.StickyNote"; |
|
1928
|
|
|
// set body otherwise the note will be "broken" when editing it in outlook |
|
1929
|
|
|
if (isset($note->asbody)) { |
|
1930
|
|
|
$this->setASbody($note->asbody, $props, $noteprops); |
|
1931
|
|
|
} |
|
1932
|
|
|
|
|
1933
|
|
|
$props[$noteprops["internetcpid"]] = INTERNET_CPID_UTF8; |
|
1934
|
|
|
mapi_setprops($mapimessage, $props); |
|
|
|
|
|
|
1935
|
|
|
|
|
1936
|
|
|
return true; |
|
1937
|
|
|
} |
|
1938
|
|
|
|
|
1939
|
|
|
/*---------------------------------------------------------------------------------------------------------- |
|
1940
|
|
|
* HELPER |
|
1941
|
|
|
*/ |
|
1942
|
|
|
|
|
1943
|
|
|
/** |
|
1944
|
|
|
* Returns the timestamp offset. |
|
1945
|
|
|
* |
|
1946
|
|
|
* @param string $ts |
|
1947
|
|
|
* |
|
1948
|
|
|
* @return long |
|
1949
|
|
|
*/ |
|
1950
|
|
|
private function GetTZOffset($ts) { |
|
1951
|
|
|
$Offset = date("O", $ts); |
|
|
|
|
|
|
1952
|
|
|
|
|
1953
|
|
|
$Parity = $Offset < 0 ? -1 : 1; |
|
1954
|
|
|
$Offset = $Parity * $Offset; |
|
1955
|
|
|
$Offset = ($Offset - ($Offset % 100)) / 100 * 60 + $Offset % 100; |
|
1956
|
|
|
|
|
1957
|
|
|
return $Parity * $Offset; |
|
|
|
|
|
|
1958
|
|
|
} |
|
1959
|
|
|
|
|
1960
|
|
|
/** |
|
1961
|
|
|
* Localtime of the timestamp. |
|
1962
|
|
|
* |
|
1963
|
|
|
* @param long $time |
|
1964
|
|
|
* |
|
1965
|
|
|
* @return array |
|
1966
|
|
|
*/ |
|
1967
|
|
|
private function gmtime($time) { |
|
1968
|
|
|
$TZOffset = $this->GetTZOffset($time); |
|
1969
|
|
|
|
|
1970
|
|
|
$t_time = $time - $TZOffset * 60; # Counter adjust for localtime() |
|
1971
|
|
|
|
|
1972
|
|
|
return localtime($t_time, 1); |
|
1973
|
|
|
} |
|
1974
|
|
|
|
|
1975
|
|
|
/** |
|
1976
|
|
|
* Sets the properties in a MAPI object according to an Sync object and a property mapping. |
|
1977
|
|
|
* |
|
1978
|
|
|
* @param mixed $mapimessage |
|
1979
|
|
|
* @param SyncObject $message |
|
1980
|
|
|
* @param array $mapping |
|
1981
|
|
|
* |
|
1982
|
|
|
* @return |
|
1983
|
|
|
*/ |
|
1984
|
|
|
private function setPropsInMAPI($mapimessage, $message, $mapping) { |
|
1985
|
|
|
$mapiprops = $this->getPropIdsFromStrings($mapping); |
|
1986
|
|
|
$unsetVars = $message->getUnsetVars(); |
|
1987
|
|
|
$propsToDelete = []; |
|
1988
|
|
|
$propsToSet = []; |
|
1989
|
|
|
|
|
1990
|
|
|
foreach ($mapiprops as $asprop => $mapiprop) { |
|
1991
|
|
|
if (isset($message->{$asprop})) { |
|
1992
|
|
|
// UTF8->windows1252.. this is ok for all numerical values |
|
1993
|
|
|
if (mapi_prop_type($mapiprop) != PT_BINARY && mapi_prop_type($mapiprop) != PT_MV_BINARY) { |
|
|
|
|
|
|
1994
|
|
|
if (is_array($message->{$asprop})) { |
|
1995
|
|
|
$value = array_map("u2wi", $message->{$asprop}); |
|
1996
|
|
|
} |
|
1997
|
|
|
else { |
|
1998
|
|
|
$value = u2wi($message->{$asprop}); |
|
1999
|
|
|
} |
|
2000
|
|
|
} |
|
2001
|
|
|
else { |
|
2002
|
|
|
$value = $message->{$asprop}; |
|
2003
|
|
|
} |
|
2004
|
|
|
|
|
2005
|
|
|
// Make sure the php values are the correct type |
|
2006
|
|
|
switch (mapi_prop_type($mapiprop)) { |
|
2007
|
|
|
case PT_BINARY: |
|
2008
|
|
|
case PT_STRING8: |
|
2009
|
|
|
settype($value, "string"); |
|
2010
|
|
|
break; |
|
2011
|
|
|
|
|
2012
|
|
|
case PT_BOOLEAN: |
|
2013
|
|
|
settype($value, "boolean"); |
|
2014
|
|
|
break; |
|
2015
|
|
|
|
|
2016
|
|
|
case PT_SYSTIME: |
|
2017
|
|
|
case PT_LONG: |
|
2018
|
|
|
settype($value, "integer"); |
|
2019
|
|
|
break; |
|
2020
|
|
|
} |
|
2021
|
|
|
|
|
2022
|
|
|
// decode base64 value |
|
2023
|
|
|
if ($mapiprop == PR_RTF_COMPRESSED) { |
|
2024
|
|
|
$value = base64_decode($value); |
|
2025
|
|
|
if (strlen($value) == 0) { |
|
2026
|
|
|
continue; |
|
2027
|
|
|
} // PDA will sometimes give us an empty RTF, which we'll ignore. |
|
2028
|
|
|
|
|
2029
|
|
|
// Note that you can still remove notes because when you remove notes it gives |
|
2030
|
|
|
// a valid compressed RTF with nothing in it. |
|
2031
|
|
|
} |
|
2032
|
|
|
// if an "empty array" is to be saved, it the mvprop should be deleted - fixes Mantis #468 |
|
2033
|
|
|
if (is_array($value) && empty($value)) { |
|
2034
|
|
|
$propsToDelete[] = $mapiprop; |
|
2035
|
|
|
SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->setPropsInMAPI(): Property '%s' to be deleted as it is an empty array", $asprop)); |
|
2036
|
|
|
} |
|
2037
|
|
|
else { |
|
2038
|
|
|
// all properties will be set at once |
|
2039
|
|
|
$propsToSet[$mapiprop] = $value; |
|
2040
|
|
|
} |
|
2041
|
|
|
} |
|
2042
|
|
|
elseif (in_array($asprop, $unsetVars)) { |
|
2043
|
|
|
$propsToDelete[] = $mapiprop; |
|
2044
|
|
|
} |
|
2045
|
|
|
} |
|
2046
|
|
|
|
|
2047
|
|
|
mapi_setprops($mapimessage, $propsToSet); |
|
|
|
|
|
|
2048
|
|
|
if (mapi_last_hresult()) { |
|
|
|
|
|
|
2049
|
|
|
SLog::Write(LOGLEVEL_WARN, sprintf("Failed to set properties, trying to set them separately. Error code was:%x", mapi_last_hresult())); |
|
2050
|
|
|
$this->setPropsIndividually($mapimessage, $propsToSet, $mapiprops); |
|
2051
|
|
|
} |
|
2052
|
|
|
|
|
2053
|
|
|
mapi_deleteprops($mapimessage, $propsToDelete); |
|
|
|
|
|
|
2054
|
|
|
|
|
2055
|
|
|
// clean up |
|
2056
|
|
|
unset($unsetVars, $propsToDelete); |
|
2057
|
|
|
} |
|
2058
|
|
|
|
|
2059
|
|
|
/** |
|
2060
|
|
|
* Sets the properties one by one in a MAPI object. |
|
2061
|
|
|
* |
|
2062
|
|
|
* @param mixed &$mapimessage |
|
2063
|
|
|
* @param array &$propsToSet |
|
2064
|
|
|
* @param array &$mapiprops |
|
2065
|
|
|
* |
|
2066
|
|
|
* @return |
|
2067
|
|
|
*/ |
|
2068
|
|
|
private function setPropsIndividually(&$mapimessage, &$propsToSet, &$mapiprops) { |
|
2069
|
|
|
foreach ($propsToSet as $prop => $value) { |
|
2070
|
|
|
mapi_setprops($mapimessage, [$prop => $value]); |
|
|
|
|
|
|
2071
|
|
|
if (mapi_last_hresult()) { |
|
|
|
|
|
|
2072
|
|
|
SLog::Write(LOGLEVEL_ERROR, sprintf("Failed setting property [%s] with value [%s], error code was:%x", array_search($prop, $mapiprops), $value, mapi_last_hresult())); |
|
2073
|
|
|
} |
|
2074
|
|
|
} |
|
2075
|
|
|
} |
|
2076
|
|
|
|
|
2077
|
|
|
/** |
|
2078
|
|
|
* Gets the properties from a MAPI object and sets them in the Sync object according to mapping. |
|
2079
|
|
|
* |
|
2080
|
|
|
* @param SyncObject &$message |
|
2081
|
|
|
* @param mixed $mapimessage |
|
2082
|
|
|
* @param array $mapping |
|
2083
|
|
|
* |
|
2084
|
|
|
* @return |
|
2085
|
|
|
*/ |
|
2086
|
|
|
private function getPropsFromMAPI(&$message, $mapimessage, $mapping) { |
|
2087
|
|
|
$messageprops = $this->getProps($mapimessage, $mapping); |
|
2088
|
|
|
foreach ($mapping as $asprop => $mapiprop) { |
|
2089
|
|
|
// Get long strings via openproperty |
|
2090
|
|
|
if (isset($messageprops[mapi_prop_tag(PT_ERROR, mapi_prop_id($mapiprop))])) { |
|
|
|
|
|
|
2091
|
|
|
if ($messageprops[mapi_prop_tag(PT_ERROR, mapi_prop_id($mapiprop))] == MAPI_E_NOT_ENOUGH_MEMORY_32BIT || |
|
2092
|
|
|
$messageprops[mapi_prop_tag(PT_ERROR, mapi_prop_id($mapiprop))] == MAPI_E_NOT_ENOUGH_MEMORY_64BIT) { |
|
2093
|
|
|
$messageprops[$mapiprop] = MAPIUtils::readPropStream($mapimessage, $mapiprop); |
|
2094
|
|
|
} |
|
2095
|
|
|
} |
|
2096
|
|
|
|
|
2097
|
|
|
if (isset($messageprops[$mapiprop])) { |
|
2098
|
|
|
if (mapi_prop_type($mapiprop) == PT_BOOLEAN) { |
|
|
|
|
|
|
2099
|
|
|
// Force to actual '0' or '1' |
|
2100
|
|
|
if ($messageprops[$mapiprop]) { |
|
2101
|
|
|
$message->{$asprop} = 1; |
|
2102
|
|
|
} |
|
2103
|
|
|
else { |
|
2104
|
|
|
$message->{$asprop} = 0; |
|
2105
|
|
|
} |
|
2106
|
|
|
} |
|
2107
|
|
|
else { |
|
2108
|
|
|
// Special handling for PR_MESSAGE_FLAGS |
|
2109
|
|
|
if ($mapiprop == PR_MESSAGE_FLAGS) { |
|
2110
|
|
|
$message->{$asprop} = $messageprops[$mapiprop] & 1; |
|
2111
|
|
|
} // only look at 'read' flag |
|
2112
|
|
|
elseif ($mapiprop == PR_RTF_COMPRESSED) { |
|
2113
|
|
|
// do not send rtf to the mobile |
|
2114
|
|
|
continue; |
|
2115
|
|
|
} |
|
2116
|
|
|
elseif (is_array($messageprops[$mapiprop])) { |
|
2117
|
|
|
$message->{$asprop} = array_map("w2u", $messageprops[$mapiprop]); |
|
2118
|
|
|
} |
|
2119
|
|
|
else { |
|
2120
|
|
|
if (mapi_prop_type($mapiprop) != PT_BINARY && mapi_prop_type($mapiprop) != PT_MV_BINARY) { |
|
2121
|
|
|
$message->{$asprop} = w2u($messageprops[$mapiprop]); |
|
2122
|
|
|
} |
|
2123
|
|
|
else { |
|
2124
|
|
|
$message->{$asprop} = $messageprops[$mapiprop]; |
|
2125
|
|
|
} |
|
2126
|
|
|
} |
|
2127
|
|
|
} |
|
2128
|
|
|
} |
|
2129
|
|
|
} |
|
2130
|
|
|
} |
|
2131
|
|
|
|
|
2132
|
|
|
/** |
|
2133
|
|
|
* Wraps getPropIdsFromStrings() calls. |
|
2134
|
|
|
* |
|
2135
|
|
|
* @param mixed &$mapiprops |
|
2136
|
|
|
* |
|
2137
|
|
|
* @return |
|
2138
|
|
|
*/ |
|
2139
|
|
|
private function getPropIdsFromStrings(&$mapiprops) { |
|
2140
|
|
|
return getPropIdsFromStrings($this->store, $mapiprops); |
|
2141
|
|
|
} |
|
2142
|
|
|
|
|
2143
|
|
|
/** |
|
2144
|
|
|
* Wraps mapi_getprops() calls. |
|
2145
|
|
|
* |
|
2146
|
|
|
* @param mixed &$mapiprops |
|
2147
|
|
|
* @param mixed $mapimessage |
|
2148
|
|
|
* @param mixed $mapiproperties |
|
2149
|
|
|
* |
|
2150
|
|
|
* @return |
|
2151
|
|
|
*/ |
|
2152
|
|
|
protected function getProps($mapimessage, &$mapiproperties) { |
|
2153
|
|
|
$mapiproperties = $this->getPropIdsFromStrings($mapiproperties); |
|
2154
|
|
|
|
|
2155
|
|
|
return mapi_getprops($mapimessage, $mapiproperties); |
|
|
|
|
|
|
2156
|
|
|
} |
|
2157
|
|
|
|
|
2158
|
|
|
/** |
|
2159
|
|
|
* Returns an GMT timezone array. |
|
2160
|
|
|
* |
|
2161
|
|
|
* @return array |
|
2162
|
|
|
*/ |
|
2163
|
|
|
private function getGMTTZ() { |
|
2164
|
|
|
return [ |
|
2165
|
|
|
"bias" => 0, |
|
2166
|
|
|
"tzname" => "", |
|
2167
|
|
|
"dstendyear" => 0, |
|
2168
|
|
|
"dstendmonth" => 10, |
|
2169
|
|
|
"dstendday" => 0, |
|
2170
|
|
|
"dstendweek" => 5, |
|
2171
|
|
|
"dstendhour" => 2, |
|
2172
|
|
|
"dstendminute" => 0, |
|
2173
|
|
|
"dstendsecond" => 0, |
|
2174
|
|
|
"dstendmillis" => 0, |
|
2175
|
|
|
"stdbias" => 0, |
|
2176
|
|
|
"tznamedst" => "", |
|
2177
|
|
|
"dststartyear" => 0, |
|
2178
|
|
|
"dststartmonth" => 3, |
|
2179
|
|
|
"dststartday" => 0, |
|
2180
|
|
|
"dststartweek" => 5, |
|
2181
|
|
|
"dststarthour" => 1, |
|
2182
|
|
|
"dststartminute" => 0, |
|
2183
|
|
|
"dststartsecond" => 0, |
|
2184
|
|
|
"dststartmillis" => 0, |
|
2185
|
|
|
"dstbias" => -60, |
|
2186
|
|
|
]; |
|
2187
|
|
|
} |
|
2188
|
|
|
|
|
2189
|
|
|
/** |
|
2190
|
|
|
* Unpack timezone info from MAPI. |
|
2191
|
|
|
* |
|
2192
|
|
|
* @param string $data |
|
2193
|
|
|
* |
|
2194
|
|
|
* @return array |
|
2195
|
|
|
*/ |
|
2196
|
|
|
private function getTZFromMAPIBlob($data) { |
|
2197
|
|
|
return unpack("lbias/lstdbias/ldstbias/" . |
|
2198
|
|
|
"vconst1/vdstendyear/vdstendmonth/vdstendday/vdstendweek/vdstendhour/vdstendminute/vdstendsecond/vdstendmillis/" . |
|
2199
|
|
|
"vconst2/vdststartyear/vdststartmonth/vdststartday/vdststartweek/vdststarthour/vdststartminute/vdststartsecond/vdststartmillis", $data); |
|
2200
|
|
|
} |
|
2201
|
|
|
|
|
2202
|
|
|
/** |
|
2203
|
|
|
* Unpack timezone info from Sync. |
|
2204
|
|
|
* |
|
2205
|
|
|
* @param string $data |
|
2206
|
|
|
* |
|
2207
|
|
|
* @return array |
|
2208
|
|
|
*/ |
|
2209
|
|
|
private function getTZFromSyncBlob($data) { |
|
2210
|
|
|
$tz = unpack("lbias/a64tzname/vdstendyear/vdstendmonth/vdstendday/vdstendweek/vdstendhour/vdstendminute/vdstendsecond/vdstendmillis/" . |
|
2211
|
|
|
"lstdbias/a64tznamedst/vdststartyear/vdststartmonth/vdststartday/vdststartweek/vdststarthour/vdststartminute/vdststartsecond/vdststartmillis/" . |
|
2212
|
|
|
"ldstbias", $data); |
|
2213
|
|
|
|
|
2214
|
|
|
// Make the structure compatible with class.recurrence.php |
|
2215
|
|
|
$tz["timezone"] = $tz["bias"]; |
|
2216
|
|
|
$tz["timezonedst"] = $tz["dstbias"]; |
|
2217
|
|
|
|
|
2218
|
|
|
return $tz; |
|
2219
|
|
|
} |
|
2220
|
|
|
|
|
2221
|
|
|
/** |
|
2222
|
|
|
* Pack timezone info for MAPI. |
|
2223
|
|
|
* |
|
2224
|
|
|
* @param array $tz |
|
2225
|
|
|
* |
|
2226
|
|
|
* @return string |
|
2227
|
|
|
*/ |
|
2228
|
|
|
private function getMAPIBlobFromTZ($tz) { |
|
2229
|
|
|
return pack( |
|
2230
|
|
|
"lll" . "vvvvvvvvv" . "vvvvvvvvv", |
|
2231
|
|
|
$tz["bias"], |
|
2232
|
|
|
$tz["stdbias"], |
|
2233
|
|
|
$tz["dstbias"], |
|
2234
|
|
|
0, |
|
2235
|
|
|
0, |
|
2236
|
|
|
$tz["dstendmonth"], |
|
2237
|
|
|
$tz["dstendday"], |
|
2238
|
|
|
$tz["dstendweek"], |
|
2239
|
|
|
$tz["dstendhour"], |
|
2240
|
|
|
$tz["dstendminute"], |
|
2241
|
|
|
$tz["dstendsecond"], |
|
2242
|
|
|
$tz["dstendmillis"], |
|
2243
|
|
|
0, |
|
2244
|
|
|
0, |
|
2245
|
|
|
$tz["dststartmonth"], |
|
2246
|
|
|
$tz["dststartday"], |
|
2247
|
|
|
$tz["dststartweek"], |
|
2248
|
|
|
$tz["dststarthour"], |
|
2249
|
|
|
$tz["dststartminute"], |
|
2250
|
|
|
$tz["dststartsecond"], |
|
2251
|
|
|
$tz["dststartmillis"] |
|
2252
|
|
|
); |
|
2253
|
|
|
} |
|
2254
|
|
|
|
|
2255
|
|
|
/** |
|
2256
|
|
|
* Checks the date to see if it is in DST, and returns correct GMT date accordingly. |
|
2257
|
|
|
* |
|
2258
|
|
|
* @param long $localtime |
|
2259
|
|
|
* @param array $tz |
|
2260
|
|
|
* |
|
2261
|
|
|
* @return long |
|
2262
|
|
|
*/ |
|
2263
|
|
|
private function getGMTTimeByTZ($localtime, $tz) { |
|
2264
|
|
|
if (!isset($tz) || !is_array($tz)) { |
|
2265
|
|
|
return $localtime; |
|
2266
|
|
|
} |
|
2267
|
|
|
|
|
2268
|
|
|
if ($this->isDST($localtime, $tz)) { |
|
2269
|
|
|
return $localtime + $tz["bias"] * 60 + $tz["dstbias"] * 60; |
|
|
|
|
|
|
2270
|
|
|
} |
|
2271
|
|
|
|
|
2272
|
|
|
return $localtime + $tz["bias"] * 60; |
|
|
|
|
|
|
2273
|
|
|
} |
|
2274
|
|
|
|
|
2275
|
|
|
/** |
|
2276
|
|
|
* Returns the local time for the given GMT time, taking account of the given timezone. |
|
2277
|
|
|
* |
|
2278
|
|
|
* @param long $gmttime |
|
2279
|
|
|
* @param array $tz |
|
2280
|
|
|
* |
|
2281
|
|
|
* @return long |
|
2282
|
|
|
*/ |
|
2283
|
|
|
private function getLocaltimeByTZ($gmttime, $tz) { |
|
2284
|
|
|
if (!isset($tz) || !is_array($tz)) { |
|
2285
|
|
|
return $gmttime; |
|
2286
|
|
|
} |
|
2287
|
|
|
|
|
2288
|
|
|
if ($this->isDST($gmttime - $tz["bias"] * 60, $tz)) { // may bug around the switch time because it may have to be 'gmttime - bias - dstbias' |
|
|
|
|
|
|
2289
|
|
|
return $gmttime - $tz["bias"] * 60 - $tz["dstbias"] * 60; |
|
|
|
|
|
|
2290
|
|
|
} |
|
2291
|
|
|
|
|
2292
|
|
|
return $gmttime - $tz["bias"] * 60; |
|
|
|
|
|
|
2293
|
|
|
} |
|
2294
|
|
|
|
|
2295
|
|
|
/** |
|
2296
|
|
|
* Returns TRUE if it is the summer and therefore DST is in effect. |
|
2297
|
|
|
* |
|
2298
|
|
|
* @param long $localtime |
|
2299
|
|
|
* @param array $tz |
|
2300
|
|
|
* |
|
2301
|
|
|
* @return bool |
|
2302
|
|
|
*/ |
|
2303
|
|
|
private function isDST($localtime, $tz) { |
|
2304
|
|
|
if (!isset($tz) || !is_array($tz) || |
|
2305
|
|
|
!isset($tz["dstbias"]) || $tz["dstbias"] == 0 || |
|
2306
|
|
|
!isset($tz["dststartmonth"]) || $tz["dststartmonth"] == 0 || |
|
2307
|
|
|
!isset($tz["dstendmonth"]) || $tz["dstendmonth"] == 0) { |
|
2308
|
|
|
return false; |
|
2309
|
|
|
} |
|
2310
|
|
|
|
|
2311
|
|
|
$year = gmdate("Y", $localtime); |
|
|
|
|
|
|
2312
|
|
|
$start = $this->getTimestampOfWeek($year, $tz["dststartmonth"], $tz["dststartweek"], $tz["dststartday"], $tz["dststarthour"], $tz["dststartminute"], $tz["dststartsecond"]); |
|
|
|
|
|
|
2313
|
|
|
$end = $this->getTimestampOfWeek($year, $tz["dstendmonth"], $tz["dstendweek"], $tz["dstendday"], $tz["dstendhour"], $tz["dstendminute"], $tz["dstendsecond"]); |
|
2314
|
|
|
|
|
2315
|
|
|
if ($start < $end) { |
|
2316
|
|
|
// northern hemisphere (july = dst) |
|
2317
|
|
|
if ($localtime >= $start && $localtime < $end) { |
|
2318
|
|
|
$dst = true; |
|
2319
|
|
|
} |
|
2320
|
|
|
else { |
|
2321
|
|
|
$dst = false; |
|
2322
|
|
|
} |
|
2323
|
|
|
} |
|
2324
|
|
|
else { |
|
2325
|
|
|
// southern hemisphere (january = dst) |
|
2326
|
|
|
if ($localtime >= $end && $localtime < $start) { |
|
2327
|
|
|
$dst = false; |
|
2328
|
|
|
} |
|
2329
|
|
|
else { |
|
2330
|
|
|
$dst = true; |
|
2331
|
|
|
} |
|
2332
|
|
|
} |
|
2333
|
|
|
|
|
2334
|
|
|
return $dst; |
|
2335
|
|
|
} |
|
2336
|
|
|
|
|
2337
|
|
|
/** |
|
2338
|
|
|
* Returns the local timestamp for the $week'th $wday of $month in $year at $hour:$minute:$second. |
|
2339
|
|
|
* |
|
2340
|
|
|
* @param int $year |
|
2341
|
|
|
* @param int $month |
|
2342
|
|
|
* @param int $week |
|
2343
|
|
|
* @param int $wday |
|
2344
|
|
|
* @param int $hour |
|
2345
|
|
|
* @param int $minute |
|
2346
|
|
|
* @param int $second |
|
2347
|
|
|
* |
|
2348
|
|
|
* @return long |
|
2349
|
|
|
*/ |
|
2350
|
|
|
private function getTimestampOfWeek($year, $month, $week, $wday, $hour, $minute, $second) { |
|
2351
|
|
|
if ($month == 0) { |
|
2352
|
|
|
return; |
|
2353
|
|
|
} |
|
2354
|
|
|
|
|
2355
|
|
|
$date = gmmktime($hour, $minute, $second, $month, 1, $year); |
|
2356
|
|
|
|
|
2357
|
|
|
// Find first day in month which matches day of the week |
|
2358
|
|
|
while (1) { |
|
2359
|
|
|
$wdaynow = gmdate("w", $date); |
|
2360
|
|
|
if ($wdaynow == $wday) { |
|
2361
|
|
|
break; |
|
2362
|
|
|
} |
|
2363
|
|
|
$date += 24 * 60 * 60; |
|
2364
|
|
|
} |
|
2365
|
|
|
|
|
2366
|
|
|
// Forward $week weeks (may 'overflow' into the next month) |
|
2367
|
|
|
$date = $date + $week * (24 * 60 * 60 * 7); |
|
2368
|
|
|
|
|
2369
|
|
|
// Reverse 'overflow'. Eg week '10' will always be the last week of the month in which the |
|
2370
|
|
|
// specified weekday exists |
|
2371
|
|
|
while (1) { |
|
2372
|
|
|
$monthnow = gmdate("n", $date); // gmdate returns 1-12 |
|
2373
|
|
|
if ($monthnow > $month) { |
|
2374
|
|
|
$date = $date - (24 * 7 * 60 * 60); |
|
2375
|
|
|
} |
|
2376
|
|
|
else { |
|
2377
|
|
|
break; |
|
2378
|
|
|
} |
|
2379
|
|
|
} |
|
2380
|
|
|
|
|
2381
|
|
|
return $date; |
|
|
|
|
|
|
2382
|
|
|
} |
|
2383
|
|
|
|
|
2384
|
|
|
/** |
|
2385
|
|
|
* Normalize the given timestamp to the start of the day. |
|
2386
|
|
|
* |
|
2387
|
|
|
* @param long $timestamp |
|
2388
|
|
|
* |
|
2389
|
|
|
* @return long |
|
2390
|
|
|
*/ |
|
2391
|
|
|
private function getDayStartOfTimestamp($timestamp) { |
|
2392
|
|
|
return $timestamp - ($timestamp % (60 * 60 * 24)); |
|
|
|
|
|
|
2393
|
|
|
} |
|
2394
|
|
|
|
|
2395
|
|
|
/** |
|
2396
|
|
|
* Returns an SMTP address from an entry id. |
|
2397
|
|
|
* |
|
2398
|
|
|
* @param string $entryid |
|
2399
|
|
|
* |
|
2400
|
|
|
* @return string |
|
2401
|
|
|
*/ |
|
2402
|
|
|
private function getSMTPAddressFromEntryID($entryid) { |
|
2403
|
|
|
$addrbook = $this->getAddressbook(); |
|
2404
|
|
|
|
|
2405
|
|
|
$mailuser = mapi_ab_openentry($addrbook, $entryid); |
|
|
|
|
|
|
2406
|
|
|
if (!$mailuser) { |
|
2407
|
|
|
return ""; |
|
2408
|
|
|
} |
|
2409
|
|
|
|
|
2410
|
|
|
$props = mapi_getprops($mailuser, [PR_ADDRTYPE, PR_SMTP_ADDRESS, PR_EMAIL_ADDRESS]); |
|
|
|
|
|
|
2411
|
|
|
|
|
2412
|
|
|
$addrtype = isset($props[PR_ADDRTYPE]) ? $props[PR_ADDRTYPE] : ""; |
|
2413
|
|
|
|
|
2414
|
|
|
if (isset($props[PR_SMTP_ADDRESS])) { |
|
2415
|
|
|
return $props[PR_SMTP_ADDRESS]; |
|
2416
|
|
|
} |
|
2417
|
|
|
|
|
2418
|
|
|
if ($addrtype == "SMTP" && isset($props[PR_EMAIL_ADDRESS])) { |
|
2419
|
|
|
return $props[PR_EMAIL_ADDRESS]; |
|
2420
|
|
|
} |
|
2421
|
|
|
if ($addrtype == "ZARAFA" && isset($props[PR_EMAIL_ADDRESS])) { |
|
2422
|
|
|
$userinfo = nsp_getuserinfo($props[PR_EMAIL_ADDRESS]); |
|
|
|
|
|
|
2423
|
|
|
if (is_array($userinfo) && isset($userinfo["primary_email"])) { |
|
2424
|
|
|
return $userinfo["primary_email"]; |
|
2425
|
|
|
} |
|
2426
|
|
|
} |
|
2427
|
|
|
|
|
2428
|
|
|
return ""; |
|
2429
|
|
|
} |
|
2430
|
|
|
|
|
2431
|
|
|
/** |
|
2432
|
|
|
* Returns fullname from an entryid. |
|
2433
|
|
|
* |
|
2434
|
|
|
* @param binary $entryid |
|
|
|
|
|
|
2435
|
|
|
* |
|
2436
|
|
|
* @return string fullname or false on error |
|
2437
|
|
|
*/ |
|
2438
|
|
|
private function getFullnameFromEntryID($entryid) { |
|
2439
|
|
|
$addrbook = $this->getAddressbook(); |
|
2440
|
|
|
$mailuser = mapi_ab_openentry($addrbook, $entryid); |
|
|
|
|
|
|
2441
|
|
|
if (!$mailuser) { |
|
2442
|
|
|
SLog::Write(LOGLEVEL_ERROR, sprintf("Unable to get mailuser for getFullnameFromEntryID (0x%X)", mapi_last_hresult())); |
|
|
|
|
|
|
2443
|
|
|
|
|
2444
|
|
|
return false; |
|
|
|
|
|
|
2445
|
|
|
} |
|
2446
|
|
|
|
|
2447
|
|
|
$props = mapi_getprops($mailuser, [PR_DISPLAY_NAME]); |
|
|
|
|
|
|
2448
|
|
|
if (isset($props[PR_DISPLAY_NAME])) { |
|
2449
|
|
|
return $props[PR_DISPLAY_NAME]; |
|
2450
|
|
|
} |
|
2451
|
|
|
SLog::Write(LOGLEVEL_ERROR, sprintf("Unable to get fullname for getFullnameFromEntryID (0x%X)", mapi_last_hresult())); |
|
2452
|
|
|
|
|
2453
|
|
|
return false; |
|
|
|
|
|
|
2454
|
|
|
} |
|
2455
|
|
|
|
|
2456
|
|
|
/** |
|
2457
|
|
|
* Builds a displayname from several separated values. |
|
2458
|
|
|
* |
|
2459
|
|
|
* @param SyncContact $contact |
|
2460
|
|
|
* |
|
2461
|
|
|
* @return string |
|
2462
|
|
|
*/ |
|
2463
|
|
|
private function composeDisplayName(&$contact) { |
|
2464
|
|
|
// Set display name and subject to a combined value of firstname and lastname |
|
2465
|
|
|
$cname = (isset($contact->prefix)) ? u2w($contact->prefix) . " " : ""; |
|
|
|
|
|
|
2466
|
|
|
$cname .= u2w($contact->firstname); |
|
2467
|
|
|
$cname .= (isset($contact->middlename)) ? " " . u2w($contact->middlename) : ""; |
|
|
|
|
|
|
2468
|
|
|
$cname .= " " . u2w($contact->lastname); |
|
|
|
|
|
|
2469
|
|
|
$cname .= (isset($contact->suffix)) ? " " . u2w($contact->suffix) : ""; |
|
|
|
|
|
|
2470
|
|
|
|
|
2471
|
|
|
return trim($cname); |
|
2472
|
|
|
} |
|
2473
|
|
|
|
|
2474
|
|
|
/** |
|
2475
|
|
|
* Sets all dependent properties for an email address. |
|
2476
|
|
|
* |
|
2477
|
|
|
* @param string $emailAddress |
|
2478
|
|
|
* @param string $displayName |
|
2479
|
|
|
* @param int $cnt |
|
2480
|
|
|
* @param array &$props |
|
2481
|
|
|
* @param array &$properties |
|
2482
|
|
|
* @param array &$nremails |
|
2483
|
|
|
* @param int &$abprovidertype |
|
2484
|
|
|
* |
|
2485
|
|
|
* @return |
|
2486
|
|
|
*/ |
|
2487
|
|
|
private function setEmailAddress($emailAddress, $displayName, $cnt, &$props, &$properties, &$nremails, &$abprovidertype) { |
|
2488
|
|
|
if (isset($emailAddress)) { |
|
2489
|
|
|
$name = (isset($displayName)) ? $displayName : $emailAddress; |
|
2490
|
|
|
|
|
2491
|
|
|
$props[$properties["emailaddress{$cnt}"]] = $emailAddress; |
|
2492
|
|
|
$props[$properties["emailaddressdemail{$cnt}"]] = $emailAddress; |
|
2493
|
|
|
$props[$properties["emailaddressdname{$cnt}"]] = $name; |
|
2494
|
|
|
$props[$properties["emailaddresstype{$cnt}"]] = "SMTP"; |
|
2495
|
|
|
$props[$properties["emailaddressentryid{$cnt}"]] = mapi_createoneoff($name, "SMTP", $emailAddress); |
|
|
|
|
|
|
2496
|
|
|
$nremails[] = $cnt - 1; |
|
2497
|
|
|
$abprovidertype |= 2 ^ ($cnt - 1); |
|
2498
|
|
|
} |
|
2499
|
|
|
} |
|
2500
|
|
|
|
|
2501
|
|
|
/** |
|
2502
|
|
|
* Sets the properties for an address string. |
|
2503
|
|
|
* |
|
2504
|
|
|
* @param string $type which address is being set |
|
2505
|
|
|
* @param string $city |
|
2506
|
|
|
* @param string $country |
|
2507
|
|
|
* @param string $postalcode |
|
2508
|
|
|
* @param string $state |
|
2509
|
|
|
* @param string $street |
|
2510
|
|
|
* @param array &$props |
|
2511
|
|
|
* @param array &$properties |
|
2512
|
|
|
* |
|
2513
|
|
|
* @return |
|
2514
|
|
|
*/ |
|
2515
|
|
|
private function setAddress($type, &$city, &$country, &$postalcode, &$state, &$street, &$props, &$properties) { |
|
2516
|
|
|
if (isset($city)) { |
|
2517
|
|
|
$props[$properties[$type . "city"]] = $city = u2w($city); |
|
2518
|
|
|
} |
|
2519
|
|
|
|
|
2520
|
|
|
if (isset($country)) { |
|
2521
|
|
|
$props[$properties[$type . "country"]] = $country = u2w($country); |
|
2522
|
|
|
} |
|
2523
|
|
|
|
|
2524
|
|
|
if (isset($postalcode)) { |
|
2525
|
|
|
$props[$properties[$type . "postalcode"]] = $postalcode = u2w($postalcode); |
|
2526
|
|
|
} |
|
2527
|
|
|
|
|
2528
|
|
|
if (isset($state)) { |
|
2529
|
|
|
$props[$properties[$type . "state"]] = $state = u2w($state); |
|
2530
|
|
|
} |
|
2531
|
|
|
|
|
2532
|
|
|
if (isset($street)) { |
|
2533
|
|
|
$props[$properties[$type . "street"]] = $street = u2w($street); |
|
2534
|
|
|
} |
|
2535
|
|
|
|
|
2536
|
|
|
// set composed address |
|
2537
|
|
|
$address = Utils::BuildAddressString($street, $postalcode, $city, $state, $country); |
|
2538
|
|
|
if ($address) { |
|
2539
|
|
|
$props[$properties[$type . "address"]] = $address; |
|
2540
|
|
|
} |
|
2541
|
|
|
} |
|
2542
|
|
|
|
|
2543
|
|
|
/** |
|
2544
|
|
|
* Sets the properties for a mailing address. |
|
2545
|
|
|
* |
|
2546
|
|
|
* @param string $city |
|
2547
|
|
|
* @param string $country |
|
2548
|
|
|
* @param string $postalcode |
|
2549
|
|
|
* @param string $state |
|
2550
|
|
|
* @param string $street |
|
2551
|
|
|
* @param string $address |
|
2552
|
|
|
* @param array &$props |
|
2553
|
|
|
* @param array &$properties |
|
2554
|
|
|
* |
|
2555
|
|
|
* @return |
|
2556
|
|
|
*/ |
|
2557
|
|
|
private function setMailingAddress($city, $country, $postalcode, $state, $street, $address, &$props, &$properties) { |
|
2558
|
|
|
if (isset($city)) { |
|
2559
|
|
|
$props[$properties["city"]] = $city; |
|
2560
|
|
|
} |
|
2561
|
|
|
if (isset($country)) { |
|
2562
|
|
|
$props[$properties["country"]] = $country; |
|
2563
|
|
|
} |
|
2564
|
|
|
if (isset($postalcode)) { |
|
2565
|
|
|
$props[$properties["postalcode"]] = $postalcode; |
|
2566
|
|
|
} |
|
2567
|
|
|
if (isset($state)) { |
|
2568
|
|
|
$props[$properties["state"]] = $state; |
|
2569
|
|
|
} |
|
2570
|
|
|
if (isset($street)) { |
|
2571
|
|
|
$props[$properties["street"]] = $street; |
|
2572
|
|
|
} |
|
2573
|
|
|
if (isset($address)) { |
|
2574
|
|
|
$props[$properties["postaladdress"]] = $address; |
|
2575
|
|
|
} |
|
2576
|
|
|
} |
|
2577
|
|
|
|
|
2578
|
|
|
/** |
|
2579
|
|
|
* Sets data in a recurrence array. |
|
2580
|
|
|
* |
|
2581
|
|
|
* @param SyncObject $message |
|
2582
|
|
|
* @param array &$recur |
|
2583
|
|
|
* |
|
2584
|
|
|
* @return |
|
2585
|
|
|
*/ |
|
2586
|
|
|
private function setRecurrence($message, &$recur) { |
|
2587
|
|
|
if (isset($message->complete)) { |
|
2588
|
|
|
$recur["complete"] = $message->complete; |
|
2589
|
|
|
} |
|
2590
|
|
|
|
|
2591
|
|
|
if (!isset($message->recurrence->interval)) { |
|
2592
|
|
|
$message->recurrence->interval = 1; |
|
2593
|
|
|
} |
|
2594
|
|
|
|
|
2595
|
|
|
// set the default value of numoccur |
|
2596
|
|
|
$recur["numoccur"] = 0; |
|
2597
|
|
|
// a place holder for recurrencetype property |
|
2598
|
|
|
$recur["recurrencetype"] = 0; |
|
2599
|
|
|
|
|
2600
|
|
|
switch ($message->recurrence->type) { |
|
2601
|
|
|
case 0: |
|
2602
|
|
|
$recur["type"] = 10; |
|
2603
|
|
|
if (isset($message->recurrence->dayofweek)) { |
|
2604
|
|
|
$recur["subtype"] = 1; |
|
2605
|
|
|
} |
|
2606
|
|
|
else { |
|
2607
|
|
|
$recur["subtype"] = 0; |
|
2608
|
|
|
} |
|
2609
|
|
|
|
|
2610
|
|
|
$recur["everyn"] = $message->recurrence->interval * (60 * 24); |
|
2611
|
|
|
$recur["recurrencetype"] = 1; |
|
2612
|
|
|
break; |
|
2613
|
|
|
|
|
2614
|
|
|
case 1: |
|
2615
|
|
|
$recur["type"] = 11; |
|
2616
|
|
|
$recur["subtype"] = 1; |
|
2617
|
|
|
$recur["everyn"] = $message->recurrence->interval; |
|
2618
|
|
|
$recur["recurrencetype"] = 2; |
|
2619
|
|
|
break; |
|
2620
|
|
|
|
|
2621
|
|
|
case 2: |
|
2622
|
|
|
$recur["type"] = 12; |
|
2623
|
|
|
$recur["subtype"] = 2; |
|
2624
|
|
|
$recur["everyn"] = $message->recurrence->interval; |
|
2625
|
|
|
$recur["recurrencetype"] = 3; |
|
2626
|
|
|
break; |
|
2627
|
|
|
|
|
2628
|
|
|
case 3: |
|
2629
|
|
|
$recur["type"] = 12; |
|
2630
|
|
|
$recur["subtype"] = 3; |
|
2631
|
|
|
$recur["everyn"] = $message->recurrence->interval; |
|
2632
|
|
|
$recur["recurrencetype"] = 3; |
|
2633
|
|
|
break; |
|
2634
|
|
|
|
|
2635
|
|
|
case 4: |
|
2636
|
|
|
$recur["type"] = 13; |
|
2637
|
|
|
$recur["subtype"] = 1; |
|
2638
|
|
|
$recur["everyn"] = $message->recurrence->interval * 12; |
|
2639
|
|
|
$recur["recurrencetype"] = 4; |
|
2640
|
|
|
break; |
|
2641
|
|
|
|
|
2642
|
|
|
case 5: |
|
2643
|
|
|
$recur["type"] = 13; |
|
2644
|
|
|
$recur["subtype"] = 2; |
|
2645
|
|
|
$recur["everyn"] = $message->recurrence->interval * 12; |
|
2646
|
|
|
$recur["recurrencetype"] = 4; |
|
2647
|
|
|
break; |
|
2648
|
|
|
|
|
2649
|
|
|
case 6: |
|
2650
|
|
|
$recur["type"] = 13; |
|
2651
|
|
|
$recur["subtype"] = 3; |
|
2652
|
|
|
$recur["everyn"] = $message->recurrence->interval * 12; |
|
2653
|
|
|
$recur["recurrencetype"] = 4; |
|
2654
|
|
|
break; |
|
2655
|
|
|
} |
|
2656
|
|
|
|
|
2657
|
|
|
// "start" and "end" are in GMT when passing to class.recurrence |
|
2658
|
|
|
$recur["end"] = $this->getDayStartOfTimestamp(0x7FFFFFFF); // Maximum GMT value for end by default |
|
|
|
|
|
|
2659
|
|
|
|
|
2660
|
|
|
if (isset($message->recurrence->until)) { |
|
2661
|
|
|
$recur["term"] = 0x21; |
|
2662
|
|
|
$recur["end"] = $message->recurrence->until; |
|
2663
|
|
|
} |
|
2664
|
|
|
elseif (isset($message->recurrence->occurrences)) { |
|
2665
|
|
|
$recur["term"] = 0x22; |
|
2666
|
|
|
$recur["numoccur"] = $message->recurrence->occurrences; |
|
2667
|
|
|
} |
|
2668
|
|
|
else { |
|
2669
|
|
|
$recur["term"] = 0x23; |
|
2670
|
|
|
} |
|
2671
|
|
|
|
|
2672
|
|
|
if (isset($message->recurrence->dayofweek)) { |
|
2673
|
|
|
$recur["weekdays"] = $message->recurrence->dayofweek; |
|
2674
|
|
|
} |
|
2675
|
|
|
if (isset($message->recurrence->weekofmonth)) { |
|
2676
|
|
|
$recur["nday"] = $message->recurrence->weekofmonth; |
|
2677
|
|
|
} |
|
2678
|
|
|
if (isset($message->recurrence->monthofyear)) { |
|
2679
|
|
|
// MAPI stores months as the amount of minutes until the beginning of the month in a |
|
2680
|
|
|
// non-leapyear. Why this is, is totally unclear. |
|
2681
|
|
|
$monthminutes = [0, 44640, 84960, 129600, 172800, 217440, 260640, 305280, 348480, 393120, 437760, 480960]; |
|
2682
|
|
|
$recur["month"] = $monthminutes[$message->recurrence->monthofyear - 1]; |
|
2683
|
|
|
} |
|
2684
|
|
|
if (isset($message->recurrence->dayofmonth)) { |
|
2685
|
|
|
$recur["monthday"] = $message->recurrence->dayofmonth; |
|
2686
|
|
|
} |
|
2687
|
|
|
} |
|
2688
|
|
|
|
|
2689
|
|
|
/** |
|
2690
|
|
|
* Extracts the email address (mailbox@host) from an email address because |
|
2691
|
|
|
* some devices send email address as "Firstname Lastname" <[email protected]>. |
|
2692
|
|
|
* |
|
2693
|
|
|
* @see http://developer.berlios.de/mantis/view.php?id=486 |
|
2694
|
|
|
* |
|
2695
|
|
|
* @param string $email |
|
2696
|
|
|
* |
|
2697
|
|
|
* @return string or false on error |
|
2698
|
|
|
*/ |
|
2699
|
|
|
private function extractEmailAddress($email) { |
|
2700
|
|
|
if (!isset($this->zRFC822)) { |
|
2701
|
|
|
$this->zRFC822 = new Mail_RFC822(); |
|
2702
|
|
|
} |
|
2703
|
|
|
$parsedAddress = $this->zRFC822->parseAddressList($email); |
|
2704
|
|
|
if (!isset($parsedAddress[0]->mailbox) || !isset($parsedAddress[0]->host)) { |
|
2705
|
|
|
return false; |
|
|
|
|
|
|
2706
|
|
|
} |
|
2707
|
|
|
|
|
2708
|
|
|
return $parsedAddress[0]->mailbox . '@' . $parsedAddress[0]->host; |
|
2709
|
|
|
} |
|
2710
|
|
|
|
|
2711
|
|
|
/** |
|
2712
|
|
|
* Returns the message body for a required format. |
|
2713
|
|
|
* |
|
2714
|
|
|
* @param MAPIMessage $mapimessage |
|
|
|
|
|
|
2715
|
|
|
* @param int $bpReturnType |
|
2716
|
|
|
* @param SyncObject $message |
|
2717
|
|
|
* |
|
2718
|
|
|
* @return bool |
|
2719
|
|
|
*/ |
|
2720
|
|
|
private function setMessageBodyForType($mapimessage, $bpReturnType, &$message) { |
|
2721
|
|
|
$truncateHtmlSafe = false; |
|
2722
|
|
|
// default value is PR_BODY |
|
2723
|
|
|
$property = PR_BODY; |
|
2724
|
|
|
|
|
2725
|
|
|
switch ($bpReturnType) { |
|
2726
|
|
|
case SYNC_BODYPREFERENCE_HTML: |
|
2727
|
|
|
$property = PR_HTML; |
|
2728
|
|
|
$truncateHtmlSafe = true; |
|
2729
|
|
|
break; |
|
2730
|
|
|
|
|
2731
|
|
|
case SYNC_BODYPREFERENCE_RTF: |
|
2732
|
|
|
$property = PR_RTF_COMPRESSED; |
|
2733
|
|
|
break; |
|
2734
|
|
|
|
|
2735
|
|
|
case SYNC_BODYPREFERENCE_MIME: |
|
2736
|
|
|
$stat = $this->imtoinet($mapimessage, $message); |
|
2737
|
|
|
if (isset($message->asbody)) { |
|
2738
|
|
|
$message->asbody->type = $bpReturnType; |
|
2739
|
|
|
} |
|
2740
|
|
|
|
|
2741
|
|
|
return $stat; |
|
2742
|
|
|
} |
|
2743
|
|
|
|
|
2744
|
|
|
$stream = mapi_openproperty($mapimessage, $property, IID_IStream, 0, 0); |
|
|
|
|
|
|
2745
|
|
|
if ($stream) { |
|
2746
|
|
|
$stat = mapi_stream_stat($stream); |
|
|
|
|
|
|
2747
|
|
|
$streamsize = $stat['cb']; |
|
2748
|
|
|
} |
|
2749
|
|
|
else { |
|
2750
|
|
|
$streamsize = 0; |
|
2751
|
|
|
} |
|
2752
|
|
|
|
|
2753
|
|
|
// set the properties according to supported AS version |
|
2754
|
|
|
if (Request::GetProtocolVersion() >= 12.0) { |
|
2755
|
|
|
$message->asbody = new SyncBaseBody(); |
|
2756
|
|
|
$message->asbody->type = $bpReturnType; |
|
2757
|
|
|
if ($bpReturnType == SYNC_BODYPREFERENCE_RTF) { |
|
2758
|
|
|
$body = $this->mapiReadStream($stream, $streamsize); |
|
2759
|
|
|
$message->asbody->data = StringStreamWrapper::Open(base64_encode($body)); |
|
2760
|
|
|
} |
|
2761
|
|
|
elseif (isset($message->internetcpid) && $bpReturnType == SYNC_BODYPREFERENCE_HTML) { |
|
2762
|
|
|
// if PR_HTML is UTF-8 we can stream it directly, else we have to convert to UTF-8 & wrap it |
|
2763
|
|
|
if ($message->internetcpid == INTERNET_CPID_UTF8) { |
|
2764
|
|
|
$message->asbody->data = MAPIStreamWrapper::Open($stream, $truncateHtmlSafe); |
|
2765
|
|
|
} |
|
2766
|
|
|
else { |
|
2767
|
|
|
$body = $this->mapiReadStream($stream, $streamsize); |
|
2768
|
|
|
$message->asbody->data = StringStreamWrapper::Open(Utils::ConvertCodepageStringToUtf8($message->internetcpid, $body), $truncateHtmlSafe); |
|
2769
|
|
|
$message->internetcpid = INTERNET_CPID_UTF8; |
|
2770
|
|
|
} |
|
2771
|
|
|
} |
|
2772
|
|
|
else { |
|
2773
|
|
|
$message->asbody->data = MAPIStreamWrapper::Open($stream); |
|
2774
|
|
|
} |
|
2775
|
|
|
$message->asbody->estimatedDataSize = $streamsize; |
|
2776
|
|
|
} |
|
2777
|
|
|
else { |
|
2778
|
|
|
$body = $this->mapiReadStream($stream, $streamsize); |
|
2779
|
|
|
$message->body = str_replace("\n", "\r\n", w2u(str_replace("\r", "", $body))); |
|
2780
|
|
|
$message->bodysize = $streamsize; |
|
2781
|
|
|
$message->bodytruncated = 0; |
|
2782
|
|
|
} |
|
2783
|
|
|
|
|
2784
|
|
|
return true; |
|
2785
|
|
|
} |
|
2786
|
|
|
|
|
2787
|
|
|
/** |
|
2788
|
|
|
* Reads from a mapi stream, if it's set. If not, returns an empty string. |
|
2789
|
|
|
* |
|
2790
|
|
|
* @param resource $stream |
|
2791
|
|
|
* @param int $size |
|
2792
|
|
|
* |
|
2793
|
|
|
* @return string |
|
2794
|
|
|
*/ |
|
2795
|
|
|
private function mapiReadStream($stream, $size) { |
|
2796
|
|
|
if (!$stream || $size == 0) { |
|
|
|
|
|
|
2797
|
|
|
return ""; |
|
2798
|
|
|
} |
|
2799
|
|
|
|
|
2800
|
|
|
return mapi_stream_read($stream, $size); |
|
|
|
|
|
|
2801
|
|
|
} |
|
2802
|
|
|
|
|
2803
|
|
|
/** |
|
2804
|
|
|
* A wrapper for mapi_inetmapi_imtoinet function. |
|
2805
|
|
|
* |
|
2806
|
|
|
* @param MAPIMessage $mapimessage |
|
2807
|
|
|
* @param SyncObject $message |
|
2808
|
|
|
* |
|
2809
|
|
|
* @return bool |
|
2810
|
|
|
*/ |
|
2811
|
|
|
private function imtoinet($mapimessage, &$message) { |
|
2812
|
|
|
$mapiEmail = mapi_getprops($mapimessage, [PR_EC_IMAP_EMAIL]); |
|
|
|
|
|
|
2813
|
|
|
$stream = false; |
|
2814
|
|
|
if (isset($mapiEmail[PR_EC_IMAP_EMAIL]) || MAPIUtils::GetError(PR_EC_IMAP_EMAIL, $mapiEmail) == MAPI_E_NOT_ENOUGH_MEMORY) { |
|
2815
|
|
|
$stream = mapi_openproperty($mapimessage, PR_EC_IMAP_EMAIL, IID_IStream, 0, 0); |
|
|
|
|
|
|
2816
|
|
|
SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->imtoinet(): using PR_EC_IMAP_EMAIL as full RFC822 message"); |
|
2817
|
|
|
} |
|
2818
|
|
|
else { |
|
2819
|
|
|
$addrbook = $this->getAddressbook(); |
|
2820
|
|
|
$stream = mapi_inetmapi_imtoinet($this->session, $addrbook, $mapimessage, ['use_tnef' => -1, 'ignore_missing_attachments' => 1]); |
|
|
|
|
|
|
2821
|
|
|
} |
|
2822
|
|
|
if (is_resource($stream)) { |
|
2823
|
|
|
$mstreamstat = mapi_stream_stat($stream); |
|
|
|
|
|
|
2824
|
|
|
$streamsize = $mstreamstat["cb"]; |
|
2825
|
|
|
if (isset($streamsize)) { |
|
2826
|
|
|
if (Request::GetProtocolVersion() >= 12.0) { |
|
2827
|
|
|
if (!isset($message->asbody)) { |
|
2828
|
|
|
$message->asbody = new SyncBaseBody(); |
|
2829
|
|
|
} |
|
2830
|
|
|
$message->asbody->data = MAPIStreamWrapper::Open($stream); |
|
|
|
|
|
|
2831
|
|
|
$message->asbody->estimatedDataSize = $streamsize; |
|
2832
|
|
|
$message->asbody->truncated = 0; |
|
2833
|
|
|
} |
|
2834
|
|
|
else { |
|
2835
|
|
|
$message->mimedata = MAPIStreamWrapper::Open($stream); |
|
2836
|
|
|
$message->mimesize = $streamsize; |
|
2837
|
|
|
$message->mimetruncated = 0; |
|
2838
|
|
|
} |
|
2839
|
|
|
unset($message->body, $message->bodytruncated); |
|
2840
|
|
|
|
|
2841
|
|
|
return true; |
|
2842
|
|
|
} |
|
2843
|
|
|
} |
|
2844
|
|
|
SLog::Write(LOGLEVEL_ERROR, "MAPIProvider->imtoinet(): got no stream or content from mapi_inetmapi_imtoinet()"); |
|
2845
|
|
|
|
|
2846
|
|
|
return false; |
|
2847
|
|
|
} |
|
2848
|
|
|
|
|
2849
|
|
|
/** |
|
2850
|
|
|
* Sets the message body. |
|
2851
|
|
|
* |
|
2852
|
|
|
* @param MAPIMessage $mapimessage |
|
2853
|
|
|
* @param ContentParameters $contentparameters |
|
2854
|
|
|
* @param SyncObject $message |
|
2855
|
|
|
*/ |
|
2856
|
|
|
private function setMessageBody($mapimessage, $contentparameters, &$message) { |
|
2857
|
|
|
// get the available body preference types |
|
2858
|
|
|
$bpTypes = $contentparameters->GetBodyPreference(); |
|
2859
|
|
|
if ($bpTypes !== false) { |
|
2860
|
|
|
SLog::Write(LOGLEVEL_DEBUG, sprintf("BodyPreference types: %s", implode(', ', $bpTypes))); |
|
2861
|
|
|
// do not send mime data if the client requests it |
|
2862
|
|
|
if (($contentparameters->GetMimeSupport() == SYNC_MIMESUPPORT_NEVER) && ($key = array_search(SYNC_BODYPREFERENCE_MIME, $bpTypes) !== false)) { |
|
2863
|
|
|
unset($bpTypes[$key]); |
|
2864
|
|
|
SLog::Write(LOGLEVEL_DEBUG, sprintf("Remove mime body preference type because the device required no mime support. BodyPreference types: %s", implode(', ', $bpTypes))); |
|
2865
|
|
|
} |
|
2866
|
|
|
// get the best fitting preference type |
|
2867
|
|
|
$bpReturnType = Utils::GetBodyPreferenceBestMatch($bpTypes); |
|
2868
|
|
|
SLog::Write(LOGLEVEL_DEBUG, sprintf("GetBodyPreferenceBestMatch: %d", $bpReturnType)); |
|
2869
|
|
|
$bpo = $contentparameters->BodyPreference($bpReturnType); |
|
2870
|
|
|
SLog::Write(LOGLEVEL_DEBUG, sprintf("bpo: truncation size:'%d', allornone:'%d', preview:'%d'", $bpo->GetTruncationSize(), $bpo->GetAllOrNone(), $bpo->GetPreview())); |
|
|
|
|
|
|
2871
|
|
|
|
|
2872
|
|
|
// Android Blackberry expects a full mime message for signed emails |
|
2873
|
|
|
// @see https://jira.z-hub.io/projects/ZP/issues/ZP-1154 |
|
2874
|
|
|
// @TODO change this when refactoring |
|
2875
|
|
|
$props = mapi_getprops($mapimessage, [PR_MESSAGE_CLASS]); |
|
|
|
|
|
|
2876
|
|
|
if (isset($props[PR_MESSAGE_CLASS]) && |
|
2877
|
|
|
stripos($props[PR_MESSAGE_CLASS], 'IPM.Note.SMIME.MultipartSigned') !== false && |
|
2878
|
|
|
($key = array_search(SYNC_BODYPREFERENCE_MIME, $bpTypes) !== false)) { |
|
|
|
|
|
|
2879
|
|
|
SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->setMessageBody(): enforcing SYNC_BODYPREFERENCE_MIME type for a signed message")); |
|
2880
|
|
|
$bpReturnType = SYNC_BODYPREFERENCE_MIME; |
|
2881
|
|
|
} |
|
2882
|
|
|
|
|
2883
|
|
|
$this->setMessageBodyForType($mapimessage, $bpReturnType, $message); |
|
2884
|
|
|
// only set the truncation size data if device set it in request |
|
2885
|
|
|
if ($bpo->GetTruncationSize() != false && |
|
2886
|
|
|
$bpReturnType != SYNC_BODYPREFERENCE_MIME && |
|
2887
|
|
|
$message->asbody->estimatedDataSize > $bpo->GetTruncationSize() |
|
2888
|
|
|
) { |
|
2889
|
|
|
// Truncated plaintext requests are used on iOS for the preview in the email list. All images and links should be removed - see https://jira.z-hub.io/browse/ZP-1025 |
|
2890
|
|
|
if ($bpReturnType == SYNC_BODYPREFERENCE_PLAIN) { |
|
2891
|
|
|
SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->setMessageBody(): truncated plain-text body requested, stripping all links and images"); |
|
2892
|
|
|
// Get more data because of the filtering it's most probably going down in size. It's going to be truncated to the correct size below. |
|
2893
|
|
|
$plainbody = stream_get_contents($message->asbody->data, $bpo->GetTruncationSize() * 5); |
|
2894
|
|
|
$message->asbody->data = StringStreamWrapper::Open(preg_replace('/<http(s){0,1}:\/\/.*?>/i', '', $plainbody)); |
|
2895
|
|
|
} |
|
2896
|
|
|
|
|
2897
|
|
|
// truncate data stream |
|
2898
|
|
|
ftruncate($message->asbody->data, $bpo->GetTruncationSize()); |
|
|
|
|
|
|
2899
|
|
|
$message->asbody->truncated = 1; |
|
2900
|
|
|
} |
|
2901
|
|
|
// set the preview or windows phones won't show the preview of an email |
|
2902
|
|
|
if (Request::GetProtocolVersion() >= 14.0 && $bpo->GetPreview()) { |
|
2903
|
|
|
$message->asbody->preview = Utils::Utf8_truncate(MAPIUtils::readPropStream($mapimessage, PR_BODY), $bpo->GetPreview()); |
|
|
|
|
|
|
2904
|
|
|
} |
|
2905
|
|
|
} |
|
2906
|
|
|
else { |
|
2907
|
|
|
// Override 'body' for truncation |
|
2908
|
|
|
$truncsize = Utils::GetTruncSize($contentparameters->GetTruncation()); |
|
|
|
|
|
|
2909
|
|
|
$this->setMessageBodyForType($mapimessage, SYNC_BODYPREFERENCE_PLAIN, $message); |
|
2910
|
|
|
|
|
2911
|
|
|
if ($message->bodysize > $truncsize) { |
|
2912
|
|
|
$message->body = Utils::Utf8_truncate($message->body, $truncsize); |
|
2913
|
|
|
$message->bodytruncated = 1; |
|
2914
|
|
|
} |
|
2915
|
|
|
|
|
2916
|
|
|
if (!isset($message->body) || strlen($message->body) == 0) { |
|
2917
|
|
|
$message->body = " "; |
|
2918
|
|
|
} |
|
2919
|
|
|
|
|
2920
|
|
|
if ($contentparameters->GetMimeSupport() == SYNC_MIMESUPPORT_ALWAYS) { |
|
2921
|
|
|
// set the html body for iphone in AS 2.5 version |
|
2922
|
|
|
$this->imtoinet($mapimessage, $message); |
|
2923
|
|
|
} |
|
2924
|
|
|
} |
|
2925
|
|
|
} |
|
2926
|
|
|
|
|
2927
|
|
|
/** |
|
2928
|
|
|
* Sets properties for an email message. |
|
2929
|
|
|
* |
|
2930
|
|
|
* @param mixed $mapimessage |
|
2931
|
|
|
* @param SyncMail $message |
|
2932
|
|
|
*/ |
|
2933
|
|
|
private function setFlag($mapimessage, &$message) { |
|
2934
|
|
|
// do nothing if protocol version is lower than 12.0 as flags haven't been defined before |
|
2935
|
|
|
if (Request::GetProtocolVersion() < 12.0) { |
|
2936
|
|
|
return; |
|
2937
|
|
|
} |
|
2938
|
|
|
|
|
2939
|
|
|
$message->flag = new SyncMailFlags(); |
|
2940
|
|
|
|
|
2941
|
|
|
$this->getPropsFromMAPI($message->flag, $mapimessage, MAPIMapping::GetMailFlagsMapping()); |
|
2942
|
|
|
} |
|
2943
|
|
|
|
|
2944
|
|
|
/** |
|
2945
|
|
|
* Sets information from SyncBaseBody type for a MAPI message. |
|
2946
|
|
|
* |
|
2947
|
|
|
* @param SyncBaseBody $asbody |
|
2948
|
|
|
* @param array $props |
|
2949
|
|
|
* @param array $appointmentprops |
|
2950
|
|
|
*/ |
|
2951
|
|
|
private function setASbody($asbody, &$props, $appointmentprops) { |
|
2952
|
|
|
// TODO: fix checking for the length |
|
2953
|
|
|
if (isset($asbody->type, $asbody->data) /* && strlen($asbody->data) > 0 */) { |
|
2954
|
|
|
switch ($asbody->type) { |
|
2955
|
|
|
case SYNC_BODYPREFERENCE_PLAIN: |
|
2956
|
|
|
default: |
|
2957
|
|
|
// set plain body if the type is not in valid range |
|
2958
|
|
|
$props[$appointmentprops["body"]] = stream_get_contents($asbody->data); |
|
2959
|
|
|
break; |
|
2960
|
|
|
|
|
2961
|
|
|
case SYNC_BODYPREFERENCE_HTML: |
|
2962
|
|
|
$props[$appointmentprops["html"]] = stream_get_contents($asbody->data); |
|
2963
|
|
|
break; |
|
2964
|
|
|
|
|
2965
|
|
|
case SYNC_BODYPREFERENCE_RTF: |
|
2966
|
|
|
break; |
|
2967
|
|
|
|
|
2968
|
|
|
case SYNC_BODYPREFERENCE_MIME: |
|
2969
|
|
|
break; |
|
2970
|
|
|
} |
|
2971
|
|
|
} |
|
2972
|
|
|
else { |
|
2973
|
|
|
SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->setASbody either type or data are not set. Setting to empty body"); |
|
2974
|
|
|
$props[$appointmentprops["body"]] = ""; |
|
2975
|
|
|
} |
|
2976
|
|
|
} |
|
2977
|
|
|
|
|
2978
|
|
|
/** |
|
2979
|
|
|
* Get MAPI addressbook object. |
|
2980
|
|
|
* |
|
2981
|
|
|
* @return MAPIAddressbook object to be used with mapi_ab_* or false on failure |
|
|
|
|
|
|
2982
|
|
|
*/ |
|
2983
|
|
|
private function getAddressbook() { |
|
2984
|
|
|
if (isset($this->addressbook) && $this->addressbook) { |
|
2985
|
|
|
return $this->addressbook; |
|
2986
|
|
|
} |
|
2987
|
|
|
$this->addressbook = mapi_openaddressbook($this->session); |
|
|
|
|
|
|
2988
|
|
|
$result = mapi_last_hresult(); |
|
|
|
|
|
|
2989
|
|
|
if ($result && $this->addressbook === false) { |
|
2990
|
|
|
SLog::Write(LOGLEVEL_ERROR, sprintf("MAPIProvider->getAddressbook error opening addressbook 0x%X", $result)); |
|
2991
|
|
|
|
|
2992
|
|
|
return false; |
|
|
|
|
|
|
2993
|
|
|
} |
|
2994
|
|
|
|
|
2995
|
|
|
return $this->addressbook; |
|
2996
|
|
|
} |
|
2997
|
|
|
|
|
2998
|
|
|
/** |
|
2999
|
|
|
* Gets the required store properties. |
|
3000
|
|
|
* |
|
3001
|
|
|
* @return array |
|
3002
|
|
|
*/ |
|
3003
|
|
|
public function GetStoreProps() { |
|
3004
|
|
|
if (!isset($this->storeProps) || empty($this->storeProps)) { |
|
3005
|
|
|
SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->GetStoreProps(): Getting store properties."); |
|
3006
|
|
|
$this->storeProps = mapi_getprops($this->store, [PR_IPM_SUBTREE_ENTRYID, PR_IPM_OUTBOX_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_ENTRYID, PR_IPM_PUBLIC_FOLDERS_ENTRYID, PR_IPM_FAVORITES_ENTRYID, PR_MAILBOX_OWNER_ENTRYID]); |
|
|
|
|
|
|
3007
|
|
|
// make sure all properties are set |
|
3008
|
|
|
if (!isset($this->storeProps[PR_IPM_WASTEBASKET_ENTRYID])) { |
|
3009
|
|
|
$this->storeProps[PR_IPM_WASTEBASKET_ENTRYID] = false; |
|
3010
|
|
|
} |
|
3011
|
|
|
if (!isset($this->storeProps[PR_IPM_SENTMAIL_ENTRYID])) { |
|
3012
|
|
|
$this->storeProps[PR_IPM_SENTMAIL_ENTRYID] = false; |
|
3013
|
|
|
} |
|
3014
|
|
|
if (!isset($this->storeProps[PR_IPM_OUTBOX_ENTRYID])) { |
|
3015
|
|
|
$this->storeProps[PR_IPM_OUTBOX_ENTRYID] = false; |
|
3016
|
|
|
} |
|
3017
|
|
|
if (!isset($this->storeProps[PR_IPM_PUBLIC_FOLDERS_ENTRYID])) { |
|
3018
|
|
|
$this->storeProps[PR_IPM_PUBLIC_FOLDERS_ENTRYID] = false; |
|
3019
|
|
|
} |
|
3020
|
|
|
} |
|
3021
|
|
|
|
|
3022
|
|
|
return $this->storeProps; |
|
3023
|
|
|
} |
|
3024
|
|
|
|
|
3025
|
|
|
/** |
|
3026
|
|
|
* Gets the required inbox properties. |
|
3027
|
|
|
* |
|
3028
|
|
|
* @return array |
|
3029
|
|
|
*/ |
|
3030
|
|
|
public function GetInboxProps() { |
|
3031
|
|
|
if (!isset($this->inboxProps) || empty($this->inboxProps)) { |
|
3032
|
|
|
SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->GetInboxProps(): Getting inbox properties."); |
|
3033
|
|
|
$this->inboxProps = []; |
|
3034
|
|
|
$inbox = mapi_msgstore_getreceivefolder($this->store); |
|
|
|
|
|
|
3035
|
|
|
if ($inbox) { |
|
3036
|
|
|
$this->inboxProps = mapi_getprops($inbox, [PR_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_TASK_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_JOURNAL_ENTRYID]); |
|
|
|
|
|
|
3037
|
|
|
// make sure all properties are set |
|
3038
|
|
|
if (!isset($this->inboxProps[PR_ENTRYID])) { |
|
3039
|
|
|
$this->inboxProps[PR_ENTRYID] = false; |
|
3040
|
|
|
} |
|
3041
|
|
|
if (!isset($this->inboxProps[PR_IPM_DRAFTS_ENTRYID])) { |
|
3042
|
|
|
$this->inboxProps[PR_IPM_DRAFTS_ENTRYID] = false; |
|
3043
|
|
|
} |
|
3044
|
|
|
if (!isset($this->inboxProps[PR_IPM_TASK_ENTRYID])) { |
|
3045
|
|
|
$this->inboxProps[PR_IPM_TASK_ENTRYID] = false; |
|
3046
|
|
|
} |
|
3047
|
|
|
if (!isset($this->inboxProps[PR_IPM_APPOINTMENT_ENTRYID])) { |
|
3048
|
|
|
$this->inboxProps[PR_IPM_APPOINTMENT_ENTRYID] = false; |
|
3049
|
|
|
} |
|
3050
|
|
|
if (!isset($this->inboxProps[PR_IPM_CONTACT_ENTRYID])) { |
|
3051
|
|
|
$this->inboxProps[PR_IPM_CONTACT_ENTRYID] = false; |
|
3052
|
|
|
} |
|
3053
|
|
|
if (!isset($this->inboxProps[PR_IPM_NOTE_ENTRYID])) { |
|
3054
|
|
|
$this->inboxProps[PR_IPM_NOTE_ENTRYID] = false; |
|
3055
|
|
|
} |
|
3056
|
|
|
if (!isset($this->inboxProps[PR_IPM_JOURNAL_ENTRYID])) { |
|
3057
|
|
|
$this->inboxProps[PR_IPM_JOURNAL_ENTRYID] = false; |
|
3058
|
|
|
} |
|
3059
|
|
|
} |
|
3060
|
|
|
} |
|
3061
|
|
|
|
|
3062
|
|
|
return $this->inboxProps; |
|
3063
|
|
|
} |
|
3064
|
|
|
|
|
3065
|
|
|
/** |
|
3066
|
|
|
* Gets the required store root properties. |
|
3067
|
|
|
* |
|
3068
|
|
|
* @return array |
|
3069
|
|
|
*/ |
|
3070
|
|
|
private function getRootProps() { |
|
3071
|
|
|
if (!isset($this->rootProps)) { |
|
3072
|
|
|
$root = mapi_msgstore_openentry($this->store, null); |
|
|
|
|
|
|
3073
|
|
|
$this->rootProps = mapi_getprops($root, [PR_IPM_OL2007_ENTRYIDS]); |
|
|
|
|
|
|
3074
|
|
|
} |
|
3075
|
|
|
|
|
3076
|
|
|
return $this->rootProps; |
|
3077
|
|
|
} |
|
3078
|
|
|
|
|
3079
|
|
|
/** |
|
3080
|
|
|
* Returns an array with entryids of some special folders. |
|
3081
|
|
|
* |
|
3082
|
|
|
* @return array |
|
3083
|
|
|
*/ |
|
3084
|
|
|
private function getSpecialFoldersData() { |
|
3085
|
|
|
// The persist data of an entry in PR_IPM_OL2007_ENTRYIDS consists of: |
|
3086
|
|
|
// PersistId - e.g. RSF_PID_SUGGESTED_CONTACTS (2 bytes) |
|
3087
|
|
|
// DataElementsSize - size of DataElements field (2 bytes) |
|
3088
|
|
|
// DataElements - array of PersistElement structures (variable size) |
|
3089
|
|
|
// PersistElement Structure consists of |
|
3090
|
|
|
// ElementID - e.g. RSF_ELID_ENTRYID (2 bytes) |
|
3091
|
|
|
// ElementDataSize - size of ElementData (2 bytes) |
|
3092
|
|
|
// ElementData - The data for the special folder identified by the PersistID (variable size) |
|
3093
|
|
|
if (empty($this->specialFoldersData)) { |
|
3094
|
|
|
$this->specialFoldersData = []; |
|
3095
|
|
|
$rootProps = $this->getRootProps(); |
|
3096
|
|
|
if (isset($rootProps[PR_IPM_OL2007_ENTRYIDS])) { |
|
3097
|
|
|
$persistData = $rootProps[PR_IPM_OL2007_ENTRYIDS]; |
|
3098
|
|
|
while (strlen($persistData) > 0) { |
|
3099
|
|
|
// PERSIST_SENTINEL marks the end of the persist data |
|
3100
|
|
|
if (strlen($persistData) == 4 && $persistData == PERSIST_SENTINEL) { |
|
3101
|
|
|
break; |
|
3102
|
|
|
} |
|
3103
|
|
|
$unpackedData = unpack("vdataSize/velementID/velDataSize", substr($persistData, 2, 6)); |
|
3104
|
|
|
if (isset($unpackedData['dataSize'], $unpackedData['elementID']) && $unpackedData['elementID'] == RSF_ELID_ENTRYID && isset($unpackedData['elDataSize'])) { |
|
3105
|
|
|
$this->specialFoldersData[] = substr($persistData, 8, $unpackedData['elDataSize']); |
|
3106
|
|
|
// Add PersistId and DataElementsSize lengths to the data size as they're not part of it |
|
3107
|
|
|
$persistData = substr($persistData, $unpackedData['dataSize'] + 4); |
|
3108
|
|
|
} |
|
3109
|
|
|
else { |
|
3110
|
|
|
SLog::Write(LOGLEVEL_INFO, "MAPIProvider->getSpecialFoldersData(): persistent data is not valid"); |
|
3111
|
|
|
break; |
|
3112
|
|
|
} |
|
3113
|
|
|
} |
|
3114
|
|
|
} |
|
3115
|
|
|
} |
|
3116
|
|
|
|
|
3117
|
|
|
return $this->specialFoldersData; |
|
3118
|
|
|
} |
|
3119
|
|
|
|
|
3120
|
|
|
/** |
|
3121
|
|
|
* Extracts email address from PR_SEARCH_KEY property if possible. |
|
3122
|
|
|
* |
|
3123
|
|
|
* @param string $searchKey |
|
3124
|
|
|
* |
|
3125
|
|
|
* @see https://jira.z-hub.io/browse/ZP-1178 |
|
3126
|
|
|
* |
|
3127
|
|
|
* @return string |
|
3128
|
|
|
*/ |
|
3129
|
|
|
private function getEmailAddressFromSearchKey($searchKey) { |
|
3130
|
|
|
if (strpos($searchKey, ':') !== false && strpos($searchKey, '@') !== false) { |
|
3131
|
|
|
SLog::Write(LOGLEVEL_INFO, "MAPIProvider->getEmailAddressFromSearchKey(): fall back to PR_SEARCH_KEY or PR_SENT_REPRESENTING_SEARCH_KEY to resolve user and get email address"); |
|
3132
|
|
|
|
|
3133
|
|
|
return trim(strtolower(explode(':', $searchKey)[1])); |
|
3134
|
|
|
} |
|
3135
|
|
|
|
|
3136
|
|
|
return ""; |
|
3137
|
|
|
} |
|
3138
|
|
|
|
|
3139
|
|
|
/** |
|
3140
|
|
|
* Returns categories for a message. |
|
3141
|
|
|
* |
|
3142
|
|
|
* @param binary $parentsourcekey |
|
3143
|
|
|
* @param binary $sourcekey |
|
3144
|
|
|
* |
|
3145
|
|
|
* @return array or false on failure |
|
3146
|
|
|
*/ |
|
3147
|
|
|
public function GetMessageCategories($parentsourcekey, $sourcekey) { |
|
3148
|
|
|
$entryid = mapi_msgstore_entryidfromsourcekey($this->store, $parentsourcekey, $sourcekey); |
|
|
|
|
|
|
3149
|
|
|
if (!$entryid) { |
|
3150
|
|
|
SLog::Write(LOGLEVEL_INFO, sprintf("MAPIProvider->GetMessageCategories(): Couldn't retrieve message, sourcekey: '%s', parentsourcekey: '%s'", bin2hex($sourcekey), bin2hex($parentsourcekey))); |
|
3151
|
|
|
|
|
3152
|
|
|
return false; |
|
|
|
|
|
|
3153
|
|
|
} |
|
3154
|
|
|
$mapimessage = mapi_msgstore_openentry($this->store, $entryid); |
|
|
|
|
|
|
3155
|
|
|
$emailMapping = MAPIMapping::GetEmailMapping(); |
|
3156
|
|
|
$emailMapping = ["categories" => $emailMapping["categories"]]; |
|
3157
|
|
|
$messageCategories = $this->getProps($mapimessage, $emailMapping); |
|
3158
|
|
|
if (isset($messageCategories[$emailMapping["categories"]]) && is_array($messageCategories[$emailMapping["categories"]])) { |
|
3159
|
|
|
return $messageCategories[$emailMapping["categories"]]; |
|
3160
|
|
|
} |
|
3161
|
|
|
|
|
3162
|
|
|
return false; |
|
|
|
|
|
|
3163
|
|
|
} |
|
3164
|
|
|
} |
|
3165
|
|
|
|