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

MAPIProvider::setMailingAddress()   B

Complexity

Conditions 7
Paths 64

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 12
nop 8
dl 0
loc 18
rs 8.8333
c 0
b 0
f 0
nc 64

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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