Passed
Push — master ( bc722f...03cdb2 )
by
unknown
03:21 queued 12s
created

MAPIProvider::imtoinet()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 28
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 20
c 0
b 0
f 0
nop 2
dl 0
loc 28
rs 9.2888
nc 5
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]);
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
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]);
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_SIZE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_NUM was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
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));
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_DATA_BIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
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);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type array expected by parameter $tz of MAPIProvider::getRecurrence(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

139
			$this->getRecurrence($mapimessage, $messageprops, $message, $message->recurrence, /** @scrutinizer ignore-type */ false);
Loading history...
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);
0 ignored issues
show
Bug introduced by
The constant PR_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_RECIPIENT_TRACKSTATUS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SMTP_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_RECIPIENT_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
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_DEBUG, 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]) &&
0 ignored issues
show
Bug introduced by
The constant PR_MAILBOX_OWNER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
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);
0 ignored issues
show
Bug introduced by
$this->getDayStartOfTime...>recur['startocc'] * 60 of type integer is incompatible with the type long expected by parameter $localtime of MAPIProvider::getGMTTimeByTZ(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

504
				$exception->exceptionstarttime = $this->getGMTTimeByTZ(/** @scrutinizer ignore-type */ $this->getDayStartOfTimestamp($change["basedate"]) + $recurrence->recur["startocc"] * 60, $tz);
Loading history...
505
506
				// open body because getting only property might not work because of memory limit
507
				$exceptionatt = $recurrence->getExceptionAttachment($change["basedate"]);
0 ignored issues
show
Bug introduced by
The method getExceptionAttachment() does not exist on TaskRecurrence. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

507
				/** @scrutinizer ignore-call */ 
508
    $exceptionatt = $recurrence->getExceptionAttachment($change["basedate"]);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
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());
0 ignored issues
show
Bug introduced by
It seems like $contentparameters->GetBodyPreference() can also be of type false; however, parameter $bpTypes of Utils::GetBodyPreferenceBestMatch() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

593
		$bpReturnType = Utils::GetBodyPreferenceBestMatch(/** @scrutinizer ignore-type */ $contentparameters->GetBodyPreference());
Loading history...
594
		if (($contentparameters->GetMimeSupport() == SYNC_MIMESUPPORT_NEVER) ||
0 ignored issues
show
Bug introduced by
The method GetMimeSupport() does not exist on ContentParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

594
		if (($contentparameters->/** @scrutinizer ignore-call */ GetMimeSupport() == SYNC_MIMESUPPORT_NEVER) ||
Loading history...
595
				($key = array_search(SYNC_BODYPREFERENCE_MIME, $contentparameters->GetBodyPreference()) === false) ||
0 ignored issues
show
Unused Code introduced by
The assignment to $key is dead and can be removed.
Loading history...
Bug introduced by
It seems like $contentparameters->GetBodyPreference() can also be of type false; however, parameter $haystack of array_search() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

595
				($key = array_search(SYNC_BODYPREFERENCE_MIME, /** @scrutinizer ignore-type */ $contentparameters->GetBodyPreference()) === false) ||
Loading history...
596
				$bpReturnType != SYNC_BODYPREFERENCE_MIME) {
597
			MAPIUtils::ParseSmime($this->session, $this->store, $this->getAddressbook(), $mapimessage);
0 ignored issues
show
Bug introduced by
$this->store of type resource is incompatible with the type MAPIStore expected by parameter $store of MAPIUtils::ParseSmime(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

597
			MAPIUtils::ParseSmime($this->session, /** @scrutinizer ignore-type */ $this->store, $this->getAddressbook(), $mapimessage);
Loading history...
Bug introduced by
$this->session of type resource is incompatible with the type MAPISession expected by parameter $session of MAPIUtils::ParseSmime(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

597
			MAPIUtils::ParseSmime(/** @scrutinizer ignore-type */ $this->session, $this->store, $this->getAddressbook(), $mapimessage);
Loading history...
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])) {
0 ignored issues
show
Bug introduced by
The constant PR_SOURCE_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
608
			$sourcekey = $messageprops[PR_SOURCE_KEY];
0 ignored issues
show
Unused Code introduced by
The assignment to $sourcekey is dead and can be removed.
Loading history...
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) . ">";
0 ignored issues
show
Bug introduced by
Are you sure w2u($fromname) of type false|mixed|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

640
			$from = "\"" . /** @scrutinizer ignore-type */ w2u($fromname) . "\" <" . w2u($fromaddr) . ">";
Loading history...
Bug introduced by
Are you sure w2u($fromaddr) of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

640
			$from = "\"" . w2u($fromname) . "\" <" . /** @scrutinizer ignore-type */ w2u($fromaddr) . ">";
Loading history...
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)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $req seems to never exist and therefore isset should always be false.
Loading history...
766
						$req = new Meetingrequest($this->store, $mapimessage, $this->session);
767
					}
768
					if ($req->isMeetingRequest() && !$req->isLocalOrganiser() && !$req->isMeetingOutOfDate()) {
769
						$req->doAccept(true, false, false);
770
					}
771
					if ($req->isMeetingRequestResponse()) {
772
						$req->processMeetingRequestResponse();
773
					}
774
					if ($req->isMeetingCancellation()) {
775
						$req->processMeetingCancellation();
776
					}
777
				}
778
			}
779
			$message->contentclass = DEFAULT_CALENDAR_CONTENTCLASS;
780
781
			// MeetingMessageType values
782
			// 0 = A silent update was performed, or the message type is unspecified.
783
			// 1 = Initial meeting request.
784
			// 2 = Full update.
785
			// 3 = Informational update.
786
			// 4 = Outdated. A newer meeting request or meeting update was received after this message.
787
			// 5 = Identifies the delegator's copy of the meeting request.
788
			// 6 = Identifies that the meeting request has been delegated and the meeting request cannot be responded to.
789
			$message->meetingrequest->meetingmessagetype = mtgEmpty;
790
791
			if (isset($props[$meetingrequestproperties["meetingType"]])) {
792
				switch ($props[$meetingrequestproperties["meetingType"]]) {
793
					case mtgRequest:
794
						$message->meetingrequest->meetingmessagetype = 1;
795
						break;
796
797
					case mtgFull:
798
						$message->meetingrequest->meetingmessagetype = 2;
799
						break;
800
801
					case mtgInfo:
802
						$message->meetingrequest->meetingmessagetype = 3;
803
						break;
804
805
					case mtgOutOfDate:
806
						$message->meetingrequest->meetingmessagetype = 4;
807
						break;
808
809
					case mtgDelegatorCopy:
810
						$message->meetingrequest->meetingmessagetype = 5;
811
						break;
812
				}
813
			}
814
		}
815
816
		// Add attachments
817
		$attachtable = mapi_message_getattachmenttable($mapimessage);
818
		$rows = mapi_table_queryallrows($attachtable, [PR_ATTACH_NUM]);
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_NUM was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
819
		$entryid = bin2hex($messageprops[$emailproperties["entryid"]]);
820
		$parentSourcekey = bin2hex($messageprops[$emailproperties["parentsourcekey"]]);
821
822
		foreach ($rows as $row) {
823
			if (isset($row[PR_ATTACH_NUM])) {
824
				if (Request::GetProtocolVersion() >= 12.0) {
825
					$attach = new SyncBaseAttachment();
826
				}
827
				else {
828
					$attach = new SyncAttachment();
829
				}
830
831
				$mapiattach = mapi_message_openattach($mapimessage, $row[PR_ATTACH_NUM]);
832
				$attachprops = mapi_getprops($mapiattach, [PR_ATTACH_LONG_FILENAME, PR_ATTACH_FILENAME, PR_ATTACHMENT_HIDDEN, PR_ATTACH_CONTENT_ID, PR_ATTACH_CONTENT_ID_A, PR_ATTACH_MIME_TAG, PR_ATTACH_METHOD, PR_DISPLAY_NAME, PR_ATTACH_SIZE, PR_ATTACH_FLAGS]);
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_LONG_FILENAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_SIZE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_CONTENT_ID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_CONTENT_ID_A was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_FILENAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_MIME_TAG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACHMENT_HIDDEN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_METHOD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
833
				if ((isset($attachprops[PR_ATTACH_MIME_TAG]) && strpos(strtolower($attachprops[PR_ATTACH_MIME_TAG]), 'signed') !== false)) {
834
					continue;
835
				}
836
837
				// the displayname is handled equally for all AS versions
838
				$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")));
839
				// fix attachment name in case of inline images
840
				if (($attach->displayname == "inline.txt" && isset($attachprops[PR_ATTACH_MIME_TAG])) ||
841
						(substr_compare($attach->displayname, "attachment", 0, 10, true) === 0 && substr_compare($attach->displayname, ".dat", -4, 4, true) === 0)) {
0 ignored issues
show
Bug introduced by
It seems like $attach->displayname can also be of type false; however, parameter $haystack of substr_compare() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

841
						(substr_compare(/** @scrutinizer ignore-type */ $attach->displayname, "attachment", 0, 10, true) === 0 && substr_compare($attach->displayname, ".dat", -4, 4, true) === 0)) {
Loading history...
842
					$mimetype = $attachprops[PR_ATTACH_MIME_TAG] ?? 'application/octet-stream';
843
					$mime = explode("/", $mimetype);
844
845
					if (count($mime) == 2 && $mime[0] == "image") {
846
						$attach->displayname = "inline." . $mime[1];
847
					}
848
				}
849
850
				// set AS version specific parameters
851
				if (Request::GetProtocolVersion() >= 12.0) {
852
					$attach->filereference = sprintf("%s:%s:%s", $entryid, $row[PR_ATTACH_NUM], $parentSourcekey);
0 ignored issues
show
Bug introduced by
The property filereference does not seem to exist on SyncAttachment.
Loading history...
853
					$attach->method = (isset($attachprops[PR_ATTACH_METHOD])) ? $attachprops[PR_ATTACH_METHOD] : ATTACH_BY_VALUE;
0 ignored issues
show
Bug introduced by
The property method does not exist on SyncAttachment. Did you mean attmethod?
Loading history...
854
855
					// if displayname does not have the eml extension for embedde messages, android and WP devices won't open it
856
					if ($attach->method == ATTACH_EMBEDDED_MSG) {
857
						if (strtolower(substr($attach->displayname, -4)) != '.eml') {
858
							$attach->displayname .= '.eml';
859
						}
860
					}
861
					// android devices require attachment size in order to display an attachment properly
862
					if (!isset($attachprops[PR_ATTACH_SIZE])) {
863
						$stream = mapi_openproperty($mapiattach, PR_ATTACH_DATA_BIN, IID_IStream, 0, 0);
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_DATA_BIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
864
						// It's not possible to open some (embedded only?) messages, so we need to open the attachment object itself to get the data
865
						if (mapi_last_hresult()) {
866
							$embMessage = mapi_attach_openobj($mapiattach);
867
							$addrbook = $this->getAddressbook();
868
							$stream = mapi_inetmapi_imtoinet($this->session, $addrbook, $embMessage, ['use_tnef' => -1]);
869
						}
870
						$stat = mapi_stream_stat($stream);
871
						$attach->estimatedDataSize = $stat['cb'];
0 ignored issues
show
Bug introduced by
The property estimatedDataSize does not seem to exist on SyncAttachment.
Loading history...
872
					}
873
					else {
874
						$attach->estimatedDataSize = $attachprops[PR_ATTACH_SIZE];
875
					}
876
877
					if (isset($attachprops[PR_ATTACH_CONTENT_ID]) && $attachprops[PR_ATTACH_CONTENT_ID]) {
878
						$attach->contentid = $attachprops[PR_ATTACH_CONTENT_ID];
0 ignored issues
show
Bug introduced by
The property contentid does not exist on SyncAttachment. Did you mean content?
Loading history...
879
					}
880
881
					if (!isset($attach->contentid) && isset($attachprops[PR_ATTACH_CONTENT_ID_A]) && $attachprops[PR_ATTACH_CONTENT_ID_A]) {
882
						$attach->contentid = $attachprops[PR_ATTACH_CONTENT_ID_A];
883
					}
884
885
					if (isset($attachprops[PR_ATTACHMENT_HIDDEN]) && $attachprops[PR_ATTACHMENT_HIDDEN]) {
886
						$attach->isinline = 1;
0 ignored issues
show
Bug introduced by
The property isinline does not seem to exist on SyncAttachment.
Loading history...
887
					}
888
889
					if (isset($attach->contentid, $attachprops[PR_ATTACH_FLAGS]) && $attachprops[PR_ATTACH_FLAGS] & 4) {
890
						$attach->isinline = 1;
891
					}
892
893
					if (!isset($message->asattachments)) {
894
						$message->asattachments = [];
895
					}
896
897
					array_push($message->asattachments, $attach);
898
				}
899
				else {
900
					$attach->attsize = $attachprops[PR_ATTACH_SIZE];
0 ignored issues
show
Bug introduced by
The property attsize does not seem to exist on SyncBaseAttachment.
Loading history...
901
					$attach->attname = sprintf("%s:%s:%s", $entryid, $row[PR_ATTACH_NUM], $parentSourcekey);
0 ignored issues
show
Bug introduced by
The property attname does not seem to exist on SyncBaseAttachment.
Loading history...
902
					if (!isset($message->attachments)) {
903
						$message->attachments = [];
904
					}
905
906
					array_push($message->attachments, $attach);
907
				}
908
			}
909
		}
910
911
		// Get To/Cc as SMTP addresses (this is different from displayto and displaycc because we are putting
912
		// in the SMTP addresses as well, while displayto and displaycc could just contain the display names
913
		$message->to = [];
914
		$message->cc = [];
915
916
		$reciptable = mapi_message_getrecipienttable($mapimessage);
917
		$rows = mapi_table_queryallrows($reciptable, [PR_RECIPIENT_TYPE, PR_DISPLAY_NAME, PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS, PR_ENTRYID, PR_SEARCH_KEY]);
0 ignored issues
show
Bug introduced by
The constant PR_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_RECIPIENT_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SMTP_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
918
919
		foreach ($rows as $row) {
920
			$address = "";
921
			$fulladdr = "";
922
923
			$addrtype = isset($row[PR_ADDRTYPE]) ? $row[PR_ADDRTYPE] : "";
924
925
			if (isset($row[PR_SMTP_ADDRESS])) {
926
				$address = $row[PR_SMTP_ADDRESS];
927
			}
928
			elseif ($addrtype == "SMTP" && isset($row[PR_EMAIL_ADDRESS])) {
929
				$address = $row[PR_EMAIL_ADDRESS];
930
			}
931
			elseif ($addrtype == "ZARAFA" && isset($row[PR_ENTRYID])) {
932
				$address = $this->getSMTPAddressFromEntryID($row[PR_ENTRYID]);
933
			}
934
935
			// if the user was not found, do a fallback to PR_SEARCH_KEY
936
			// @see https://jira.z-hub.io/browse/ZP-1178
937
			if (empty($address) && isset($row[PR_SEARCH_KEY])) {
938
				$address = $this->getEmailAddressFromSearchKey($row[PR_SEARCH_KEY]);
939
			}
940
941
			$name = isset($row[PR_DISPLAY_NAME]) ? $row[PR_DISPLAY_NAME] : "";
942
943
			if ($name == "" || $name == $address) {
944
				$fulladdr = w2u($address);
945
			}
946
			else {
947
				if (substr($name, 0, 1) != '"' && substr($name, -1) != '"') {
948
					$fulladdr = "\"" . w2u($name) . "\" <" . w2u($address) . ">";
0 ignored issues
show
Bug introduced by
Are you sure w2u($address) of type false|mixed|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

948
					$fulladdr = "\"" . w2u($name) . "\" <" . /** @scrutinizer ignore-type */ w2u($address) . ">";
Loading history...
Bug introduced by
Are you sure w2u($name) of type false|mixed|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

948
					$fulladdr = "\"" . /** @scrutinizer ignore-type */ w2u($name) . "\" <" . w2u($address) . ">";
Loading history...
949
				}
950
				else {
951
					$fulladdr = w2u($name) . "<" . w2u($address) . ">";
952
				}
953
			}
954
955
			if ($row[PR_RECIPIENT_TYPE] == MAPI_TO) {
956
				array_push($message->to, $fulladdr);
957
			}
958
			elseif ($row[PR_RECIPIENT_TYPE] == MAPI_CC) {
959
				array_push($message->cc, $fulladdr);
960
			}
961
		}
962
963
		if (is_array($message->to) && !empty($message->to)) {
964
			$message->to = implode(", ", $message->to);
965
		}
966
		if (is_array($message->cc) && !empty($message->cc)) {
967
			$message->cc = implode(", ", $message->cc);
968
		}
969
970
		// without importance some mobiles assume "0" (low) - Mantis #439
971
		if (!isset($message->importance)) {
972
			$message->importance = IMPORTANCE_NORMAL;
973
		}
974
975
		if (!isset($message->internetcpid)) {
976
			$message->internetcpid = (defined('STORE_INTERNET_CPID')) ? constant('STORE_INTERNET_CPID') : INTERNET_CPID_WINDOWS1252;
977
		}
978
		$this->setFlag($mapimessage, $message);
979
		// TODO checkcontentclass
980
		if (!isset($message->contentclass)) {
981
			$message->contentclass = DEFAULT_EMAIL_CONTENTCLASS;
982
		}
983
984
		if (!isset($message->nativebodytype)) {
985
			$message->nativebodytype = MAPIUtils::GetNativeBodyType($messageprops);
986
		}
987
		elseif ($message->nativebodytype == SYNC_BODYPREFERENCE_UNDEFINED) {
988
			$nbt = MAPIUtils::GetNativeBodyType($messageprops);
989
			SLog::Write(LOGLEVEL_INFO, sprintf("MAPIProvider->getEmail(): native body type is undefined. Set it to %d.", $nbt));
990
			$message->nativebodytype = $nbt;
991
		}
992
993
		// reply, reply to all, forward flags
994
		if (isset($message->lastverbexecuted) && $message->lastverbexecuted) {
995
			$message->lastverbexecuted = Utils::GetLastVerbExecuted($message->lastverbexecuted);
996
		}
997
998
		return $message;
999
	}
1000
1001
	/**
1002
	 * Reads a note object from MAPI.
1003
	 *
1004
	 * @param mixed             $mapimessage
1005
	 * @param ContentParameters $contentparameters
1006
	 *
1007
	 * @return SyncNote
1008
	 */
1009
	private function getNote($mapimessage, $contentparameters) {
1010
		$message = new SyncNote();
1011
1012
		// Standard one-to-one mappings first
1013
		$this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetNoteMapping());
1014
1015
		// set the body according to contentparameters and supported AS version
1016
		$this->setMessageBody($mapimessage, $contentparameters, $message);
1017
1018
		return $message;
1019
	}
1020
1021
	/**
1022
	 * Creates a SyncFolder from MAPI properties.
1023
	 *
1024
	 * @param mixed $folderprops
1025
	 *
1026
	 * @return SyncFolder
1027
	 */
1028
	public function GetFolder($folderprops) {
1029
		$folder = new SyncFolder();
1030
1031
		$storeprops = $this->GetStoreProps();
1032
1033
		// For ZCP 7.0.x we need to retrieve more properties explicitly, see ZP-780
1034
		if (isset($folderprops[PR_SOURCE_KEY]) && !isset($folderprops[PR_ENTRYID]) && !isset($folderprops[PR_CONTAINER_CLASS])) {
0 ignored issues
show
Bug introduced by
The constant PR_SOURCE_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_CONTAINER_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1035
			$entryid = mapi_msgstore_entryidfromsourcekey($this->store, $folderprops[PR_SOURCE_KEY]);
1036
			$mapifolder = mapi_msgstore_openentry($this->store, $entryid);
1037
			$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]);
0 ignored issues
show
Bug introduced by
The constant PR_PARENT_SOURCE_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTR_HIDDEN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EXTENDED_FOLDER_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_PARENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1038
			SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->GetFolder(): received insufficient of data from ICS. Fetching required data.");
1039
		}
1040
1041
		if (!isset(
1042
				$folderprops[PR_DISPLAY_NAME],
1043
				$folderprops[PR_PARENT_ENTRYID],
1044
				$folderprops[PR_SOURCE_KEY],
1045
				$folderprops[PR_ENTRYID],
1046
				$folderprops[PR_PARENT_SOURCE_KEY],
1047
				$storeprops[PR_IPM_SUBTREE_ENTRYID])) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_SUBTREE_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1048
			SLog::Write(LOGLEVEL_ERROR, "MAPIProvider->GetFolder(): invalid folder. Missing properties");
1049
1050
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type SyncFolder.
Loading history...
1051
		}
1052
1053
		// ignore hidden folders
1054
		if (isset($folderprops[PR_ATTR_HIDDEN]) && $folderprops[PR_ATTR_HIDDEN] != false) {
1055
			SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->GetFolder(): invalid folder '%s' as it is a hidden folder (PR_ATTR_HIDDEN)", $folderprops[PR_DISPLAY_NAME]));
1056
1057
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type SyncFolder.
Loading history...
1058
		}
1059
1060
		// ignore certain undesired folders, like "RSS Feeds" and "Suggested contacts"
1061
		if ((isset($folderprops[PR_CONTAINER_CLASS]) && $folderprops[PR_CONTAINER_CLASS] == "IPF.Note.OutlookHomepage") ||
1062
				in_array($folderprops[PR_ENTRYID], $this->getSpecialFoldersData())) {
1063
			SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->GetFolder(): folder '%s' should not be synchronized", $folderprops[PR_DISPLAY_NAME]));
1064
1065
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type SyncFolder.
Loading history...
1066
		}
1067
1068
		$folder->BackendId = bin2hex($folderprops[PR_SOURCE_KEY]);
1069
		$folderOrigin = DeviceManager::FLD_ORIGIN_USER;
1070
		if (GSync::GetBackend()->GetImpersonatedUser()) {
1071
			$folderOrigin = DeviceManager::FLD_ORIGIN_IMPERSONATED;
1072
		}
1073
		$folder->serverid = GSync::GetDeviceManager()->GetFolderIdForBackendId($folder->BackendId, true, $folderOrigin, $folderprops[PR_DISPLAY_NAME]);
1074
		if ($folderprops[PR_PARENT_ENTRYID] == $storeprops[PR_IPM_SUBTREE_ENTRYID] || $folderprops[PR_PARENT_ENTRYID] == $storeprops[PR_IPM_PUBLIC_FOLDERS_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_PUBLIC_FOLDERS_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1075
			$folder->parentid = "0";
1076
		}
1077
		else {
1078
			$folder->parentid = GSync::GetDeviceManager()->GetFolderIdForBackendId(bin2hex($folderprops[PR_PARENT_SOURCE_KEY]));
1079
		}
1080
		$folder->displayname = w2u($folderprops[PR_DISPLAY_NAME]);
1081
		$folder->type = $this->GetFolderType($folderprops[PR_ENTRYID], isset($folderprops[PR_CONTAINER_CLASS]) ? $folderprops[PR_CONTAINER_CLASS] : false);
0 ignored issues
show
Bug introduced by
It seems like IssetNode ? $folderprops...ONTAINER_CLASS] : false can also be of type false; however, parameter $class of MAPIProvider::GetFolderType() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1081
		$folder->type = $this->GetFolderType($folderprops[PR_ENTRYID], /** @scrutinizer ignore-type */ isset($folderprops[PR_CONTAINER_CLASS]) ? $folderprops[PR_CONTAINER_CLASS] : false);
Loading history...
1082
1083
		return $folder;
1084
	}
1085
1086
	/**
1087
	 * Returns the foldertype for an entryid
1088
	 * Gets the folder type by checking the default folders in MAPI.
1089
	 *
1090
	 * @param string $entryid
1091
	 * @param string $class   (opt)
1092
	 *
1093
	 * @return long
0 ignored issues
show
Bug introduced by
The type long was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1094
	 */
1095
	public function GetFolderType($entryid, $class = false) {
1096
		$storeprops = $this->GetStoreProps();
1097
		$inboxprops = $this->GetInboxProps();
1098
1099
		if ($entryid == $storeprops[PR_IPM_WASTEBASKET_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_WASTEBASKET_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1100
			return SYNC_FOLDER_TYPE_WASTEBASKET;
0 ignored issues
show
Bug Best Practice introduced by
The expression return SYNC_FOLDER_TYPE_WASTEBASKET returns the type integer which is incompatible with the documented return type long.
Loading history...
1101
		}
1102
		if ($entryid == $storeprops[PR_IPM_SENTMAIL_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_SENTMAIL_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1103
			return SYNC_FOLDER_TYPE_SENTMAIL;
0 ignored issues
show
Bug Best Practice introduced by
The expression return SYNC_FOLDER_TYPE_SENTMAIL returns the type integer which is incompatible with the documented return type long.
Loading history...
1104
		}
1105
		if ($entryid == $storeprops[PR_IPM_OUTBOX_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_OUTBOX_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1106
			return SYNC_FOLDER_TYPE_OUTBOX;
0 ignored issues
show
Bug Best Practice introduced by
The expression return SYNC_FOLDER_TYPE_OUTBOX returns the type integer which is incompatible with the documented return type long.
Loading history...
1107
		}
1108
1109
		// Public folders do not have inboxprops
1110
		// @see https://jira.z-hub.io/browse/ZP-995
1111
		if (!empty($inboxprops)) {
1112
			if ($entryid == $inboxprops[PR_ENTRYID]) {
1113
				return SYNC_FOLDER_TYPE_INBOX;
0 ignored issues
show
Bug Best Practice introduced by
The expression return SYNC_FOLDER_TYPE_INBOX returns the type integer which is incompatible with the documented return type long.
Loading history...
1114
			}
1115
			if ($entryid == $inboxprops[PR_IPM_DRAFTS_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_DRAFTS_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1116
				return SYNC_FOLDER_TYPE_DRAFTS;
0 ignored issues
show
Bug Best Practice introduced by
The expression return SYNC_FOLDER_TYPE_DRAFTS returns the type integer which is incompatible with the documented return type long.
Loading history...
1117
			}
1118
			if ($entryid == $inboxprops[PR_IPM_TASK_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_TASK_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1119
				return SYNC_FOLDER_TYPE_TASK;
0 ignored issues
show
Bug Best Practice introduced by
The expression return SYNC_FOLDER_TYPE_TASK returns the type integer which is incompatible with the documented return type long.
Loading history...
1120
			}
1121
			if ($entryid == $inboxprops[PR_IPM_APPOINTMENT_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_APPOINTMENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1122
				return SYNC_FOLDER_TYPE_APPOINTMENT;
0 ignored issues
show
Bug Best Practice introduced by
The expression return SYNC_FOLDER_TYPE_APPOINTMENT returns the type integer which is incompatible with the documented return type long.
Loading history...
1123
			}
1124
			if ($entryid == $inboxprops[PR_IPM_CONTACT_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_CONTACT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1125
				return SYNC_FOLDER_TYPE_CONTACT;
0 ignored issues
show
Bug Best Practice introduced by
The expression return SYNC_FOLDER_TYPE_CONTACT returns the type integer which is incompatible with the documented return type long.
Loading history...
1126
			}
1127
			if ($entryid == $inboxprops[PR_IPM_NOTE_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_NOTE_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1128
				return SYNC_FOLDER_TYPE_NOTE;
0 ignored issues
show
Bug Best Practice introduced by
The expression return SYNC_FOLDER_TYPE_NOTE returns the type integer which is incompatible with the documented return type long.
Loading history...
1129
			}
1130
			if ($entryid == $inboxprops[PR_IPM_JOURNAL_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_JOURNAL_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1131
				return SYNC_FOLDER_TYPE_JOURNAL;
0 ignored issues
show
Bug Best Practice introduced by
The expression return SYNC_FOLDER_TYPE_JOURNAL returns the type integer which is incompatible with the documented return type long.
Loading history...
1132
			}
1133
		}
1134
1135
		// user created folders
1136
		if ($class == "IPF.Note") {
1137
			return SYNC_FOLDER_TYPE_USER_MAIL;
0 ignored issues
show
Bug Best Practice introduced by
The expression return SYNC_FOLDER_TYPE_USER_MAIL returns the type integer which is incompatible with the documented return type long.
Loading history...
1138
		}
1139
		if ($class == "IPF.Task") {
1140
			return SYNC_FOLDER_TYPE_USER_TASK;
0 ignored issues
show
Bug Best Practice introduced by
The expression return SYNC_FOLDER_TYPE_USER_TASK returns the type integer which is incompatible with the documented return type long.
Loading history...
1141
		}
1142
		if ($class == "IPF.Appointment") {
1143
			return SYNC_FOLDER_TYPE_USER_APPOINTMENT;
0 ignored issues
show
Bug Best Practice introduced by
The expression return SYNC_FOLDER_TYPE_USER_APPOINTMENT returns the type integer which is incompatible with the documented return type long.
Loading history...
1144
		}
1145
		if ($class == "IPF.Contact") {
1146
			return SYNC_FOLDER_TYPE_USER_CONTACT;
0 ignored issues
show
Bug Best Practice introduced by
The expression return SYNC_FOLDER_TYPE_USER_CONTACT returns the type integer which is incompatible with the documented return type long.
Loading history...
1147
		}
1148
		if ($class == "IPF.StickyNote") {
1149
			return SYNC_FOLDER_TYPE_USER_NOTE;
0 ignored issues
show
Bug Best Practice introduced by
The expression return SYNC_FOLDER_TYPE_USER_NOTE returns the type integer which is incompatible with the documented return type long.
Loading history...
1150
		}
1151
		if ($class == "IPF.Journal") {
1152
			return SYNC_FOLDER_TYPE_USER_JOURNAL;
0 ignored issues
show
Bug Best Practice introduced by
The expression return SYNC_FOLDER_TYPE_USER_JOURNAL returns the type integer which is incompatible with the documented return type long.
Loading history...
1153
		}
1154
1155
		return SYNC_FOLDER_TYPE_OTHER;
0 ignored issues
show
Bug Best Practice introduced by
The expression return SYNC_FOLDER_TYPE_OTHER returns the type integer which is incompatible with the documented return type long.
Loading history...
1156
	}
1157
1158
	/**
1159
	 * Indicates if the entry id is a default MAPI folder.
1160
	 *
1161
	 * @param string $entryid
1162
	 *
1163
	 * @return bool
1164
	 */
1165
	public function IsMAPIDefaultFolder($entryid) {
1166
		$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]);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_OUTBOX_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_PUBLIC_FOLDERS_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_MAILBOX_OWNER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_SENTMAIL_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_WASTEBASKET_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_FAVORITES_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_SUBTREE_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_MDB_PROVIDER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1167
1168
		$inboxProps = [];
1169
		$inbox = mapi_msgstore_getreceivefolder($this->store);
1170
		if (!mapi_last_hresult()) {
1171
			$inboxProps = mapi_getprops($inbox, [PR_ENTRYID]);
1172
		}
1173
1174
		$root = mapi_msgstore_openentry($this->store, null); // TODO use getRootProps()
1175
		$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]);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_APPOINTMENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_NOTE_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_CONTACT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_JOURNAL_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ADDITIONAL_REN_ENTRYIDS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_TASK_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_DRAFTS_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1176
1177
		$additional_ren_entryids = [];
1178
		if (isset($rootProps[PR_ADDITIONAL_REN_ENTRYIDS])) {
1179
			$additional_ren_entryids = $rootProps[PR_ADDITIONAL_REN_ENTRYIDS];
1180
		}
1181
1182
		$defaultfolders = [
1183
			"inbox" => ["inbox" => PR_ENTRYID],
1184
			"outbox" => ["store" => PR_IPM_OUTBOX_ENTRYID],
1185
			"sent" => ["store" => PR_IPM_SENTMAIL_ENTRYID],
1186
			"wastebasket" => ["store" => PR_IPM_WASTEBASKET_ENTRYID],
1187
			"favorites" => ["store" => PR_IPM_FAVORITES_ENTRYID],
1188
			"publicfolders" => ["store" => PR_IPM_PUBLIC_FOLDERS_ENTRYID],
1189
			"calendar" => ["root" => PR_IPM_APPOINTMENT_ENTRYID],
1190
			"contact" => ["root" => PR_IPM_CONTACT_ENTRYID],
1191
			"drafts" => ["root" => PR_IPM_DRAFTS_ENTRYID],
1192
			"journal" => ["root" => PR_IPM_JOURNAL_ENTRYID],
1193
			"note" => ["root" => PR_IPM_NOTE_ENTRYID],
1194
			"task" => ["root" => PR_IPM_TASK_ENTRYID],
1195
			"junk" => ["additional" => 4],
1196
			"syncissues" => ["additional" => 1],
1197
			"conflicts" => ["additional" => 0],
1198
			"localfailures" => ["additional" => 2],
1199
			"serverfailures" => ["additional" => 3],
1200
		];
1201
1202
		foreach ($defaultfolders as $key => $prop) {
1203
			$tag = reset($prop);
1204
			$from = key($prop);
1205
1206
			switch ($from) {
1207
				case "inbox":
1208
					if (isset($inboxProps[$tag]) && $entryid == $inboxProps[$tag]) {
1209
						SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Inbox found, key '%s'", $key));
1210
1211
						return true;
1212
					}
1213
					break;
1214
1215
				case "store":
1216
					if (isset($msgstore_props[$tag]) && $entryid == $msgstore_props[$tag]) {
1217
						SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Store folder found, key '%s'", $key));
1218
1219
						return true;
1220
					}
1221
					break;
1222
1223
				case "root":
1224
					if (isset($rootProps[$tag]) && $entryid == $rootProps[$tag]) {
1225
						SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Root folder found, key '%s'", $key));
1226
1227
						return true;
1228
					}
1229
					break;
1230
1231
				case "additional":
1232
					if (isset($additional_ren_entryids[$tag]) && $entryid == $additional_ren_entryids[$tag]) {
1233
						SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Additional folder found, key '%s'", $key));
1234
1235
						return true;
1236
					}
1237
					break;
1238
			}
1239
		}
1240
1241
		return false;
1242
	}
1243
1244
	/*----------------------------------------------------------------------------------------------------------
1245
	 * SETTER
1246
	 */
1247
1248
	/**
1249
	 * Writes a SyncObject to MAPI
1250
	 * Depending on the message class, a contact, appointment, task or email is written.
1251
	 *
1252
	 * @param mixed      $mapimessage
1253
	 * @param SyncObject $message
1254
	 *
1255
	 * @return bool
1256
	 */
1257
	public function SetMessage($mapimessage, $message) {
1258
		// TODO check with instanceof
1259
		switch (strtolower(get_class($message))) {
1260
			case "synccontact":
1261
				return $this->setContact($mapimessage, $message);
1262
1263
			case "syncappointment":
1264
				return $this->setAppointment($mapimessage, $message);
1265
1266
			case "synctask":
1267
				return $this->setTask($mapimessage, $message);
1268
1269
			case "syncnote":
1270
				return $this->setNote($mapimessage, $message);
1271
1272
			default:
1273
				// for emails only flag (read and todo) changes are possible
1274
				return $this->setEmail($mapimessage, $message);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->setEmail($mapimessage, $message) targeting MAPIProvider::setEmail() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1275
		}
1276
	}
1277
1278
	/**
1279
	 * Writes SyncMail to MAPI (actually flags only).
1280
	 *
1281
	 * @param mixed    $mapimessage
1282
	 * @param SyncMail $message
1283
	 */
1284
	private function setEmail($mapimessage, $message) {
1285
		// update categories
1286
		if (!isset($message->categories)) {
1287
			$message->categories = [];
1288
		}
1289
		$emailmap = MAPIMapping::GetEmailMapping();
1290
		$this->setPropsInMAPI($mapimessage, $message, ["categories" => $emailmap["categories"]]);
1291
1292
		$flagmapping = MAPIMapping::GetMailFlagsMapping();
1293
		$flagprops = MAPIMapping::GetMailFlagsProperties();
1294
		$flagprops = array_merge($this->getPropIdsFromStrings($flagmapping), $this->getPropIdsFromStrings($flagprops));
1295
		// flag specific properties to be set
1296
		$props = $delprops = [];
1297
		// unset message flags if:
1298
		// flag is not set
1299
		if (empty($message->flag) ||
1300
			// flag status is not set
1301
			!isset($message->flag->flagstatus) ||
1302
			// flag status is 0 or empty
1303
			(isset($message->flag->flagstatus) && ($message->flag->flagstatus == 0 || $message->flag->flagstatus == ""))) {
1304
			// if message flag is empty, some properties need to be deleted
1305
			// and some set to 0 or false
1306
1307
			$props[$flagprops["todoitemsflags"]] = 0;
1308
			$props[$flagprops["status"]] = 0;
1309
			$props[$flagprops["completion"]] = 0.0;
1310
			$props[$flagprops["flagtype"]] = "";
1311
			$props[$flagprops["ordinaldate"]] = 0x7FFFFFFF; // ordinal date is 12am 1.1.4501, set it to max possible value
1312
			$props[$flagprops["subordinaldate"]] = "";
1313
			$props[$flagprops["replyrequested"]] = false;
1314
			$props[$flagprops["responserequested"]] = false;
1315
			$props[$flagprops["reminderset"]] = false;
1316
			$props[$flagprops["complete"]] = false;
1317
1318
			$delprops[] = $flagprops["todotitle"];
1319
			$delprops[] = $flagprops["duedate"];
1320
			$delprops[] = $flagprops["startdate"];
1321
			$delprops[] = $flagprops["datecompleted"];
1322
			$delprops[] = $flagprops["utcstartdate"];
1323
			$delprops[] = $flagprops["utcduedate"];
1324
			$delprops[] = $flagprops["completetime"];
1325
			$delprops[] = $flagprops["flagstatus"];
1326
			$delprops[] = $flagprops["flagicon"];
1327
		}
1328
		else {
1329
			$this->setPropsInMAPI($mapimessage, $message->flag, $flagmapping);
1330
			$props[$flagprops["todoitemsflags"]] = 1;
1331
			if (isset($message->subject) && strlen($message->subject) > 0) {
1332
				$props[$flagprops["todotitle"]] = $message->subject;
1333
			}
1334
			// ordinal date is utc current time
1335
			if (!isset($message->flag->ordinaldate) || empty($message->flag->ordinaldate)) {
1336
				$props[$flagprops["ordinaldate"]] = time();
1337
			}
1338
			// the default value
1339
			if (!isset($message->flag->subordinaldate) || empty($message->flag->subordinaldate)) {
1340
				$props[$flagprops["subordinaldate"]] = "5555555";
1341
			}
1342
			$props[$flagprops["flagicon"]] = 6; // red flag icon
1343
			$props[$flagprops["replyrequested"]] = true;
1344
			$props[$flagprops["responserequested"]] = true;
1345
1346
			if ($message->flag->flagstatus == SYNC_FLAGSTATUS_COMPLETE) {
1347
				$props[$flagprops["status"]] = olTaskComplete;
1348
				$props[$flagprops["completion"]] = 1.0;
1349
				$props[$flagprops["complete"]] = true;
1350
				$props[$flagprops["replyrequested"]] = false;
1351
				$props[$flagprops["responserequested"]] = false;
1352
				unset($props[$flagprops["flagicon"]]);
1353
				$delprops[] = $flagprops["flagicon"];
1354
			}
1355
		}
1356
1357
		if (!empty($props)) {
1358
			mapi_setprops($mapimessage, $props);
1359
		}
1360
		if (!empty($delprops)) {
1361
			mapi_deleteprops($mapimessage, $delprops);
1362
		}
1363
	}
1364
1365
	/**
1366
	 * Writes a SyncAppointment to MAPI.
1367
	 *
1368
	 * @param mixed           $mapimessage
1369
	 * @param SyncAppointment $message
1370
	 * @param mixed           $appointment
1371
	 *
1372
	 * @return bool
1373
	 */
1374
	private function setAppointment($mapimessage, $appointment) {
1375
		// Get timezone info
1376
		if (isset($appointment->timezone)) {
1377
			$tz = $this->getTZFromSyncBlob(base64_decode($appointment->timezone));
1378
		}
1379
		else {
1380
			$tz = false;
1381
		}
1382
1383
		// 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
1384
		if (!isset($appointment->starttime) || !isset($appointment->endtime)) {
1385
			$amapping = MAPIMapping::GetAppointmentMapping();
1386
			$amapping = $this->getPropIdsFromStrings($amapping);
1387
			$existingstartendpropsmap = [$amapping["starttime"], $amapping["endtime"]];
1388
			$existingstartendprops = $this->getProps($mapimessage, $existingstartendpropsmap);
1389
1390
			if (isset($existingstartendprops[$amapping["starttime"]]) && !isset($appointment->starttime)) {
1391
				$appointment->starttime = $existingstartendprops[$amapping["starttime"]];
1392
				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)));
1393
			}
1394
			if (isset($existingstartendprops[$amapping["endtime"]]) && !isset($appointment->endtime)) {
1395
				$appointment->endtime = $existingstartendprops[$amapping["endtime"]];
1396
				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)));
1397
			}
1398
		}
1399
		if (!isset($appointment->starttime) || !isset($appointment->endtime)) {
1400
			throw new StatusException("MAPIProvider->setAppointment(): Error, start and/or end time not set and can not be retrieved from MAPI.", SYNC_STATUS_SYNCCANNOTBECOMPLETED);
1401
		}
1402
1403
		// calculate duration because without it some webaccess views are broken. duration is in min
1404
		$localstart = $this->getLocaltimeByTZ($appointment->starttime, $tz);
0 ignored issues
show
Bug introduced by
It seems like $tz can also be of type false; however, parameter $tz of MAPIProvider::getLocaltimeByTZ() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1404
		$localstart = $this->getLocaltimeByTZ($appointment->starttime, /** @scrutinizer ignore-type */ $tz);
Loading history...
1405
		$localend = $this->getLocaltimeByTZ($appointment->endtime, $tz);
1406
		$duration = ($localend - $localstart) / 60;
1407
1408
		// nokia sends an yearly event with 0 mins duration but as all day event,
1409
		// so make it end next day
1410
		if ($appointment->starttime == $appointment->endtime && isset($appointment->alldayevent) && $appointment->alldayevent) {
1411
			$duration = 1440;
1412
			$appointment->endtime = $appointment->starttime + 24 * 60 * 60;
1413
			$localend = $localstart + 24 * 60 * 60;
1414
		}
1415
1416
		// is the transmitted UID OL compatible?
1417
		// if not, encapsulate the transmitted uid
1418
		$appointment->uid = Utils::GetOLUidFromICalUid($appointment->uid);
1419
1420
		mapi_setprops($mapimessage, [PR_MESSAGE_CLASS => "IPM.Appointment"]);
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1421
1422
		$appointmentmapping = MAPIMapping::GetAppointmentMapping();
1423
		$this->setPropsInMAPI($mapimessage, $appointment, $appointmentmapping);
1424
		$appointmentprops = MAPIMapping::GetAppointmentProperties();
1425
		$appointmentprops = array_merge($this->getPropIdsFromStrings($appointmentmapping), $this->getPropIdsFromStrings($appointmentprops));
1426
		// appointment specific properties to be set
1427
		$props = [];
1428
1429
		// sensitivity is not enough to mark an appointment as private, so we use another mapi tag
1430
		$private = (isset($appointment->sensitivity) && $appointment->sensitivity >= SENSITIVITY_PRIVATE) ? true : false;
1431
1432
		// Set commonstart/commonend to start/end and remindertime to start, duration, private and cleanGlobalObjectId
1433
		$props[$appointmentprops["commonstart"]] = $appointment->starttime;
1434
		$props[$appointmentprops["commonend"]] = $appointment->endtime;
1435
		$props[$appointmentprops["reminderstart"]] = $appointment->starttime;
1436
		// Set reminder boolean to 'true' if reminder is set
1437
		$props[$appointmentprops["reminderset"]] = isset($appointment->reminder) ? true : false;
1438
		$props[$appointmentprops["duration"]] = $duration;
1439
		$props[$appointmentprops["private"]] = $private;
1440
		$props[$appointmentprops["uid"]] = $appointment->uid;
1441
		// Set named prop 8510, unknown property, but enables deleting a single occurrence of a recurring
1442
		// type in OLK2003.
1443
		$props[$appointmentprops["sideeffects"]] = 369;
1444
1445
		if (isset($appointment->reminder) && $appointment->reminder >= 0) {
1446
			// Set 'flagdueby' to correct value (start - reminderminutes)
1447
			$props[$appointmentprops["flagdueby"]] = $appointment->starttime - $appointment->reminder * 60;
1448
			$props[$appointmentprops["remindertime"]] = $appointment->reminder;
1449
		}
1450
		// unset the reminder
1451
		else {
1452
			$props[$appointmentprops["reminderset"]] = false;
1453
		}
1454
1455
		if (isset($appointment->asbody)) {
1456
			$this->setASbody($appointment->asbody, $props, $appointmentprops);
1457
		}
1458
1459
		if ($tz !== false) {
1460
			$props[$appointmentprops["timezonetag"]] = $this->getMAPIBlobFromTZ($tz);
1461
		}
1462
1463
		if (isset($appointment->recurrence)) {
1464
			// Set PR_ICON_INDEX to 1025 to show correct icon in category view
1465
			$props[$appointmentprops["icon"]] = 1025;
1466
1467
			// if there aren't any exceptions, use the 'old style' set recurrence
1468
			$noexceptions = true;
1469
1470
			$recurrence = new Recurrence($this->store, $mapimessage);
1471
			$recur = [];
1472
			$this->setRecurrence($appointment, $recur);
1473
1474
			// set the recurrence type to that of the MAPI
1475
			$props[$appointmentprops["recurrencetype"]] = $recur["recurrencetype"];
1476
1477
			$starttime = $this->gmtime($localstart);
1478
			$endtime = $this->gmtime($localend);
0 ignored issues
show
Bug introduced by
It seems like $localend can also be of type integer; however, parameter $time of MAPIProvider::gmtime() does only seem to accept long, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1478
			$endtime = $this->gmtime(/** @scrutinizer ignore-type */ $localend);
Loading history...
Unused Code introduced by
The assignment to $endtime is dead and can be removed.
Loading history...
1479
1480
			// set recurrence start here because it's calculated differently for tasks and appointments
1481
			$recur["start"] = $this->getDayStartOfTimestamp($this->getGMTTimeByTZ($localstart, $tz));
0 ignored issues
show
Bug introduced by
It seems like $tz can also be of type false; however, parameter $tz of MAPIProvider::getGMTTimeByTZ() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1481
			$recur["start"] = $this->getDayStartOfTimestamp($this->getGMTTimeByTZ($localstart, /** @scrutinizer ignore-type */ $tz));
Loading history...
1482
1483
			$recur["startocc"] = $starttime["tm_hour"] * 60 + $starttime["tm_min"];
1484
			$recur["endocc"] = $recur["startocc"] + $duration; // Note that this may be > 24*60 if multi-day
1485
1486
			// only tasks can regenerate
1487
			$recur["regen"] = false;
1488
1489
			// Process exceptions. The PDA will send all exceptions for this recurring item.
1490
			if (isset($appointment->exceptions)) {
1491
				foreach ($appointment->exceptions as $exception) {
1492
					// we always need the base date
1493
					if (!isset($exception->exceptionstarttime)) {
1494
						continue;
1495
					}
1496
1497
					$basedate = $this->getDayStartOfTimestamp($exception->exceptionstarttime);
1498
					if (isset($exception->deleted) && $exception->deleted) {
1499
						$noexceptions = false;
1500
						// Delete exception
1501
						$recurrence->createException([], $basedate, true);
1502
					}
1503
					else {
1504
						// Change exception
1505
						$mapiexception = ["basedate" => $basedate];
1506
						// other exception properties which are not handled in recurrence
1507
						$exceptionprops = [];
1508
1509
						if (isset($exception->starttime)) {
1510
							$mapiexception["start"] = $this->getLocaltimeByTZ($exception->starttime, $tz);
1511
							$exceptionprops[$appointmentprops["starttime"]] = $exception->starttime;
1512
						}
1513
						if (isset($exception->endtime)) {
1514
							$mapiexception["end"] = $this->getLocaltimeByTZ($exception->endtime, $tz);
1515
							$exceptionprops[$appointmentprops["endtime"]] = $exception->endtime;
1516
						}
1517
						if (isset($exception->subject)) {
1518
							$exceptionprops[$appointmentprops["subject"]] = $mapiexception["subject"] = u2w($exception->subject);
1519
						}
1520
						if (isset($exception->location)) {
1521
							$exceptionprops[$appointmentprops["location"]] = $mapiexception["location"] = u2w($exception->location);
1522
						}
1523
						if (isset($exception->busystatus)) {
1524
							$exceptionprops[$appointmentprops["busystatus"]] = $mapiexception["busystatus"] = $exception->busystatus;
1525
						}
1526
						if (isset($exception->reminder)) {
1527
							$exceptionprops[$appointmentprops["reminderset"]] = $mapiexception["reminder_set"] = 1;
1528
							$exceptionprops[$appointmentprops["remindertime"]] = $mapiexception["remind_before"] = $exception->reminder;
1529
						}
1530
						if (isset($exception->alldayevent)) {
1531
							$exceptionprops[$appointmentprops["alldayevent"]] = $mapiexception["alldayevent"] = $exception->alldayevent;
1532
						}
1533
1534
						if (!isset($recur["changed_occurrences"])) {
1535
							$recur["changed_occurrences"] = [];
1536
						}
1537
1538
						if (isset($exception->body)) {
1539
							$exceptionprops[$appointmentprops["body"]] = u2w($exception->body);
1540
						}
1541
1542
						if (isset($exception->asbody)) {
1543
							$this->setASbody($exception->asbody, $exceptionprops, $appointmentprops);
1544
							$mapiexception["body"] = $exceptionprops[$appointmentprops["body"]] =
1545
								(isset($exceptionprops[$appointmentprops["body"]])) ? $exceptionprops[$appointmentprops["body"]] :
1546
								((isset($exceptionprops[$appointmentprops["html"]])) ? $exceptionprops[$appointmentprops["html"]] : "");
1547
						}
1548
1549
						array_push($recur["changed_occurrences"], $mapiexception);
1550
1551
						if (!empty($exceptionprops)) {
1552
							$noexceptions = false;
1553
							if ($recurrence->isException($basedate)) {
1554
								$recurrence->modifyException($exceptionprops, $basedate);
1555
							}
1556
							else {
1557
								$recurrence->createException($exceptionprops, $basedate);
1558
							}
1559
						}
1560
					}
1561
				}
1562
			}
1563
1564
			// setRecurrence deletes the attachments from an appointment
1565
			if ($noexceptions) {
1566
				$recurrence->setRecurrence($tz, $recur);
1567
			}
1568
		}
1569
		else {
1570
			$props[$appointmentprops["isrecurring"]] = false;
1571
		}
1572
1573
		// always set the PR_SENT_REPRESENTING_* props so that the attendee status update also works with the webaccess
1574
		$p = [$appointmentprops["representingentryid"], $appointmentprops["representingname"], $appointmentprops["sentrepresentingaddt"],
1575
			$appointmentprops["sentrepresentingemail"], $appointmentprops["sentrepresentinsrchk"], $appointmentprops["responsestatus"], ];
1576
		$representingprops = $this->getProps($mapimessage, $p);
1577
1578
		if (!isset($representingprops[$appointmentprops["representingentryid"]])) {
1579
			// TODO use GetStoreProps
1580
			$storeProps = mapi_getprops($this->store, [PR_MAILBOX_OWNER_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_MAILBOX_OWNER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1581
			$props[$appointmentprops["representingentryid"]] = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
1582
			$displayname = $this->getFullnameFromEntryID($storeProps[PR_MAILBOX_OWNER_ENTRYID]);
1583
1584
			$props[$appointmentprops["representingname"]] = ($displayname !== false) ? $displayname : Request::GetUser();
0 ignored issues
show
introduced by
The condition $displayname !== false is always true.
Loading history...
1585
			$props[$appointmentprops["sentrepresentingemail"]] = Request::GetUser();
1586
			$props[$appointmentprops["sentrepresentingaddt"]] = "ZARAFA";
1587
			$props[$appointmentprops["sentrepresentinsrchk"]] = $props[$appointmentprops["sentrepresentingaddt"]] . ":" . $props[$appointmentprops["sentrepresentingemail"]];
1588
1589
			if (isset($appointment->attendees) && is_array($appointment->attendees) && !empty($appointment->attendees)) {
1590
				$props[$appointmentprops["icon"]] = 1026;
1591
				// the user is the organizer
1592
				// set these properties to show tracking tab in webapp
1593
1594
				$props[$appointmentprops["mrwassent"]] = true;
1595
				$props[$appointmentprops["responsestatus"]] = olResponseOrganized;
1596
				$props[$appointmentprops["meetingstatus"]] = olMeeting;
1597
			}
1598
		}
1599
		// we also have to set the responsestatus and not only meetingstatus, so we use another mapi tag
1600
		if (!isset($props[$appointmentprops["responsestatus"]])) {
1601
			if (isset($appointment->responsetype)) {
1602
				$props[$appointmentprops["responsestatus"]] = $appointment->responsetype;
1603
			}
1604
			// only set responsestatus to none if it is not set on the server
1605
			elseif (!isset($representingprops[$appointmentprops["responsestatus"]])) {
1606
				$props[$appointmentprops["responsestatus"]] = olResponseNone;
1607
			}
1608
		}
1609
1610
		// Do attendees
1611
		if (isset($appointment->attendees) && is_array($appointment->attendees)) {
1612
			$recips = [];
1613
1614
			// Outlook XP requires organizer in the attendee list as well
1615
			$org = [];
1616
			$org[PR_ENTRYID] = isset($representingprops[$appointmentprops["representingentryid"]]) ? $representingprops[$appointmentprops["representingentryid"]] : $props[$appointmentprops["representingentryid"]];
1617
			$org[PR_DISPLAY_NAME] = isset($representingprops[$appointmentprops["representingname"]]) ? $representingprops[$appointmentprops["representingname"]] : $props[$appointmentprops["representingname"]];
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1618
			$org[PR_ADDRTYPE] = isset($representingprops[$appointmentprops["sentrepresentingaddt"]]) ? $representingprops[$appointmentprops["sentrepresentingaddt"]] : $props[$appointmentprops["sentrepresentingaddt"]];
0 ignored issues
show
Bug introduced by
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1619
			$org[PR_SMTP_ADDRESS] = $org[PR_EMAIL_ADDRESS] = isset($representingprops[$appointmentprops["sentrepresentingemail"]]) ? $representingprops[$appointmentprops["sentrepresentingemail"]] : $props[$appointmentprops["sentrepresentingemail"]];
0 ignored issues
show
Bug introduced by
The constant PR_SMTP_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1620
			$org[PR_SEARCH_KEY] = isset($representingprops[$appointmentprops["sentrepresentinsrchk"]]) ? $representingprops[$appointmentprops["sentrepresentinsrchk"]] : $props[$appointmentprops["sentrepresentinsrchk"]];
0 ignored issues
show
Bug introduced by
The constant PR_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1621
			$org[PR_RECIPIENT_FLAGS] = recipOrganizer | recipSendable;
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1622
			$org[PR_RECIPIENT_TYPE] = MAPI_ORIG;
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1623
1624
			array_push($recips, $org);
1625
1626
			// Open address book for user resolve
1627
			$addrbook = $this->getAddressbook();
1628
			foreach ($appointment->attendees as $attendee) {
1629
				$recip = [];
1630
				$recip[PR_EMAIL_ADDRESS] = u2w($attendee->email);
1631
				$recip[PR_SMTP_ADDRESS] = u2w($attendee->email);
1632
1633
				// lookup information in GAB if possible so we have up-to-date name for given address
1634
				$userinfo = [[PR_DISPLAY_NAME => $recip[PR_EMAIL_ADDRESS]]];
1635
				$userinfo = mapi_ab_resolvename($addrbook, $userinfo, EMS_AB_ADDRESS_LOOKUP);
1636
				if (mapi_last_hresult() == NOERROR) {
1637
					$recip[PR_DISPLAY_NAME] = $userinfo[0][PR_DISPLAY_NAME];
1638
					$recip[PR_EMAIL_ADDRESS] = $userinfo[0][PR_EMAIL_ADDRESS];
1639
					$recip[PR_SEARCH_KEY] = $userinfo[0][PR_SEARCH_KEY];
1640
					$recip[PR_ADDRTYPE] = $userinfo[0][PR_ADDRTYPE];
1641
					$recip[PR_ENTRYID] = $userinfo[0][PR_ENTRYID];
1642
					$recip[PR_RECIPIENT_TYPE] = isset($attendee->attendeetype) ? $attendee->attendeetype : MAPI_TO;
1643
					$recip[PR_RECIPIENT_FLAGS] = recipSendable;
1644
					$recip[PR_RECIPIENT_TRACKSTATUS] = isset($attendee->attendeestatus) ? $attendee->attendeestatus : olResponseNone;
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TRACKSTATUS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1645
				}
1646
				else {
1647
					$recip[PR_DISPLAY_NAME] = u2w($attendee->name);
1648
					$recip[PR_SEARCH_KEY] = "SMTP:" . $recip[PR_EMAIL_ADDRESS] . "\0";
1649
					$recip[PR_ADDRTYPE] = "SMTP";
1650
					$recip[PR_RECIPIENT_TYPE] = isset($attendee->attendeetype) ? $attendee->attendeetype : MAPI_TO;
1651
					$recip[PR_ENTRYID] = mapi_createoneoff($recip[PR_DISPLAY_NAME], $recip[PR_ADDRTYPE], $recip[PR_EMAIL_ADDRESS]);
1652
				}
1653
1654
				array_push($recips, $recip);
1655
			}
1656
1657
			mapi_message_modifyrecipients($mapimessage, 0, $recips);
1658
		}
1659
		mapi_setprops($mapimessage, $props);
1660
1661
		return true;
1662
	}
1663
1664
	/**
1665
	 * Writes a SyncContact to MAPI.
1666
	 *
1667
	 * @param mixed       $mapimessage
1668
	 * @param SyncContact $contact
1669
	 *
1670
	 * @return bool
1671
	 */
1672
	private function setContact($mapimessage, $contact) {
1673
		mapi_setprops($mapimessage, [PR_MESSAGE_CLASS => "IPM.Contact"]);
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1674
1675
		// normalize email addresses
1676
		if (isset($contact->email1address) && (($contact->email1address = $this->extractEmailAddress($contact->email1address)) === false)) {
0 ignored issues
show
introduced by
The condition $contact->email1address ...mail1address) === false is always false.
Loading history...
1677
			unset($contact->email1address);
1678
		}
1679
1680
		if (isset($contact->email2address) && (($contact->email2address = $this->extractEmailAddress($contact->email2address)) === false)) {
0 ignored issues
show
introduced by
The condition $contact->email2address ...mail2address) === false is always false.
Loading history...
1681
			unset($contact->email2address);
1682
		}
1683
1684
		if (isset($contact->email3address) && (($contact->email3address = $this->extractEmailAddress($contact->email3address)) === false)) {
0 ignored issues
show
introduced by
The condition $contact->email3address ...mail3address) === false is always false.
Loading history...
1685
			unset($contact->email3address);
1686
		}
1687
1688
		$contactmapping = MAPIMapping::GetContactMapping();
1689
		$contactprops = MAPIMapping::GetContactProperties();
1690
		$this->setPropsInMAPI($mapimessage, $contact, $contactmapping);
1691
1692
		// /set display name from contact's properties
1693
		$cname = $this->composeDisplayName($contact);
1694
1695
		// get contact specific mapi properties and merge them with the AS properties
1696
		$contactprops = array_merge($this->getPropIdsFromStrings($contactmapping), $this->getPropIdsFromStrings($contactprops));
1697
1698
		// contact specific properties to be set
1699
		$props = [];
1700
1701
		// need to be set in order to show contacts properly in outlook and wa
1702
		$nremails = [];
1703
		$abprovidertype = 0;
1704
1705
		if (isset($contact->email1address)) {
1706
			$this->setEmailAddress($contact->email1address, $cname, 1, $props, $contactprops, $nremails, $abprovidertype);
1707
		}
1708
		if (isset($contact->email2address)) {
1709
			$this->setEmailAddress($contact->email2address, $cname, 2, $props, $contactprops, $nremails, $abprovidertype);
1710
		}
1711
		if (isset($contact->email3address)) {
1712
			$this->setEmailAddress($contact->email3address, $cname, 3, $props, $contactprops, $nremails, $abprovidertype);
1713
		}
1714
1715
		$props[$contactprops["addressbooklong"]] = $abprovidertype;
1716
		$props[$contactprops["displayname"]] = $props[$contactprops["subject"]] = $cname;
1717
1718
		// pda multiple e-mail addresses bug fix for the contact
1719
		if (!empty($nremails)) {
1720
			$props[$contactprops["addressbookmv"]] = $nremails;
1721
		}
1722
1723
		// set addresses
1724
		$this->setAddress("home", $contact->homecity, $contact->homecountry, $contact->homepostalcode, $contact->homestate, $contact->homestreet, $props, $contactprops);
1725
		$this->setAddress("business", $contact->businesscity, $contact->businesscountry, $contact->businesspostalcode, $contact->businessstate, $contact->businessstreet, $props, $contactprops);
1726
		$this->setAddress("other", $contact->othercity, $contact->othercountry, $contact->otherpostalcode, $contact->otherstate, $contact->otherstreet, $props, $contactprops);
1727
1728
		// set the mailing address and its type
1729
		if (isset($props[$contactprops["businessaddress"]])) {
1730
			$props[$contactprops["mailingaddress"]] = 2;
1731
			$this->setMailingAddress($contact->businesscity, $contact->businesscountry, $contact->businesspostalcode, $contact->businessstate, $contact->businessstreet, $props[$contactprops["businessaddress"]], $props, $contactprops);
1732
		}
1733
		elseif (isset($props[$contactprops["homeaddress"]])) {
1734
			$props[$contactprops["mailingaddress"]] = 1;
1735
			$this->setMailingAddress($contact->homecity, $contact->homecountry, $contact->homepostalcode, $contact->homestate, $contact->homestreet, $props[$contactprops["homeaddress"]], $props, $contactprops);
1736
		}
1737
		elseif (isset($props[$contactprops["otheraddress"]])) {
1738
			$props[$contactprops["mailingaddress"]] = 3;
1739
			$this->setMailingAddress($contact->othercity, $contact->othercountry, $contact->otherpostalcode, $contact->otherstate, $contact->otherstreet, $props[$contactprops["otheraddress"]], $props, $contactprops);
1740
		}
1741
1742
		if (isset($contact->picture)) {
1743
			$picbinary = base64_decode($contact->picture);
1744
			$picsize = strlen($picbinary);
1745
			$props[$contactprops["haspic"]] = false;
1746
1747
			// TODO contact picture handling
1748
			// check if contact has already got a picture. delete it first in that case
1749
			// delete it also if it was removed on a mobile
1750
			$picprops = mapi_getprops($mapimessage, [$contactprops["haspic"]]);
1751
			if (isset($picprops[$contactprops["haspic"]]) && $picprops[$contactprops["haspic"]]) {
1752
				SLog::Write(LOGLEVEL_DEBUG, "Contact already has a picture. Delete it");
1753
1754
				$attachtable = mapi_message_getattachmenttable($mapimessage);
1755
				mapi_table_restrict($attachtable, MAPIUtils::GetContactPicRestriction());
1756
				$rows = mapi_table_queryallrows($attachtable, [PR_ATTACH_NUM]);
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_NUM was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1757
				if (isset($rows) && is_array($rows)) {
1758
					foreach ($rows as $row) {
1759
						mapi_message_deleteattach($mapimessage, $row[PR_ATTACH_NUM]);
1760
					}
1761
				}
1762
			}
1763
1764
			// only set picture if there's data in the request
1765
			if ($picbinary !== false && $picsize > 0) {
1766
				$props[$contactprops["haspic"]] = true;
1767
				$pic = mapi_message_createattach($mapimessage);
1768
				// Set properties of the attachment
1769
				$picprops = [
1770
					PR_ATTACH_LONG_FILENAME => "ContactPicture.jpg",
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_LONG_FILENAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1771
					PR_DISPLAY_NAME => "ContactPicture.jpg",
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1772
					0x7FFF000B => true,
1773
					PR_ATTACHMENT_HIDDEN => false,
0 ignored issues
show
Bug introduced by
The constant PR_ATTACHMENT_HIDDEN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1774
					PR_ATTACHMENT_FLAGS => 1,
0 ignored issues
show
Bug introduced by
The constant PR_ATTACHMENT_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1775
					PR_ATTACH_METHOD => ATTACH_BY_VALUE,
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_METHOD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1776
					PR_ATTACH_EXTENSION => ".jpg",
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_EXTENSION was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1777
					PR_ATTACH_NUM => 1,
1778
					PR_ATTACH_SIZE => $picsize,
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_SIZE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1779
					PR_ATTACH_DATA_BIN => $picbinary,
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_DATA_BIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1780
				];
1781
1782
				mapi_setprops($pic, $picprops);
1783
				mapi_savechanges($pic);
1784
			}
1785
		}
1786
1787
		if (isset($contact->asbody)) {
1788
			$this->setASbody($contact->asbody, $props, $contactprops);
1789
		}
1790
1791
		// set fileas
1792
		if (defined('FILEAS_ORDER')) {
1793
			$lastname = (isset($contact->lastname)) ? $contact->lastname : "";
1794
			$firstname = (isset($contact->firstname)) ? $contact->firstname : "";
1795
			$middlename = (isset($contact->middlename)) ? $contact->middlename : "";
1796
			$company = (isset($contact->companyname)) ? $contact->companyname : "";
1797
			$props[$contactprops["fileas"]] = Utils::BuildFileAs($lastname, $firstname, $middlename, $company);
1798
		}
1799
		else {
1800
			SLog::Write(LOGLEVEL_DEBUG, "FILEAS_ORDER not defined");
1801
		}
1802
1803
		mapi_setprops($mapimessage, $props);
1804
1805
		return true;
1806
	}
1807
1808
	/**
1809
	 * Writes a SyncTask to MAPI.
1810
	 *
1811
	 * @param mixed    $mapimessage
1812
	 * @param SyncTask $task
1813
	 *
1814
	 * @return bool
1815
	 */
1816
	private function setTask($mapimessage, $task) {
1817
		mapi_setprops($mapimessage, [PR_MESSAGE_CLASS => "IPM.Task"]);
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1818
1819
		$taskmapping = MAPIMapping::GetTaskMapping();
1820
		$taskprops = MAPIMapping::GetTaskProperties();
1821
		$this->setPropsInMAPI($mapimessage, $task, $taskmapping);
1822
		$taskprops = array_merge($this->getPropIdsFromStrings($taskmapping), $this->getPropIdsFromStrings($taskprops));
1823
1824
		// task specific properties to be set
1825
		$props = [];
1826
1827
		if (isset($task->asbody)) {
1828
			$this->setASbody($task->asbody, $props, $taskprops);
1829
		}
1830
1831
		if (isset($task->complete)) {
1832
			if ($task->complete) {
1833
				// Set completion to 100%
1834
				// Set status to 'complete'
1835
				$props[$taskprops["completion"]] = 1.0;
1836
				$props[$taskprops["status"]] = 2;
1837
				$props[$taskprops["reminderset"]] = false;
1838
			}
1839
			else {
1840
				// Set completion to 0%
1841
				// Set status to 'not started'
1842
				$props[$taskprops["completion"]] = 0.0;
1843
				$props[$taskprops["status"]] = 0;
1844
			}
1845
		}
1846
		if (isset($task->recurrence) && class_exists('TaskRecurrence')) {
1847
			$deadoccur = false;
1848
			if ((isset($task->recurrence->occurrences) && $task->recurrence->occurrences == 1) ||
1849
				(isset($task->recurrence->deadoccur) && $task->recurrence->deadoccur == 1)) { // ios5 sends deadoccur inside the recurrence
1850
				$deadoccur = true;
1851
			}
1852
1853
			// Set PR_ICON_INDEX to 1281 to show correct icon in category view
1854
			$props[$taskprops["icon"]] = 1281;
1855
			// dead occur - false if new occurrences should be generated from the task
1856
			// true - if it is the last occurrence of the task
1857
			$props[$taskprops["deadoccur"]] = $deadoccur;
1858
			$props[$taskprops["isrecurringtag"]] = true;
1859
1860
			$recurrence = new TaskRecurrence($this->store, $mapimessage);
1861
			$recur = [];
1862
			$this->setRecurrence($task, $recur);
1863
1864
			// task specific recurrence properties which we need to set here
1865
			// "start" and "end" are in GMT when passing to class.recurrence
1866
			// set recurrence start here because it's calculated differently for tasks and appointments
1867
			$recur["start"] = $task->recurrence->start;
1868
			$recur["regen"] = (isset($task->recurrence->regenerate) && $task->recurrence->regenerate) ? 1 : 0;
1869
			// OL regenerates recurring task itself, but setting deleteOccurrence is required so that PHP-MAPI doesn't regenerate
1870
			// completed occurrence of a task.
1871
			if ($recur["regen"] == 0) {
1872
				$recur["deleteOccurrence"] = 0;
1873
			}
1874
			// Also add dates to $recur
1875
			$recur["duedate"] = $task->duedate;
1876
			$recur["complete"] = (isset($task->complete) && $task->complete) ? 1 : 0;
1877
			if (isset($task->datecompleted)) {
1878
				$recur["datecompleted"] = $task->datecompleted;
1879
			}
1880
			$recurrence->setRecurrence($recur);
1881
		}
1882
1883
		$props[$taskprops["private"]] = (isset($task->sensitivity) && $task->sensitivity >= SENSITIVITY_PRIVATE) ? true : false;
1884
1885
		// Open address book for user resolve to set the owner
1886
		$addrbook = $this->getAddressbook();
0 ignored issues
show
Unused Code introduced by
The assignment to $addrbook is dead and can be removed.
Loading history...
1887
1888
		// check if there is already an owner for the task, set current user if not
1889
		$p = [$taskprops["owner"]];
1890
		$owner = $this->getProps($mapimessage, $p);
1891
		if (!isset($owner[$taskprops["owner"]])) {
1892
			$userinfo = nsp_getuserinfo(Request::GetUser());
1893
			if (mapi_last_hresult() == NOERROR && isset($userinfo["fullname"])) {
1894
				$props[$taskprops["owner"]] = $userinfo["fullname"];
1895
			}
1896
		}
1897
		mapi_setprops($mapimessage, $props);
1898
1899
		return true;
1900
	}
1901
1902
	/**
1903
	 * Writes a SyncNote to MAPI.
1904
	 *
1905
	 * @param mixed    $mapimessage
1906
	 * @param SyncNote $note
1907
	 *
1908
	 * @return bool
1909
	 */
1910
	private function setNote($mapimessage, $note) {
1911
		// Touchdown does not send categories if all are unset or there is none.
1912
		// Setting it to an empty array will unset the property in KC as well
1913
		if (!isset($note->categories)) {
1914
			$note->categories = [];
1915
		}
1916
1917
		// update icon index to correspond to the color
1918
		if (isset($note->Color) && $note->Color > -1 && $note->Color < 5) {
1919
			$note->Iconindex = 768 + $note->Color;
0 ignored issues
show
Bug introduced by
The property Iconindex does not seem to exist on SyncNote.
Loading history...
1920
		}
1921
1922
		$this->setPropsInMAPI($mapimessage, $note, MAPIMapping::GetNoteMapping());
1923
1924
		$noteprops = MAPIMapping::GetNoteProperties();
1925
		$noteprops = $this->getPropIdsFromStrings($noteprops);
1926
1927
		// note specific properties to be set
1928
		$props = [];
1929
		$props[$noteprops["messageclass"]] = "IPM.StickyNote";
1930
		// set body otherwise the note will be "broken" when editing it in outlook
1931
		if (isset($note->asbody)) {
1932
			$this->setASbody($note->asbody, $props, $noteprops);
1933
		}
1934
1935
		$props[$noteprops["internetcpid"]] = INTERNET_CPID_UTF8;
1936
		mapi_setprops($mapimessage, $props);
1937
1938
		return true;
1939
	}
1940
1941
	/*----------------------------------------------------------------------------------------------------------
1942
	 * HELPER
1943
	 */
1944
1945
	/**
1946
	 * Returns the timestamp offset.
1947
	 *
1948
	 * @param string $ts
1949
	 *
1950
	 * @return long
1951
	 */
1952
	private function GetTZOffset($ts) {
1953
		$Offset = date("O", $ts);
0 ignored issues
show
Bug introduced by
$ts of type string is incompatible with the type integer|null expected by parameter $timestamp of date(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1953
		$Offset = date("O", /** @scrutinizer ignore-type */ $ts);
Loading history...
1954
1955
		$Parity = $Offset < 0 ? -1 : 1;
1956
		$Offset = $Parity * $Offset;
1957
		$Offset = ($Offset - ($Offset % 100)) / 100 * 60 + $Offset % 100;
1958
1959
		return $Parity * $Offset;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $Parity * $Offset returns the type integer which is incompatible with the documented return type long.
Loading history...
1960
	}
1961
1962
	/**
1963
	 * Localtime of the timestamp.
1964
	 *
1965
	 * @param long $time
1966
	 *
1967
	 * @return array
1968
	 */
1969
	private function gmtime($time) {
1970
		$TZOffset = $this->GetTZOffset($time);
1971
1972
		$t_time = $time - $TZOffset * 60; # Counter adjust for localtime()
1973
1974
		return localtime($t_time, 1);
1975
	}
1976
1977
	/**
1978
	 * Sets the properties in a MAPI object according to an Sync object and a property mapping.
1979
	 *
1980
	 * @param mixed      $mapimessage
1981
	 * @param SyncObject $message
1982
	 * @param array      $mapping
1983
	 *
1984
	 * @return
1985
	 */
1986
	private function setPropsInMAPI($mapimessage, $message, $mapping) {
1987
		$mapiprops = $this->getPropIdsFromStrings($mapping);
1988
		$unsetVars = $message->getUnsetVars();
1989
		$propsToDelete = [];
1990
		$propsToSet = [];
1991
1992
		foreach ($mapiprops as $asprop => $mapiprop) {
1993
			if (isset($message->{$asprop})) {
1994
				// UTF8->windows1252.. this is ok for all numerical values
1995
				if (mapi_prop_type($mapiprop) != PT_BINARY && mapi_prop_type($mapiprop) != PT_MV_BINARY) {
1996
					if (is_array($message->{$asprop})) {
1997
						$value = array_map("u2wi", $message->{$asprop});
1998
					}
1999
					else {
2000
						$value = u2wi($message->{$asprop});
2001
					}
2002
				}
2003
				else {
2004
					$value = $message->{$asprop};
2005
				}
2006
2007
				// Make sure the php values are the correct type
2008
				switch (mapi_prop_type($mapiprop)) {
2009
					case PT_BINARY:
2010
					case PT_STRING8:
2011
						settype($value, "string");
2012
						break;
2013
2014
					case PT_BOOLEAN:
2015
						settype($value, "boolean");
2016
						break;
2017
2018
					case PT_SYSTIME:
2019
					case PT_LONG:
2020
						settype($value, "integer");
2021
						break;
2022
				}
2023
2024
				// decode base64 value
2025
				if ($mapiprop == PR_RTF_COMPRESSED) {
0 ignored issues
show
Bug introduced by
The constant PR_RTF_COMPRESSED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2026
					$value = base64_decode($value);
2027
					if (strlen($value) == 0) {
2028
						continue;
2029
					} // PDA will sometimes give us an empty RTF, which we'll ignore.
2030
2031
					// Note that you can still remove notes because when you remove notes it gives
2032
					// a valid compressed RTF with nothing in it.
2033
				}
2034
				// if an "empty array" is to be saved, it the mvprop should be deleted - fixes Mantis #468
2035
				if (is_array($value) && empty($value)) {
2036
					$propsToDelete[] = $mapiprop;
2037
					SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->setPropsInMAPI(): Property '%s' to be deleted as it is an empty array", $asprop));
2038
				}
2039
				else {
2040
					// all properties will be set at once
2041
					$propsToSet[$mapiprop] = $value;
2042
				}
2043
			}
2044
			elseif (in_array($asprop, $unsetVars)) {
2045
				$propsToDelete[] = $mapiprop;
2046
			}
2047
		}
2048
2049
		mapi_setprops($mapimessage, $propsToSet);
2050
		if (mapi_last_hresult()) {
2051
			SLog::Write(LOGLEVEL_WARN, sprintf("Failed to set properties, trying to set them separately. Error code was:%x", mapi_last_hresult()));
2052
			$this->setPropsIndividually($mapimessage, $propsToSet, $mapiprops);
2053
		}
2054
2055
		mapi_deleteprops($mapimessage, $propsToDelete);
2056
2057
		// clean up
2058
		unset($unsetVars, $propsToDelete);
2059
	}
2060
2061
	/**
2062
	 * Sets the properties one by one in a MAPI object.
2063
	 *
2064
	 * @param mixed &$mapimessage
2065
	 * @param array &$propsToSet
2066
	 * @param array &$mapiprops
2067
	 *
2068
	 * @return
2069
	 */
2070
	private function setPropsIndividually(&$mapimessage, &$propsToSet, &$mapiprops) {
2071
		foreach ($propsToSet as $prop => $value) {
2072
			mapi_setprops($mapimessage, [$prop => $value]);
2073
			if (mapi_last_hresult()) {
2074
				SLog::Write(LOGLEVEL_ERROR, sprintf("Failed setting property [%s] with value [%s], error code was:%x", array_search($prop, $mapiprops), $value, mapi_last_hresult()));
2075
			}
2076
		}
2077
	}
2078
2079
	/**
2080
	 * Gets the properties from a MAPI object and sets them in the Sync object according to mapping.
2081
	 *
2082
	 * @param SyncObject &$message
2083
	 * @param mixed      $mapimessage
2084
	 * @param array      $mapping
2085
	 *
2086
	 * @return
2087
	 */
2088
	private function getPropsFromMAPI(&$message, $mapimessage, $mapping) {
2089
		$messageprops = $this->getProps($mapimessage, $mapping);
2090
		foreach ($mapping as $asprop => $mapiprop) {
2091
			// Get long strings via openproperty
2092
			if (isset($messageprops[mapi_prop_tag(PT_ERROR, mapi_prop_id($mapiprop))])) {
2093
				if ($messageprops[mapi_prop_tag(PT_ERROR, mapi_prop_id($mapiprop))] == MAPI_E_NOT_ENOUGH_MEMORY_32BIT ||
2094
					$messageprops[mapi_prop_tag(PT_ERROR, mapi_prop_id($mapiprop))] == MAPI_E_NOT_ENOUGH_MEMORY_64BIT) {
2095
					$messageprops[$mapiprop] = MAPIUtils::readPropStream($mapimessage, $mapiprop);
2096
				}
2097
			}
2098
2099
			if (isset($messageprops[$mapiprop])) {
2100
				if (mapi_prop_type($mapiprop) == PT_BOOLEAN) {
2101
					// Force to actual '0' or '1'
2102
					if ($messageprops[$mapiprop]) {
2103
						$message->{$asprop} = 1;
2104
					}
2105
					else {
2106
						$message->{$asprop} = 0;
2107
					}
2108
				}
2109
				else {
2110
					// Special handling for PR_MESSAGE_FLAGS
2111
					if ($mapiprop == PR_MESSAGE_FLAGS) {
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2112
						$message->{$asprop} = $messageprops[$mapiprop] & 1;
2113
					} // only look at 'read' flag
2114
					elseif ($mapiprop == PR_RTF_COMPRESSED) {
0 ignored issues
show
Bug introduced by
The constant PR_RTF_COMPRESSED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2115
						// do not send rtf to the mobile
2116
						continue;
2117
					}
2118
					elseif (is_array($messageprops[$mapiprop])) {
2119
						$message->{$asprop} = array_map("w2u", $messageprops[$mapiprop]);
2120
					}
2121
					else {
2122
						if (mapi_prop_type($mapiprop) != PT_BINARY && mapi_prop_type($mapiprop) != PT_MV_BINARY) {
2123
							$message->{$asprop} = w2u($messageprops[$mapiprop]);
2124
						}
2125
						else {
2126
							$message->{$asprop} = $messageprops[$mapiprop];
2127
						}
2128
					}
2129
				}
2130
			}
2131
		}
2132
	}
2133
2134
	/**
2135
	 * Wraps getPropIdsFromStrings() calls.
2136
	 *
2137
	 * @param mixed &$mapiprops
2138
	 *
2139
	 * @return
2140
	 */
2141
	private function getPropIdsFromStrings(&$mapiprops) {
2142
		return getPropIdsFromStrings($this->store, $mapiprops);
2143
	}
2144
2145
	/**
2146
	 * Wraps mapi_getprops() calls.
2147
	 *
2148
	 * @param mixed &$mapiprops
2149
	 * @param mixed $mapimessage
2150
	 * @param mixed $mapiproperties
2151
	 *
2152
	 * @return
2153
	 */
2154
	protected function getProps($mapimessage, &$mapiproperties) {
2155
		$mapiproperties = $this->getPropIdsFromStrings($mapiproperties);
2156
2157
		return mapi_getprops($mapimessage, $mapiproperties);
2158
	}
2159
2160
	/**
2161
	 * Returns an GMT timezone array.
2162
	 *
2163
	 * @return array
2164
	 */
2165
	private function getGMTTZ() {
2166
		return [
2167
			"bias" => 0,
2168
			"tzname" => "",
2169
			"dstendyear" => 0,
2170
			"dstendmonth" => 10,
2171
			"dstendday" => 0,
2172
			"dstendweek" => 5,
2173
			"dstendhour" => 2,
2174
			"dstendminute" => 0,
2175
			"dstendsecond" => 0,
2176
			"dstendmillis" => 0,
2177
			"stdbias" => 0,
2178
			"tznamedst" => "",
2179
			"dststartyear" => 0,
2180
			"dststartmonth" => 3,
2181
			"dststartday" => 0,
2182
			"dststartweek" => 5,
2183
			"dststarthour" => 1,
2184
			"dststartminute" => 0,
2185
			"dststartsecond" => 0,
2186
			"dststartmillis" => 0,
2187
			"dstbias" => -60,
2188
		];
2189
	}
2190
2191
	/**
2192
	 * Unpack timezone info from MAPI.
2193
	 *
2194
	 * @param string $data
2195
	 *
2196
	 * @return array
2197
	 */
2198
	private function getTZFromMAPIBlob($data) {
2199
		return unpack("lbias/lstdbias/ldstbias/" .
2200
						   "vconst1/vdstendyear/vdstendmonth/vdstendday/vdstendweek/vdstendhour/vdstendminute/vdstendsecond/vdstendmillis/" .
2201
						   "vconst2/vdststartyear/vdststartmonth/vdststartday/vdststartweek/vdststarthour/vdststartminute/vdststartsecond/vdststartmillis", $data);
2202
	}
2203
2204
	/**
2205
	 * Unpack timezone info from Sync.
2206
	 *
2207
	 * @param string $data
2208
	 *
2209
	 * @return array
2210
	 */
2211
	private function getTZFromSyncBlob($data) {
2212
		$tz = unpack("lbias/a64tzname/vdstendyear/vdstendmonth/vdstendday/vdstendweek/vdstendhour/vdstendminute/vdstendsecond/vdstendmillis/" .
2213
						"lstdbias/a64tznamedst/vdststartyear/vdststartmonth/vdststartday/vdststartweek/vdststarthour/vdststartminute/vdststartsecond/vdststartmillis/" .
2214
						"ldstbias", $data);
2215
2216
		// Make the structure compatible with class.recurrence.php
2217
		$tz["timezone"] = $tz["bias"];
2218
		$tz["timezonedst"] = $tz["dstbias"];
2219
2220
		return $tz;
2221
	}
2222
2223
	/**
2224
	 * Pack timezone info for MAPI.
2225
	 *
2226
	 * @param array $tz
2227
	 *
2228
	 * @return string
2229
	 */
2230
	private function getMAPIBlobFromTZ($tz) {
2231
		return pack(
2232
			"lll" . "vvvvvvvvv" . "vvvvvvvvv",
2233
			$tz["bias"],
2234
			$tz["stdbias"],
2235
			$tz["dstbias"],
2236
			0,
2237
			0,
2238
			$tz["dstendmonth"],
2239
			$tz["dstendday"],
2240
			$tz["dstendweek"],
2241
			$tz["dstendhour"],
2242
			$tz["dstendminute"],
2243
			$tz["dstendsecond"],
2244
			$tz["dstendmillis"],
2245
			0,
2246
			0,
2247
			$tz["dststartmonth"],
2248
			$tz["dststartday"],
2249
			$tz["dststartweek"],
2250
			$tz["dststarthour"],
2251
			$tz["dststartminute"],
2252
			$tz["dststartsecond"],
2253
			$tz["dststartmillis"]
2254
		);
2255
	}
2256
2257
	/**
2258
	 * Checks the date to see if it is in DST, and returns correct GMT date accordingly.
2259
	 *
2260
	 * @param long  $localtime
2261
	 * @param array $tz
2262
	 *
2263
	 * @return long
2264
	 */
2265
	private function getGMTTimeByTZ($localtime, $tz) {
2266
		if (!isset($tz) || !is_array($tz)) {
2267
			return $localtime;
2268
		}
2269
2270
		if ($this->isDST($localtime, $tz)) {
2271
			return $localtime + $tz["bias"] * 60 + $tz["dstbias"] * 60;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $localtime + $tz[...0 + $tz['dstbias'] * 60 returns the type integer which is incompatible with the documented return type long.
Loading history...
2272
		}
2273
2274
		return $localtime + $tz["bias"] * 60;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $localtime + $tz['bias'] * 60 returns the type integer which is incompatible with the documented return type long.
Loading history...
2275
	}
2276
2277
	/**
2278
	 * Returns the local time for the given GMT time, taking account of the given timezone.
2279
	 *
2280
	 * @param long  $gmttime
2281
	 * @param array $tz
2282
	 *
2283
	 * @return long
2284
	 */
2285
	private function getLocaltimeByTZ($gmttime, $tz) {
2286
		if (!isset($tz) || !is_array($tz)) {
2287
			return $gmttime;
2288
		}
2289
2290
		if ($this->isDST($gmttime - $tz["bias"] * 60, $tz)) { // may bug around the switch time because it may have to be 'gmttime - bias - dstbias'
0 ignored issues
show
Bug introduced by
$gmttime - $tz['bias'] * 60 of type integer is incompatible with the type long expected by parameter $localtime of MAPIProvider::isDST(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2290
		if ($this->isDST(/** @scrutinizer ignore-type */ $gmttime - $tz["bias"] * 60, $tz)) { // may bug around the switch time because it may have to be 'gmttime - bias - dstbias'
Loading history...
2291
			return $gmttime - $tz["bias"] * 60 - $tz["dstbias"] * 60;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $gmttime - $tz['b...0 - $tz['dstbias'] * 60 returns the type integer which is incompatible with the documented return type long.
Loading history...
2292
		}
2293
2294
		return $gmttime - $tz["bias"] * 60;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $gmttime - $tz['bias'] * 60 returns the type integer which is incompatible with the documented return type long.
Loading history...
2295
	}
2296
2297
	/**
2298
	 * Returns TRUE if it is the summer and therefore DST is in effect.
2299
	 *
2300
	 * @param long  $localtime
2301
	 * @param array $tz
2302
	 *
2303
	 * @return bool
2304
	 */
2305
	private function isDST($localtime, $tz) {
2306
		if (!isset($tz) || !is_array($tz) ||
2307
			!isset($tz["dstbias"]) || $tz["dstbias"] == 0 ||
2308
			!isset($tz["dststartmonth"]) || $tz["dststartmonth"] == 0 ||
2309
			!isset($tz["dstendmonth"]) || $tz["dstendmonth"] == 0) {
2310
			return false;
2311
		}
2312
2313
		$year = gmdate("Y", $localtime);
0 ignored issues
show
Bug introduced by
$localtime of type long is incompatible with the type integer|null expected by parameter $timestamp of gmdate(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2313
		$year = gmdate("Y", /** @scrutinizer ignore-type */ $localtime);
Loading history...
2314
		$start = $this->getTimestampOfWeek($year, $tz["dststartmonth"], $tz["dststartweek"], $tz["dststartday"], $tz["dststarthour"], $tz["dststartminute"], $tz["dststartsecond"]);
0 ignored issues
show
Bug introduced by
$year of type string is incompatible with the type integer expected by parameter $year of MAPIProvider::getTimestampOfWeek(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2314
		$start = $this->getTimestampOfWeek(/** @scrutinizer ignore-type */ $year, $tz["dststartmonth"], $tz["dststartweek"], $tz["dststartday"], $tz["dststarthour"], $tz["dststartminute"], $tz["dststartsecond"]);
Loading history...
2315
		$end = $this->getTimestampOfWeek($year, $tz["dstendmonth"], $tz["dstendweek"], $tz["dstendday"], $tz["dstendhour"], $tz["dstendminute"], $tz["dstendsecond"]);
2316
2317
		if ($start < $end) {
2318
			// northern hemisphere (july = dst)
2319
			if ($localtime >= $start && $localtime < $end) {
2320
				$dst = true;
2321
			}
2322
			else {
2323
				$dst = false;
2324
			}
2325
		}
2326
		else {
2327
			// southern hemisphere (january = dst)
2328
			if ($localtime >= $end && $localtime < $start) {
2329
				$dst = false;
2330
			}
2331
			else {
2332
				$dst = true;
2333
			}
2334
		}
2335
2336
		return $dst;
2337
	}
2338
2339
	/**
2340
	 * Returns the local timestamp for the $week'th $wday of $month in $year at $hour:$minute:$second.
2341
	 *
2342
	 * @param int $year
2343
	 * @param int $month
2344
	 * @param int $week
2345
	 * @param int $wday
2346
	 * @param int $hour
2347
	 * @param int $minute
2348
	 * @param int $second
2349
	 *
2350
	 * @return long
2351
	 */
2352
	private function getTimestampOfWeek($year, $month, $week, $wday, $hour, $minute, $second) {
2353
		if ($month == 0) {
2354
			return;
2355
		}
2356
2357
		$date = gmmktime($hour, $minute, $second, $month, 1, $year);
2358
2359
		// Find first day in month which matches day of the week
2360
		while (1) {
2361
			$wdaynow = gmdate("w", $date);
2362
			if ($wdaynow == $wday) {
2363
				break;
2364
			}
2365
			$date += 24 * 60 * 60;
2366
		}
2367
2368
		// Forward $week weeks (may 'overflow' into the next month)
2369
		$date = $date + $week * (24 * 60 * 60 * 7);
2370
2371
		// Reverse 'overflow'. Eg week '10' will always be the last week of the month in which the
2372
		// specified weekday exists
2373
		while (1) {
2374
			$monthnow = gmdate("n", $date); // gmdate returns 1-12
2375
			if ($monthnow > $month) {
2376
				$date = $date - (24 * 7 * 60 * 60);
2377
			}
2378
			else {
2379
				break;
2380
			}
2381
		}
2382
2383
		return $date;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $date returns the type integer which is incompatible with the documented return type long.
Loading history...
2384
	}
2385
2386
	/**
2387
	 * Normalize the given timestamp to the start of the day.
2388
	 *
2389
	 * @param long $timestamp
2390
	 *
2391
	 * @return long
2392
	 */
2393
	private function getDayStartOfTimestamp($timestamp) {
2394
		return $timestamp - ($timestamp % (60 * 60 * 24));
0 ignored issues
show
Bug Best Practice introduced by
The expression return $timestamp - $timestamp % 60 * 60 * 24 returns the type integer which is incompatible with the documented return type long.
Loading history...
2395
	}
2396
2397
	/**
2398
	 * Returns an SMTP address from an entry id.
2399
	 *
2400
	 * @param string $entryid
2401
	 *
2402
	 * @return string
2403
	 */
2404
	private function getSMTPAddressFromEntryID($entryid) {
2405
		$addrbook = $this->getAddressbook();
2406
2407
		$mailuser = mapi_ab_openentry($addrbook, $entryid);
2408
		if (!$mailuser) {
2409
			return "";
2410
		}
2411
2412
		$props = mapi_getprops($mailuser, [PR_ADDRTYPE, PR_SMTP_ADDRESS, PR_EMAIL_ADDRESS]);
0 ignored issues
show
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SMTP_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2413
2414
		$addrtype = isset($props[PR_ADDRTYPE]) ? $props[PR_ADDRTYPE] : "";
2415
2416
		if (isset($props[PR_SMTP_ADDRESS])) {
2417
			return $props[PR_SMTP_ADDRESS];
2418
		}
2419
2420
		if ($addrtype == "SMTP" && isset($props[PR_EMAIL_ADDRESS])) {
2421
			return $props[PR_EMAIL_ADDRESS];
2422
		}
2423
		if ($addrtype == "ZARAFA" && isset($props[PR_EMAIL_ADDRESS])) {
2424
			$userinfo = nsp_getuserinfo($props[PR_EMAIL_ADDRESS]);
2425
			if (is_array($userinfo) && isset($userinfo["primary_email"])) {
2426
				return $userinfo["primary_email"];
2427
			}
2428
		}
2429
2430
		return "";
2431
	}
2432
2433
	/**
2434
	 * Returns fullname from an entryid.
2435
	 *
2436
	 * @param binary $entryid
0 ignored issues
show
Bug introduced by
The type binary was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
2437
	 *
2438
	 * @return string fullname or false on error
2439
	 */
2440
	private function getFullnameFromEntryID($entryid) {
2441
		$addrbook = $this->getAddressbook();
2442
		$mailuser = mapi_ab_openentry($addrbook, $entryid);
2443
		if (!$mailuser) {
2444
			SLog::Write(LOGLEVEL_ERROR, sprintf("Unable to get mailuser for getFullnameFromEntryID (0x%X)", mapi_last_hresult()));
2445
2446
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
2447
		}
2448
2449
		$props = mapi_getprops($mailuser, [PR_DISPLAY_NAME]);
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2450
		if (isset($props[PR_DISPLAY_NAME])) {
2451
			return $props[PR_DISPLAY_NAME];
2452
		}
2453
		SLog::Write(LOGLEVEL_ERROR, sprintf("Unable to get fullname for getFullnameFromEntryID (0x%X)", mapi_last_hresult()));
2454
2455
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
2456
	}
2457
2458
	/**
2459
	 * Builds a displayname from several separated values.
2460
	 *
2461
	 * @param SyncContact $contact
2462
	 *
2463
	 * @return string
2464
	 */
2465
	private function composeDisplayName(&$contact) {
2466
		// Set display name and subject to a combined value of firstname and lastname
2467
		$cname = (isset($contact->prefix)) ? u2w($contact->prefix) . " " : "";
0 ignored issues
show
Bug introduced by
Are you sure u2w($contact->prefix) of type false|mixed|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2467
		$cname = (isset($contact->prefix)) ? /** @scrutinizer ignore-type */ u2w($contact->prefix) . " " : "";
Loading history...
2468
		$cname .= u2w($contact->firstname);
2469
		$cname .= (isset($contact->middlename)) ? " " . u2w($contact->middlename) : "";
0 ignored issues
show
Bug introduced by
Are you sure u2w($contact->middlename) of type false|mixed|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2469
		$cname .= (isset($contact->middlename)) ? " " . /** @scrutinizer ignore-type */ u2w($contact->middlename) : "";
Loading history...
2470
		$cname .= " " . u2w($contact->lastname);
0 ignored issues
show
Bug introduced by
Are you sure u2w($contact->lastname) of type false|mixed|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2470
		$cname .= " " . /** @scrutinizer ignore-type */ u2w($contact->lastname);
Loading history...
2471
		$cname .= (isset($contact->suffix)) ? " " . u2w($contact->suffix) : "";
0 ignored issues
show
Bug introduced by
Are you sure u2w($contact->suffix) of type false|mixed|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2471
		$cname .= (isset($contact->suffix)) ? " " . /** @scrutinizer ignore-type */ u2w($contact->suffix) : "";
Loading history...
2472
2473
		return trim($cname);
2474
	}
2475
2476
	/**
2477
	 * Sets all dependent properties for an email address.
2478
	 *
2479
	 * @param string $emailAddress
2480
	 * @param string $displayName
2481
	 * @param int    $cnt
2482
	 * @param array  &$props
2483
	 * @param array  &$properties
2484
	 * @param array  &$nremails
2485
	 * @param int    &$abprovidertype
2486
	 *
2487
	 * @return
2488
	 */
2489
	private function setEmailAddress($emailAddress, $displayName, $cnt, &$props, &$properties, &$nremails, &$abprovidertype) {
2490
		if (isset($emailAddress)) {
2491
			$name = (isset($displayName)) ? $displayName : $emailAddress;
2492
2493
			$props[$properties["emailaddress{$cnt}"]] = $emailAddress;
2494
			$props[$properties["emailaddressdemail{$cnt}"]] = $emailAddress;
2495
			$props[$properties["emailaddressdname{$cnt}"]] = $name;
2496
			$props[$properties["emailaddresstype{$cnt}"]] = "SMTP";
2497
			$props[$properties["emailaddressentryid{$cnt}"]] = mapi_createoneoff($name, "SMTP", $emailAddress);
2498
			$nremails[] = $cnt - 1;
2499
			$abprovidertype |= 2 ^ ($cnt - 1);
2500
		}
2501
	}
2502
2503
	/**
2504
	 * Sets the properties for an address string.
2505
	 *
2506
	 * @param string $type        which address is being set
2507
	 * @param string $city
2508
	 * @param string $country
2509
	 * @param string $postalcode
2510
	 * @param string $state
2511
	 * @param string $street
2512
	 * @param array  &$props
2513
	 * @param array  &$properties
2514
	 *
2515
	 * @return
2516
	 */
2517
	private function setAddress($type, &$city, &$country, &$postalcode, &$state, &$street, &$props, &$properties) {
2518
		if (isset($city)) {
2519
			$props[$properties[$type . "city"]] = $city = u2w($city);
2520
		}
2521
2522
		if (isset($country)) {
2523
			$props[$properties[$type . "country"]] = $country = u2w($country);
2524
		}
2525
2526
		if (isset($postalcode)) {
2527
			$props[$properties[$type . "postalcode"]] = $postalcode = u2w($postalcode);
2528
		}
2529
2530
		if (isset($state)) {
2531
			$props[$properties[$type . "state"]] = $state = u2w($state);
2532
		}
2533
2534
		if (isset($street)) {
2535
			$props[$properties[$type . "street"]] = $street = u2w($street);
2536
		}
2537
2538
		// set composed address
2539
		$address = Utils::BuildAddressString($street, $postalcode, $city, $state, $country);
2540
		if ($address) {
2541
			$props[$properties[$type . "address"]] = $address;
2542
		}
2543
	}
2544
2545
	/**
2546
	 * Sets the properties for a mailing address.
2547
	 *
2548
	 * @param string $city
2549
	 * @param string $country
2550
	 * @param string $postalcode
2551
	 * @param string $state
2552
	 * @param string $street
2553
	 * @param string $address
2554
	 * @param array  &$props
2555
	 * @param array  &$properties
2556
	 *
2557
	 * @return
2558
	 */
2559
	private function setMailingAddress($city, $country, $postalcode, $state, $street, $address, &$props, &$properties) {
2560
		if (isset($city)) {
2561
			$props[$properties["city"]] = $city;
2562
		}
2563
		if (isset($country)) {
2564
			$props[$properties["country"]] = $country;
2565
		}
2566
		if (isset($postalcode)) {
2567
			$props[$properties["postalcode"]] = $postalcode;
2568
		}
2569
		if (isset($state)) {
2570
			$props[$properties["state"]] = $state;
2571
		}
2572
		if (isset($street)) {
2573
			$props[$properties["street"]] = $street;
2574
		}
2575
		if (isset($address)) {
2576
			$props[$properties["postaladdress"]] = $address;
2577
		}
2578
	}
2579
2580
	/**
2581
	 * Sets data in a recurrence array.
2582
	 *
2583
	 * @param SyncObject $message
2584
	 * @param array      &$recur
2585
	 *
2586
	 * @return
2587
	 */
2588
	private function setRecurrence($message, &$recur) {
2589
		if (isset($message->complete)) {
2590
			$recur["complete"] = $message->complete;
2591
		}
2592
2593
		if (!isset($message->recurrence->interval)) {
2594
			$message->recurrence->interval = 1;
2595
		}
2596
2597
		// set the default value of numoccur
2598
		$recur["numoccur"] = 0;
2599
		// a place holder for recurrencetype property
2600
		$recur["recurrencetype"] = 0;
2601
2602
		switch ($message->recurrence->type) {
2603
			case 0:
2604
				$recur["type"] = 10;
2605
				if (isset($message->recurrence->dayofweek)) {
2606
					$recur["subtype"] = 1;
2607
				}
2608
				else {
2609
					$recur["subtype"] = 0;
2610
				}
2611
2612
				$recur["everyn"] = $message->recurrence->interval * (60 * 24);
2613
				$recur["recurrencetype"] = 1;
2614
				break;
2615
2616
			case 1:
2617
				$recur["type"] = 11;
2618
				$recur["subtype"] = 1;
2619
				$recur["everyn"] = $message->recurrence->interval;
2620
				$recur["recurrencetype"] = 2;
2621
				break;
2622
2623
			case 2:
2624
				$recur["type"] = 12;
2625
				$recur["subtype"] = 2;
2626
				$recur["everyn"] = $message->recurrence->interval;
2627
				$recur["recurrencetype"] = 3;
2628
				break;
2629
2630
			case 3:
2631
				$recur["type"] = 12;
2632
				$recur["subtype"] = 3;
2633
				$recur["everyn"] = $message->recurrence->interval;
2634
				$recur["recurrencetype"] = 3;
2635
				break;
2636
2637
			case 4:
2638
				$recur["type"] = 13;
2639
				$recur["subtype"] = 1;
2640
				$recur["everyn"] = $message->recurrence->interval * 12;
2641
				$recur["recurrencetype"] = 4;
2642
				break;
2643
2644
			case 5:
2645
				$recur["type"] = 13;
2646
				$recur["subtype"] = 2;
2647
				$recur["everyn"] = $message->recurrence->interval * 12;
2648
				$recur["recurrencetype"] = 4;
2649
				break;
2650
2651
			case 6:
2652
				$recur["type"] = 13;
2653
				$recur["subtype"] = 3;
2654
				$recur["everyn"] = $message->recurrence->interval * 12;
2655
				$recur["recurrencetype"] = 4;
2656
				break;
2657
		}
2658
2659
		// "start" and "end" are in GMT when passing to class.recurrence
2660
		$recur["end"] = $this->getDayStartOfTimestamp(0x7FFFFFFF); // Maximum GMT value for end by default
0 ignored issues
show
Bug introduced by
2147483647 of type integer is incompatible with the type long expected by parameter $timestamp of MAPIProvider::getDayStartOfTimestamp(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2660
		$recur["end"] = $this->getDayStartOfTimestamp(/** @scrutinizer ignore-type */ 0x7FFFFFFF); // Maximum GMT value for end by default
Loading history...
2661
2662
		if (isset($message->recurrence->until)) {
2663
			$recur["term"] = 0x21;
2664
			$recur["end"] = $message->recurrence->until;
2665
		}
2666
		elseif (isset($message->recurrence->occurrences)) {
2667
			$recur["term"] = 0x22;
2668
			$recur["numoccur"] = $message->recurrence->occurrences;
2669
		}
2670
		else {
2671
			$recur["term"] = 0x23;
2672
		}
2673
2674
		if (isset($message->recurrence->dayofweek)) {
2675
			$recur["weekdays"] = $message->recurrence->dayofweek;
2676
		}
2677
		if (isset($message->recurrence->weekofmonth)) {
2678
			$recur["nday"] = $message->recurrence->weekofmonth;
2679
		}
2680
		if (isset($message->recurrence->monthofyear)) {
2681
			// MAPI stores months as the amount of minutes until the beginning of the month in a
2682
			// non-leapyear. Why this is, is totally unclear.
2683
			$monthminutes = [0, 44640, 84960, 129600, 172800, 217440, 260640, 305280, 348480, 393120, 437760, 480960];
2684
			$recur["month"] = $monthminutes[$message->recurrence->monthofyear - 1];
2685
		}
2686
		if (isset($message->recurrence->dayofmonth)) {
2687
			$recur["monthday"] = $message->recurrence->dayofmonth;
2688
		}
2689
	}
2690
2691
	/**
2692
	 * Extracts the email address (mailbox@host) from an email address because
2693
	 * some devices send email address as "Firstname Lastname" <[email protected]>.
2694
	 *
2695
	 *  @see http://developer.berlios.de/mantis/view.php?id=486
2696
	 *
2697
	 *  @param string           $email
2698
	 *
2699
	 *  @return string or false on error
2700
	 */
2701
	private function extractEmailAddress($email) {
2702
		if (!isset($this->zRFC822)) {
2703
			$this->zRFC822 = new Mail_RFC822();
2704
		}
2705
		$parsedAddress = $this->zRFC822->parseAddressList($email);
2706
		if (!isset($parsedAddress[0]->mailbox) || !isset($parsedAddress[0]->host)) {
2707
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
2708
		}
2709
2710
		return $parsedAddress[0]->mailbox . '@' . $parsedAddress[0]->host;
2711
	}
2712
2713
	/**
2714
	 * Returns the message body for a required format.
2715
	 *
2716
	 * @param MAPIMessage $mapimessage
0 ignored issues
show
Bug introduced by
The type MAPIMessage was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
2717
	 * @param int         $bpReturnType
2718
	 * @param SyncObject  $message
2719
	 *
2720
	 * @return bool
2721
	 */
2722
	private function setMessageBodyForType($mapimessage, $bpReturnType, &$message) {
2723
		$truncateHtmlSafe = false;
2724
		// default value is PR_BODY
2725
		$property = PR_BODY;
0 ignored issues
show
Bug introduced by
The constant PR_BODY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2726
2727
		switch ($bpReturnType) {
2728
			case SYNC_BODYPREFERENCE_HTML:
2729
				$property = PR_HTML;
0 ignored issues
show
Bug introduced by
The constant PR_HTML was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2730
				$truncateHtmlSafe = true;
2731
				break;
2732
2733
			case SYNC_BODYPREFERENCE_RTF:
2734
				$property = PR_RTF_COMPRESSED;
0 ignored issues
show
Bug introduced by
The constant PR_RTF_COMPRESSED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2735
				break;
2736
2737
			case SYNC_BODYPREFERENCE_MIME:
2738
				$stat = $this->imtoinet($mapimessage, $message);
2739
				if (isset($message->asbody)) {
2740
					$message->asbody->type = $bpReturnType;
2741
				}
2742
2743
				return $stat;
2744
		}
2745
2746
		$stream = mapi_openproperty($mapimessage, $property, IID_IStream, 0, 0);
2747
		if ($stream) {
2748
			$stat = mapi_stream_stat($stream);
2749
			$streamsize = $stat['cb'];
2750
		}
2751
		else {
2752
			$streamsize = 0;
2753
		}
2754
2755
		// set the properties according to supported AS version
2756
		if (Request::GetProtocolVersion() >= 12.0) {
2757
			$message->asbody = new SyncBaseBody();
2758
			$message->asbody->type = $bpReturnType;
2759
			if ($bpReturnType == SYNC_BODYPREFERENCE_RTF) {
2760
				$body = $this->mapiReadStream($stream, $streamsize);
2761
				$message->asbody->data = StringStreamWrapper::Open(base64_encode($body));
2762
			}
2763
			elseif (isset($message->internetcpid) && $bpReturnType == SYNC_BODYPREFERENCE_HTML) {
2764
				// if PR_HTML is UTF-8 we can stream it directly, else we have to convert to UTF-8 & wrap it
2765
				if ($message->internetcpid == INTERNET_CPID_UTF8) {
2766
					$message->asbody->data = MAPIStreamWrapper::Open($stream, $truncateHtmlSafe);
2767
				}
2768
				else {
2769
					$body = $this->mapiReadStream($stream, $streamsize);
2770
					$message->asbody->data = StringStreamWrapper::Open(Utils::ConvertCodepageStringToUtf8($message->internetcpid, $body), $truncateHtmlSafe);
2771
					$message->internetcpid = INTERNET_CPID_UTF8;
2772
				}
2773
			}
2774
			else {
2775
				$message->asbody->data = MAPIStreamWrapper::Open($stream);
2776
			}
2777
			$message->asbody->estimatedDataSize = $streamsize;
2778
		}
2779
		else {
2780
			$body = $this->mapiReadStream($stream, $streamsize);
2781
			$message->body = str_replace("\n", "\r\n", w2u(str_replace("\r", "", $body)));
2782
			$message->bodysize = $streamsize;
2783
			$message->bodytruncated = 0;
2784
		}
2785
2786
		return true;
2787
	}
2788
2789
	/**
2790
	 * Reads from a mapi stream, if it's set. If not, returns an empty string.
2791
	 *
2792
	 * @param resource $stream
2793
	 * @param int      $size
2794
	 *
2795
	 * @return string
2796
	 */
2797
	private function mapiReadStream($stream, $size) {
2798
		if (!$stream || $size == 0) {
0 ignored issues
show
introduced by
$stream is of type resource, thus it always evaluated to false.
Loading history...
2799
			return "";
2800
		}
2801
2802
		return mapi_stream_read($stream, $size);
2803
	}
2804
2805
	/**
2806
	 * A wrapper for mapi_inetmapi_imtoinet function.
2807
	 *
2808
	 * @param MAPIMessage $mapimessage
2809
	 * @param SyncObject  $message
2810
	 *
2811
	 * @return bool
2812
	 */
2813
	private function imtoinet($mapimessage, &$message) {
2814
		$addrbook = $this->getAddressbook();
2815
		$stream = mapi_inetmapi_imtoinet($this->session, $addrbook, $mapimessage, ['use_tnef' => -1, 'ignore_missing_attachments' => 1]);
2816
		if (is_resource($stream)) {
2817
			$mstreamstat = mapi_stream_stat($stream);
2818
			$streamsize = $mstreamstat["cb"];
2819
			if (isset($streamsize)) {
2820
				if (Request::GetProtocolVersion() >= 12.0) {
2821
					if (!isset($message->asbody)) {
2822
						$message->asbody = new SyncBaseBody();
2823
					}
2824
					$message->asbody->data = MAPIStreamWrapper::Open($stream);
0 ignored issues
show
Bug introduced by
$stream of type resource is incompatible with the type mapistream expected by parameter $mapistream of MAPIStreamWrapper::Open(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2824
					$message->asbody->data = MAPIStreamWrapper::Open(/** @scrutinizer ignore-type */ $stream);
Loading history...
2825
					$message->asbody->estimatedDataSize = $streamsize;
2826
					$message->asbody->truncated = 0;
2827
				}
2828
				else {
2829
					$message->mimedata = MAPIStreamWrapper::Open($stream);
2830
					$message->mimesize = $streamsize;
2831
					$message->mimetruncated = 0;
2832
				}
2833
				unset($message->body, $message->bodytruncated);
2834
2835
				return true;
2836
			}
2837
		}
2838
		SLog::Write(LOGLEVEL_ERROR, "MAPIProvider->imtoinet(): got no stream or content from mapi_inetmapi_imtoinet()");
2839
2840
		return false;
2841
	}
2842
2843
	/**
2844
	 * Sets the message body.
2845
	 *
2846
	 * @param MAPIMessage       $mapimessage
2847
	 * @param ContentParameters $contentparameters
2848
	 * @param SyncObject        $message
2849
	 */
2850
	private function setMessageBody($mapimessage, $contentparameters, &$message) {
2851
		// get the available body preference types
2852
		$bpTypes = $contentparameters->GetBodyPreference();
2853
		if ($bpTypes !== false) {
2854
			SLog::Write(LOGLEVEL_DEBUG, sprintf("BodyPreference types: %s", implode(', ', $bpTypes)));
2855
			// do not send mime data if the client requests it
2856
			if (($contentparameters->GetMimeSupport() == SYNC_MIMESUPPORT_NEVER) && ($key = array_search(SYNC_BODYPREFERENCE_MIME, $bpTypes) !== false)) {
2857
				unset($bpTypes[$key]);
2858
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Remove mime body preference type because the device required no mime support. BodyPreference types: %s", implode(', ', $bpTypes)));
2859
			}
2860
			// get the best fitting preference type
2861
			$bpReturnType = Utils::GetBodyPreferenceBestMatch($bpTypes);
2862
			SLog::Write(LOGLEVEL_DEBUG, sprintf("GetBodyPreferenceBestMatch: %d", $bpReturnType));
2863
			$bpo = $contentparameters->BodyPreference($bpReturnType);
2864
			SLog::Write(LOGLEVEL_DEBUG, sprintf("bpo: truncation size:'%d', allornone:'%d', preview:'%d'", $bpo->GetTruncationSize(), $bpo->GetAllOrNone(), $bpo->GetPreview()));
2865
2866
			// Android Blackberry expects a full mime message for signed emails
2867
			// @see https://jira.z-hub.io/projects/ZP/issues/ZP-1154
2868
			// @TODO change this when refactoring
2869
			$props = mapi_getprops($mapimessage, [PR_MESSAGE_CLASS]);
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2870
			if (isset($props[PR_MESSAGE_CLASS]) &&
2871
					stripos($props[PR_MESSAGE_CLASS], 'IPM.Note.SMIME.MultipartSigned') !== false &&
2872
					($key = array_search(SYNC_BODYPREFERENCE_MIME, $bpTypes) !== false)) {
0 ignored issues
show
Unused Code introduced by
The assignment to $key is dead and can be removed.
Loading history...
2873
				SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->setMessageBody(): enforcing SYNC_BODYPREFERENCE_MIME type for a signed message"));
2874
				$bpReturnType = SYNC_BODYPREFERENCE_MIME;
2875
			}
2876
2877
			$this->setMessageBodyForType($mapimessage, $bpReturnType, $message);
2878
			// only set the truncation size data if device set it in request
2879
			if ($bpo->GetTruncationSize() != false &&
2880
					$bpReturnType != SYNC_BODYPREFERENCE_MIME &&
2881
					$message->asbody->estimatedDataSize > $bpo->GetTruncationSize()
2882
				) {
2883
				// 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
2884
				if ($bpReturnType == SYNC_BODYPREFERENCE_PLAIN) {
2885
					SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->setMessageBody(): truncated plain-text body requested, stripping all links and images");
2886
					// 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.
2887
					$plainbody = stream_get_contents($message->asbody->data, $bpo->GetTruncationSize() * 5);
2888
					$message->asbody->data = StringStreamWrapper::Open(preg_replace('/<http(s){0,1}:\/\/.*?>/i', '', $plainbody));
2889
				}
2890
2891
				// truncate data stream
2892
				ftruncate($message->asbody->data, $bpo->GetTruncationSize());
2893
				$message->asbody->truncated = 1;
2894
			}
2895
			// set the preview or windows phones won't show the preview of an email
2896
			if (Request::GetProtocolVersion() >= 14.0 && $bpo->GetPreview()) {
2897
				$message->asbody->preview = Utils::Utf8_truncate(MAPIUtils::readPropStream($mapimessage, PR_BODY), $bpo->GetPreview());
0 ignored issues
show
Bug introduced by
The constant PR_BODY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2898
			}
2899
		}
2900
		else {
2901
			// Override 'body' for truncation
2902
			$truncsize = Utils::GetTruncSize($contentparameters->GetTruncation());
0 ignored issues
show
Bug introduced by
The method GetTruncation() does not exist on ContentParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2902
			$truncsize = Utils::GetTruncSize($contentparameters->/** @scrutinizer ignore-call */ GetTruncation());
Loading history...
2903
			$this->setMessageBodyForType($mapimessage, SYNC_BODYPREFERENCE_PLAIN, $message);
2904
2905
			if ($message->bodysize > $truncsize) {
2906
				$message->body = Utils::Utf8_truncate($message->body, $truncsize);
2907
				$message->bodytruncated = 1;
2908
			}
2909
2910
			if (!isset($message->body) || strlen($message->body) == 0) {
2911
				$message->body = " ";
2912
			}
2913
2914
			if ($contentparameters->GetMimeSupport() == SYNC_MIMESUPPORT_ALWAYS) {
2915
				// set the html body for iphone in AS 2.5 version
2916
				$this->imtoinet($mapimessage, $message);
2917
			}
2918
		}
2919
	}
2920
2921
	/**
2922
	 * Sets properties for an email message.
2923
	 *
2924
	 * @param mixed    $mapimessage
2925
	 * @param SyncMail $message
2926
	 */
2927
	private function setFlag($mapimessage, &$message) {
2928
		// do nothing if protocol version is lower than 12.0 as flags haven't been defined before
2929
		if (Request::GetProtocolVersion() < 12.0) {
2930
			return;
2931
		}
2932
2933
		$message->flag = new SyncMailFlags();
2934
2935
		$this->getPropsFromMAPI($message->flag, $mapimessage, MAPIMapping::GetMailFlagsMapping());
2936
	}
2937
2938
	/**
2939
	 * Sets information from SyncBaseBody type for a MAPI message.
2940
	 *
2941
	 * @param SyncBaseBody $asbody
2942
	 * @param array        $props
2943
	 * @param array        $appointmentprops
2944
	 */
2945
	private function setASbody($asbody, &$props, $appointmentprops) {
2946
		// TODO: fix checking for the length
2947
		if (isset($asbody->type, $asbody->data)   /* && strlen($asbody->data) > 0 */) {
2948
			switch ($asbody->type) {
2949
				case SYNC_BODYPREFERENCE_PLAIN:
2950
				default:
2951
				// set plain body if the type is not in valid range
2952
					$props[$appointmentprops["body"]] = stream_get_contents($asbody->data);
2953
					break;
2954
2955
				case SYNC_BODYPREFERENCE_HTML:
2956
					$props[$appointmentprops["html"]] = stream_get_contents($asbody->data);
2957
					break;
2958
2959
				case SYNC_BODYPREFERENCE_RTF:
2960
					break;
2961
2962
				case SYNC_BODYPREFERENCE_MIME:
2963
					break;
2964
			}
2965
		}
2966
		else {
2967
			SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->setASbody either type or data are not set. Setting to empty body");
2968
			$props[$appointmentprops["body"]] = "";
2969
		}
2970
	}
2971
2972
	/**
2973
	 * Get MAPI addressbook object.
2974
	 *
2975
	 * @return MAPIAddressbook object to be used with mapi_ab_* or false on failure
0 ignored issues
show
Bug introduced by
The type MAPIAddressbook was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
2976
	 */
2977
	private function getAddressbook() {
2978
		if (isset($this->addressbook) && $this->addressbook) {
2979
			return $this->addressbook;
2980
		}
2981
		$this->addressbook = mapi_openaddressbook($this->session);
2982
		$result = mapi_last_hresult();
2983
		if ($result && $this->addressbook === false) {
2984
			SLog::Write(LOGLEVEL_ERROR, sprintf("MAPIProvider->getAddressbook error opening addressbook 0x%X", $result));
2985
2986
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type MAPIAddressbook.
Loading history...
2987
		}
2988
2989
		return $this->addressbook;
2990
	}
2991
2992
	/**
2993
	 * Gets the required store properties.
2994
	 *
2995
	 * @return array
2996
	 */
2997
	public function GetStoreProps() {
2998
		if (!isset($this->storeProps) || empty($this->storeProps)) {
2999
			SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->GetStoreProps(): Getting store properties.");
3000
			$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]);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_PUBLIC_FOLDERS_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_MAILBOX_OWNER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_FAVORITES_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_SUBTREE_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_OUTBOX_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_SENTMAIL_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_WASTEBASKET_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3001
			// make sure all properties are set
3002
			if (!isset($this->storeProps[PR_IPM_WASTEBASKET_ENTRYID])) {
3003
				$this->storeProps[PR_IPM_WASTEBASKET_ENTRYID] = false;
3004
			}
3005
			if (!isset($this->storeProps[PR_IPM_SENTMAIL_ENTRYID])) {
3006
				$this->storeProps[PR_IPM_SENTMAIL_ENTRYID] = false;
3007
			}
3008
			if (!isset($this->storeProps[PR_IPM_OUTBOX_ENTRYID])) {
3009
				$this->storeProps[PR_IPM_OUTBOX_ENTRYID] = false;
3010
			}
3011
			if (!isset($this->storeProps[PR_IPM_PUBLIC_FOLDERS_ENTRYID])) {
3012
				$this->storeProps[PR_IPM_PUBLIC_FOLDERS_ENTRYID] = false;
3013
			}
3014
		}
3015
3016
		return $this->storeProps;
3017
	}
3018
3019
	/**
3020
	 * Gets the required inbox properties.
3021
	 *
3022
	 * @return array
3023
	 */
3024
	public function GetInboxProps() {
3025
		if (!isset($this->inboxProps) || empty($this->inboxProps)) {
3026
			SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->GetInboxProps(): Getting inbox properties.");
3027
			$this->inboxProps = [];
3028
			$inbox = mapi_msgstore_getreceivefolder($this->store);
3029
			if ($inbox) {
3030
				$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]);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_TASK_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_JOURNAL_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_NOTE_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_CONTACT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_DRAFTS_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_APPOINTMENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3031
				// make sure all properties are set
3032
				if (!isset($this->inboxProps[PR_ENTRYID])) {
3033
					$this->inboxProps[PR_ENTRYID] = false;
3034
				}
3035
				if (!isset($this->inboxProps[PR_IPM_DRAFTS_ENTRYID])) {
3036
					$this->inboxProps[PR_IPM_DRAFTS_ENTRYID] = false;
3037
				}
3038
				if (!isset($this->inboxProps[PR_IPM_TASK_ENTRYID])) {
3039
					$this->inboxProps[PR_IPM_TASK_ENTRYID] = false;
3040
				}
3041
				if (!isset($this->inboxProps[PR_IPM_APPOINTMENT_ENTRYID])) {
3042
					$this->inboxProps[PR_IPM_APPOINTMENT_ENTRYID] = false;
3043
				}
3044
				if (!isset($this->inboxProps[PR_IPM_CONTACT_ENTRYID])) {
3045
					$this->inboxProps[PR_IPM_CONTACT_ENTRYID] = false;
3046
				}
3047
				if (!isset($this->inboxProps[PR_IPM_NOTE_ENTRYID])) {
3048
					$this->inboxProps[PR_IPM_NOTE_ENTRYID] = false;
3049
				}
3050
				if (!isset($this->inboxProps[PR_IPM_JOURNAL_ENTRYID])) {
3051
					$this->inboxProps[PR_IPM_JOURNAL_ENTRYID] = false;
3052
				}
3053
			}
3054
		}
3055
3056
		return $this->inboxProps;
3057
	}
3058
3059
	/**
3060
	 * Gets the required store root properties.
3061
	 *
3062
	 * @return array
3063
	 */
3064
	private function getRootProps() {
3065
		if (!isset($this->rootProps)) {
3066
			$root = mapi_msgstore_openentry($this->store, null);
3067
			$this->rootProps = mapi_getprops($root, [PR_ADDITIONAL_REN_ENTRYIDS_EX]);
0 ignored issues
show
Bug introduced by
The constant PR_ADDITIONAL_REN_ENTRYIDS_EX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3068
		}
3069
3070
		return $this->rootProps;
3071
	}
3072
3073
	/**
3074
	 * Returns an array with entryids of some special folders.
3075
	 *
3076
	 * @return array
3077
	 */
3078
	private function getSpecialFoldersData() {
3079
		// The persist data of an entry in PR_ADDITIONAL_REN_ENTRYIDS_EX consists of:
3080
		//      PersistId - e.g. RSF_PID_SUGGESTED_CONTACTS (2 bytes)
3081
		//      DataElementsSize - size of DataElements field (2 bytes)
3082
		//      DataElements - array of PersistElement structures (variable size)
3083
		//          PersistElement Structure consists of
3084
		//              ElementID - e.g. RSF_ELID_ENTRYID (2 bytes)
3085
		//              ElementDataSize - size of ElementData (2 bytes)
3086
		//              ElementData - The data for the special folder identified by the PersistID (variable size)
3087
		if (empty($this->specialFoldersData)) {
3088
			$this->specialFoldersData = [];
3089
			$rootProps = $this->getRootProps();
3090
			if (isset($rootProps[PR_ADDITIONAL_REN_ENTRYIDS_EX])) {
0 ignored issues
show
Bug introduced by
The constant PR_ADDITIONAL_REN_ENTRYIDS_EX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3091
				$persistData = $rootProps[PR_ADDITIONAL_REN_ENTRYIDS_EX];
3092
				while (strlen($persistData) > 0) {
3093
					// PERSIST_SENTINEL marks the end of the persist data
3094
					if (strlen($persistData) == 4 && $persistData == PERSIST_SENTINEL) {
3095
						break;
3096
					}
3097
					$unpackedData = unpack("vdataSize/velementID/velDataSize", substr($persistData, 2, 6));
3098
					if (isset($unpackedData['dataSize'], $unpackedData['elementID']) && $unpackedData['elementID'] == RSF_ELID_ENTRYID && isset($unpackedData['elDataSize'])) {
3099
						$this->specialFoldersData[] = substr($persistData, 8, $unpackedData['elDataSize']);
3100
						// Add PersistId and DataElementsSize lengths to the data size as they're not part of it
3101
						$persistData = substr($persistData, $unpackedData['dataSize'] + 4);
3102
					}
3103
					else {
3104
						SLog::Write(LOGLEVEL_INFO, "MAPIProvider->getSpecialFoldersData(): persistent data is not valid");
3105
						break;
3106
					}
3107
				}
3108
			}
3109
		}
3110
3111
		return $this->specialFoldersData;
3112
	}
3113
3114
	/**
3115
	 * Extracts email address from PR_SEARCH_KEY property if possible.
3116
	 *
3117
	 * @param string $searchKey
3118
	 *
3119
	 * @see https://jira.z-hub.io/browse/ZP-1178
3120
	 *
3121
	 * @return string
3122
	 */
3123
	private function getEmailAddressFromSearchKey($searchKey) {
3124
		if (strpos($searchKey, ':') !== false && strpos($searchKey, '@') !== false) {
3125
			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");
3126
3127
			return trim(strtolower(explode(':', $searchKey)[1]));
3128
		}
3129
3130
		return "";
3131
	}
3132
3133
	/**
3134
	 * Returns categories for a message.
3135
	 *
3136
	 * @param binary $parentsourcekey
3137
	 * @param binary $sourcekey
3138
	 *
3139
	 * @return array or false on failure
3140
	 */
3141
	public function GetMessageCategories($parentsourcekey, $sourcekey) {
3142
		$entryid = mapi_msgstore_entryidfromsourcekey($this->store, $parentsourcekey, $sourcekey);
3143
		if (!$entryid) {
3144
			SLog::Write(LOGLEVEL_INFO, sprintf("MAPIProvider->GetMessageCategories(): Couldn't retrieve message, sourcekey: '%s', parentsourcekey: '%s'", bin2hex($sourcekey), bin2hex($parentsourcekey)));
3145
3146
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
3147
		}
3148
		$mapimessage = mapi_msgstore_openentry($this->store, $entryid);
3149
		$emailMapping = MAPIMapping::GetEmailMapping();
3150
		$emailMapping = ["categories" => $emailMapping["categories"]];
3151
		$messageCategories = $this->getProps($mapimessage, $emailMapping);
3152
		if (isset($messageCategories[$emailMapping["categories"]]) && is_array($messageCategories[$emailMapping["categories"]])) {
3153
			return $messageCategories[$emailMapping["categories"]];
3154
		}
3155
3156
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
3157
	}
3158
}
3159