Passed
Branch master (f497d2)
by Mike
03:18
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]);
1 ignored issue
show
Bug introduced by
The function mapi_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

46
		$props = /** @scrutinizer ignore-call */ mapi_getprops($mapimessage, [PR_MESSAGE_CLASS]);
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);
1 ignored issue
show
Bug introduced by
The function mapi_message_getattachmenttable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

94
			$attachtable = /** @scrutinizer ignore-call */ mapi_message_getattachmenttable($mapimessage);
Loading history...
95
			mapi_table_restrict($attachtable, MAPIUtils::GetContactPicRestriction());
1 ignored issue
show
Bug introduced by
The function mapi_table_restrict was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

95
			/** @scrutinizer ignore-call */ 
96
   mapi_table_restrict($attachtable, MAPIUtils::GetContactPicRestriction());
Loading history...
96
			$rows = mapi_table_queryallrows($attachtable, [PR_ATTACH_NUM, PR_ATTACH_SIZE]);
1 ignored issue
show
Bug introduced by
The function mapi_table_queryallrows was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

96
			$rows = /** @scrutinizer ignore-call */ mapi_table_queryallrows($attachtable, [PR_ATTACH_NUM, PR_ATTACH_SIZE]);
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]);
1 ignored issue
show
Bug introduced by
The function mapi_message_openattach was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

100
					$mapiattach = /** @scrutinizer ignore-call */ mapi_message_openattach($mapimessage, $row[PR_ATTACH_NUM]);
Loading history...
101
					$message->picture = base64_encode(mapi_attach_openbin($mapiattach, PR_ATTACH_DATA_BIN));
1 ignored issue
show
Bug introduced by
The function mapi_attach_openbin was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

101
					$message->picture = base64_encode(/** @scrutinizer ignore-call */ mapi_attach_openbin($mapiattach, PR_ATTACH_DATA_BIN));
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);
1 ignored issue
show
Bug introduced by
The function mapi_message_getrecipienttable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

231
		$reciptable = /** @scrutinizer ignore-call */ mapi_message_getrecipienttable($mapimessage);
Loading history...
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);
1 ignored issue
show
Bug introduced by
The function mapi_table_queryrows was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

233
		$rows = /** @scrutinizer ignore-call */ 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);
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]);
1 ignored issue
show
Bug introduced by
The function nsp_getuserinfo was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

263
					$userinfo = @/** @scrutinizer ignore-call */ nsp_getuserinfo($row[PR_EMAIL_ADDRESS]);
Loading history...
264
					if (is_array($userinfo) && isset($userinfo["primary_email"])) {
265
						$attendee->email = w2u($userinfo["primary_email"]);
266
					}
267
					// if the user was not found, do a fallback to PR_SEARCH_KEY
268
					// @see https://jira.z-hub.io/browse/ZP-1178
269
					elseif (isset($row[PR_SEARCH_KEY])) {
270
						$attendee->email = w2u($this->getEmailAddressFromSearchKey($row[PR_SEARCH_KEY]));
271
					}
272
					else {
273
						SLog::Write(LOGLEVEL_WARN, sprintf("MAPIProvider->getAppointment: The attendee '%s' of type ZARAFA can not be resolved. Code: 0x%X", $row[PR_EMAIL_ADDRESS], mapi_last_hresult()));
1 ignored issue
show
Bug introduced by
The function mapi_last_hresult was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

273
						SLog::Write(LOGLEVEL_WARN, sprintf("MAPIProvider->getAppointment: The attendee '%s' of type ZARAFA can not be resolved. Code: 0x%X", $row[PR_EMAIL_ADDRESS], /** @scrutinizer ignore-call */ mapi_last_hresult()));
Loading history...
274
					}
275
				}
276
			}
277
278
			// set attendee's status and type if they're available and if we are the organizer
279
			$storeprops = $this->GetStoreProps();
280
			if (isset($row[PR_RECIPIENT_TRACKSTATUS], $messageprops[$appointmentprops["representingentryid"]], $storeprops[PR_MAILBOX_OWNER_ENTRYID]) &&
281
					$messageprops[$appointmentprops["representingentryid"]] == $storeprops[PR_MAILBOX_OWNER_ENTRYID]) {
282
				$attendee->attendeestatus = $row[PR_RECIPIENT_TRACKSTATUS];
283
			}
284
			if (isset($row[PR_RECIPIENT_TYPE])) {
285
				$attendee->attendeetype = $row[PR_RECIPIENT_TYPE];
286
			}
287
			// Some attendees have no email or name (eg resources), and if you
288
			// don't send one of those fields, the phone will give an error ... so
289
			// we don't send it in that case.
290
			// also ignore the "attendee" if the email is equal to the organizers' email
291
			if (isset($attendee->name, $attendee->email) && $attendee->email != "" && (!isset($message->organizeremail) || (isset($message->organizeremail) && $attendee->email != $message->organizeremail))) {
292
				array_push($message->attendees, $attendee);
293
			}
294
		}
295
296
		// Status 0 = no meeting, status 1 = organizer, status 2/3/4/5 = tentative/accepted/declined/notresponded
297
		if (isset($messageprops[$appointmentprops["meetingstatus"]]) && $messageprops[$appointmentprops["meetingstatus"]] > 1) {
298
			if (!isset($message->attendees) || !is_array($message->attendees)) {
299
				$message->attendees = [];
300
			}
301
			// Work around iOS6 cancellation issue when there are no attendees for this meeting. Just add ourselves as the sole attendee.
302
			if (count($message->attendees) == 0) {
303
				SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->getAppointment: adding ourself as an attendee for iOS6 workaround"));
304
				$attendee = new SyncAttendee();
305
306
				$meinfo = nsp_getuserinfo(Request::GetUser());
307
308
				if (is_array($meinfo)) {
309
					$attendee->email = w2u($meinfo["primary_email"]);
310
					$attendee->name = w2u($meinfo["fullname"]);
311
					$attendee->attendeetype = MAPI_TO;
312
313
					array_push($message->attendees, $attendee);
314
				}
315
			}
316
			$message->responsetype = $messageprops[$appointmentprops["responsestatus"]];
317
		}
318
319
		// If it's an appointment which doesn't have any attendees, we have to make sure that
320
		// the user is the owner or it will not work properly with android devices
321
		// @see https://jira.z-hub.io/browse/ZP-1020
322
		if (isset($messageprops[$appointmentprops["meetingstatus"]]) && $messageprops[$appointmentprops["meetingstatus"]] == olNonMeeting && empty($message->attendees)) {
323
			$meinfo = nsp_getuserinfo(Request::GetUser());
324
325
			if (is_array($meinfo)) {
326
				$message->organizeremail = w2u($meinfo["primary_email"]);
327
				$message->organizername = w2u($meinfo["fullname"]);
328
				SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->getAppointment(): setting ourself as the organizer for an appointment without attendees.");
329
			}
330
		}
331
332
		if (!isset($message->nativebodytype)) {
333
			$message->nativebodytype = MAPIUtils::GetNativeBodyType($messageprops);
334
		}
335
		elseif ($message->nativebodytype == SYNC_BODYPREFERENCE_UNDEFINED) {
336
			$nbt = MAPIUtils::GetNativeBodyType($messageprops);
337
			SLog::Write(LOGLEVEL_INFO, sprintf("MAPIProvider->getAppointment(): native body type is undefined. Set it to %d.", $nbt));
338
			$message->nativebodytype = $nbt;
339
		}
340
341
		// If the user is working from a location other than the office the busystatus should be interpreted as free.
342
		if (isset($message->busystatus) && $message->busystatus == fbWorkingElsewhere) {
343
			$message->busystatus = fbFree;
344
		}
345
346
		// If the busystatus has the value of -1, we should be interpreted as tentative (1) / ZP-581
347
		if (isset($message->busystatus) && $message->busystatus == -1) {
348
			$message->busystatus = fbTentative;
349
		}
350
351
		// All-day events might appear as 24h (or multiple of it) long when they start not exactly at midnight (+/- bias of the timezone)
352
		if (isset($message->alldayevent) && $message->alldayevent) {
353
			$localStartTime = localtime($message->starttime, 1);
354
355
			// The appointment is all-day but doesn't start at midnight.
356
			// If it was created in another timezone and we have that information,
357
			// set the startime to the midnight of the current timezone.
358
			if ($appTz && ($localStartTime['tm_hour'] || $localStartTime['tm_min'])) {
359
				SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->getAppointment(): all-day event starting not midnight.");
360
				$duration = $message->endtime - $message->starttime;
361
				$serverTz = TimezoneUtil::GetFullTZ();
362
				$message->starttime = $this->getGMTTimeByTZ($this->getLocaltimeByTZ($message->starttime, $tz), $serverTz);
363
				$message->endtime = $message->starttime + $duration;
364
			}
365
		}
366
367
		return $message;
368
	}
369
370
	/**
371
	 * Reads recurrence information from MAPI.
372
	 *
373
	 * @param mixed      $mapimessage
374
	 * @param array      $recurprops
375
	 * @param SyncObject &$syncMessage    the message
376
	 * @param SyncObject &$syncRecurrence the  recurrence message
377
	 * @param array      $tz              timezone information
378
	 *
379
	 * @return
380
	 */
381
	private function getRecurrence($mapimessage, $recurprops, &$syncMessage, &$syncRecurrence, $tz) {
382
		if ($syncRecurrence instanceof SyncTaskRecurrence) {
383
			$recurrence = new TaskRecurrence($this->store, $mapimessage);
384
		}
385
		else {
386
			$recurrence = new Recurrence($this->store, $mapimessage);
387
		}
388
389
		switch ($recurrence->recur["type"]) {
390
			case 10: // daily
391
				switch ($recurrence->recur["subtype"]) {
392
					default:
393
						$syncRecurrence->type = 0;
394
						break;
395
396
					case 1:
397
						$syncRecurrence->type = 0;
398
						$syncRecurrence->dayofweek = 62; // mon-fri
399
						$syncRecurrence->interval = 1;
400
						break;
401
				}
402
				break;
403
404
			case 11: // weekly
405
				$syncRecurrence->type = 1;
406
				break;
407
408
			case 12: // monthly
409
				switch ($recurrence->recur["subtype"]) {
410
					default:
411
						$syncRecurrence->type = 2;
412
						break;
413
414
					case 3:
415
						$syncRecurrence->type = 3;
416
						break;
417
				}
418
				break;
419
420
			case 13: // yearly
421
				switch ($recurrence->recur["subtype"]) {
422
					default:
423
						$syncRecurrence->type = 4;
424
						break;
425
426
					case 2:
427
						$syncRecurrence->type = 5;
428
						break;
429
430
					case 3:
431
						$syncRecurrence->type = 6;
432
						break;
433
				}
434
		}
435
		// Termination
436
		switch ($recurrence->recur["term"]) {
437
			case 0x21:
438
				$syncRecurrence->until = $recurrence->recur["end"];
439
				// fixes Mantis #350 : recur-end does not consider timezones - use ClipEnd if available
440
				if (isset($recurprops[$recurrence->proptags["enddate_recurring"]])) {
441
					$syncRecurrence->until = $recurprops[$recurrence->proptags["enddate_recurring"]];
442
				}
443
				// add one day (minus 1 sec) to the end time to make sure the last occurrence is covered
444
				$syncRecurrence->until += 86399;
445
				break;
446
447
			case 0x22:
448
				$syncRecurrence->occurrences = $recurrence->recur["numoccur"];
449
				break;
450
451
			case 0x23:
452
				// never ends
453
				break;
454
		}
455
456
		// Correct 'alldayevent' because outlook fails to set it on recurring items of 24 hours or longer
457
		if (isset($recurrence->recur["endocc"], $recurrence->recur["startocc"]) && ($recurrence->recur["endocc"] - $recurrence->recur["startocc"] >= 1440)) {
458
			$syncMessage->alldayevent = true;
459
		}
460
461
		// Interval is different according to the type/subtype
462
		switch ($recurrence->recur["type"]) {
463
			case 10:
464
				if ($recurrence->recur["subtype"] == 0) {
465
					$syncRecurrence->interval = (int) ($recurrence->recur["everyn"] / 1440);
466
				}  // minutes
467
				break;
468
469
			case 11:
470
			case 12:
471
				$syncRecurrence->interval = $recurrence->recur["everyn"];
472
				break; // months / weeks
473
474
			case 13:
475
				$syncRecurrence->interval = (int) ($recurrence->recur["everyn"] / 12);
476
				break; // months
477
		}
478
479
		if (isset($recurrence->recur["weekdays"])) {
480
			$syncRecurrence->dayofweek = $recurrence->recur["weekdays"];
481
		} // bitmask of days (1 == sunday, 128 == saturday
482
		if (isset($recurrence->recur["nday"])) {
483
			$syncRecurrence->weekofmonth = $recurrence->recur["nday"];
484
		} // N'th {DAY} of {X} (0-5)
485
		if (isset($recurrence->recur["month"])) {
486
			$syncRecurrence->monthofyear = (int) ($recurrence->recur["month"] / (60 * 24 * 29)) + 1;
487
		} // works ok due to rounding. see also $monthminutes below (1-12)
488
		if (isset($recurrence->recur["monthday"])) {
489
			$syncRecurrence->dayofmonth = $recurrence->recur["monthday"];
490
		} // day of month (1-31)
491
492
		// All changed exceptions are appointments within the 'exceptions' array. They contain the same items as a normal appointment
493
		foreach ($recurrence->recur["changed_occurrences"] as $change) {
494
			$exception = new SyncAppointmentException();
495
496
			// start, end, basedate, subject, remind_before, reminderset, location, busystatus, alldayevent, label
497
			if (isset($change["start"])) {
498
				$exception->starttime = $this->getGMTTimeByTZ($change["start"], $tz);
499
			}
500
			if (isset($change["end"])) {
501
				$exception->endtime = $this->getGMTTimeByTZ($change["end"], $tz);
502
			}
503
			if (isset($change["basedate"])) {
504
				$exception->exceptionstarttime = $this->getGMTTimeByTZ($this->getDayStartOfTimestamp($change["basedate"]) + $recurrence->recur["startocc"] * 60, $tz);
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);
1 ignored issue
show
Bug introduced by
The function mapi_attach_openobj was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

509
					$exceptionobj = /** @scrutinizer ignore-call */ mapi_attach_openobj($exceptionatt, 0);
Loading history...
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])) {
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->isMeetingRequestResponse()) {
769
						$req->processMeetingRequestResponse();
770
					}
771
					if ($req->isMeetingCancellation()) {
772
						$req->processMeetingCancellation();
773
					}
774
				}
775
			}
776
			$message->contentclass = DEFAULT_CALENDAR_CONTENTCLASS;
777
778
			// MeetingMessageType values
779
			// 0 = A silent update was performed, or the message type is unspecified.
780
			// 1 = Initial meeting request.
781
			// 2 = Full update.
782
			// 3 = Informational update.
783
			// 4 = Outdated. A newer meeting request or meeting update was received after this message.
784
			// 5 = Identifies the delegator's copy of the meeting request.
785
			// 6 = Identifies that the meeting request has been delegated and the meeting request cannot be responded to.
786
			$message->meetingrequest->meetingmessagetype = mtgEmpty;
787
788
			if (isset($props[$meetingrequestproperties["meetingType"]])) {
789
				switch ($props[$meetingrequestproperties["meetingType"]]) {
790
					case mtgRequest:
791
						$message->meetingrequest->meetingmessagetype = 1;
792
						break;
793
794
					case mtgFull:
795
						$message->meetingrequest->meetingmessagetype = 2;
796
						break;
797
798
					case mtgInfo:
799
						$message->meetingrequest->meetingmessagetype = 3;
800
						break;
801
802
					case mtgOutOfDate:
803
						$message->meetingrequest->meetingmessagetype = 4;
804
						break;
805
806
					case mtgDelegatorCopy:
807
						$message->meetingrequest->meetingmessagetype = 5;
808
						break;
809
				}
810
			}
811
		}
812
813
		// Add attachments
814
		$attachtable = mapi_message_getattachmenttable($mapimessage);
1 ignored issue
show
Bug introduced by
The function mapi_message_getattachmenttable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

814
		$attachtable = /** @scrutinizer ignore-call */ mapi_message_getattachmenttable($mapimessage);
Loading history...
815
		$rows = mapi_table_queryallrows($attachtable, [PR_ATTACH_NUM]);
1 ignored issue
show
Bug introduced by
The function mapi_table_queryallrows was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

815
		$rows = /** @scrutinizer ignore-call */ mapi_table_queryallrows($attachtable, [PR_ATTACH_NUM]);
Loading history...
816
		$entryid = bin2hex($messageprops[$emailproperties["entryid"]]);
817
		$parentSourcekey = bin2hex($messageprops[$emailproperties["parentsourcekey"]]);
818
819
		foreach ($rows as $row) {
820
			if (isset($row[PR_ATTACH_NUM])) {
821
				if (Request::GetProtocolVersion() >= 12.0) {
822
					$attach = new SyncBaseAttachment();
823
				}
824
				else {
825
					$attach = new SyncAttachment();
826
				}
827
828
				$mapiattach = mapi_message_openattach($mapimessage, $row[PR_ATTACH_NUM]);
1 ignored issue
show
Bug introduced by
The function mapi_message_openattach was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

828
				$mapiattach = /** @scrutinizer ignore-call */ mapi_message_openattach($mapimessage, $row[PR_ATTACH_NUM]);
Loading history...
829
				$attachprops = mapi_getprops($mapiattach, [PR_ATTACH_LONG_FILENAME, PR_ATTACH_FILENAME, PR_ATTACHMENT_HIDDEN, PR_ATTACH_CONTENT_ID, PR_ATTACH_CONTENT_ID_W, PR_ATTACH_MIME_TAG, PR_ATTACH_MIME_TAG_W, PR_ATTACH_METHOD, PR_DISPLAY_NAME, PR_DISPLAY_NAME_W, PR_ATTACH_SIZE, PR_ATTACH_FLAGS]);
1 ignored issue
show
Bug introduced by
The function mapi_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

829
				$attachprops = /** @scrutinizer ignore-call */ mapi_getprops($mapiattach, [PR_ATTACH_LONG_FILENAME, PR_ATTACH_FILENAME, PR_ATTACHMENT_HIDDEN, PR_ATTACH_CONTENT_ID, PR_ATTACH_CONTENT_ID_W, PR_ATTACH_MIME_TAG, PR_ATTACH_MIME_TAG_W, PR_ATTACH_METHOD, PR_DISPLAY_NAME, PR_DISPLAY_NAME_W, PR_ATTACH_SIZE, PR_ATTACH_FLAGS]);
Loading history...
830
				if ((isset($attachprops[PR_ATTACH_MIME_TAG]) && strpos(strtolower($attachprops[PR_ATTACH_MIME_TAG]), 'signed') !== false) ||
831
					(isset($attachprops[PR_ATTACH_MIME_TAG_W]) && strpos(strtolower($attachprops[PR_ATTACH_MIME_TAG_W]), 'signed') !== false)) {
832
					continue;
833
				}
834
835
				// the displayname is handled equally for all AS versions
836
				$attach->displayname = w2u((isset($attachprops[PR_ATTACH_LONG_FILENAME])) ? $attachprops[PR_ATTACH_LONG_FILENAME] : ((isset($attachprops[PR_ATTACH_FILENAME])) ? $attachprops[PR_ATTACH_FILENAME] : ((isset($attachprops[PR_DISPLAY_NAME])) ? $attachprops[PR_DISPLAY_NAME] : "attachment.bin")));
837
				// fix attachment name in case of inline images
838
				if (($attach->displayname == "inline.txt" && (isset($attachprops[PR_ATTACH_MIME_TAG]) || $attachprops[PR_ATTACH_MIME_TAG_W])) ||
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($attach->displayname ==...at', -4, 4, true) === 0, Probably Intended Meaning: $attach->displayname == ...t', -4, 4, true) === 0)
Loading history...
839
				(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

839
				(substr_compare(/** @scrutinizer ignore-type */ $attach->displayname, "attachment", 0, 10, true) === 0 && substr_compare($attach->displayname, ".dat", -4, 4, true) === 0)) {
Loading history...
840
					$mimetype = (isset($attachprops[PR_ATTACH_MIME_TAG])) ? $attachprops[PR_ATTACH_MIME_TAG] : $attachprops[PR_ATTACH_MIME_TAG_W];
841
					$mime = explode("/", $mimetype);
842
843
					if (count($mime) == 2 && $mime[0] == "image") {
844
						$attach->displayname = "inline." . $mime[1];
845
					}
846
				}
847
848
				// set AS version specific parameters
849
				if (Request::GetProtocolVersion() >= 12.0) {
850
					$attach->filereference = sprintf("%s:%s:%s", $entryid, $row[PR_ATTACH_NUM], $parentSourcekey);
0 ignored issues
show
Bug introduced by
The property filereference does not seem to exist on SyncAttachment.
Loading history...
851
					$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...
852
853
					// if displayname does not have the eml extension for embedde messages, android and WP devices won't open it
854
					if ($attach->method == ATTACH_EMBEDDED_MSG) {
855
						if (strtolower(substr($attach->displayname, -4)) != '.eml') {
856
							$attach->displayname .= '.eml';
857
						}
858
					}
859
					// android devices require attachment size in order to display an attachment properly
860
					if (!isset($attachprops[PR_ATTACH_SIZE])) {
861
						$stream = mapi_openproperty($mapiattach, PR_ATTACH_DATA_BIN, IID_IStream, 0, 0);
1 ignored issue
show
Bug introduced by
The function mapi_openproperty was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

861
						$stream = /** @scrutinizer ignore-call */ mapi_openproperty($mapiattach, PR_ATTACH_DATA_BIN, IID_IStream, 0, 0);
Loading history...
862
						// It's not possible to open some (embedded only?) messages, so we need to open the attachment object itself to get the data
863
						if (mapi_last_hresult()) {
1 ignored issue
show
Bug introduced by
The function mapi_last_hresult was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

863
						if (/** @scrutinizer ignore-call */ mapi_last_hresult()) {
Loading history...
864
							$embMessage = mapi_attach_openobj($mapiattach);
1 ignored issue
show
Bug introduced by
The function mapi_attach_openobj was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

864
							$embMessage = /** @scrutinizer ignore-call */ mapi_attach_openobj($mapiattach);
Loading history...
865
							$addrbook = $this->getAddressbook();
866
							$stream = mapi_inetmapi_imtoinet($this->session, $addrbook, $embMessage, ['use_tnef' => -1]);
1 ignored issue
show
Bug introduced by
The function mapi_inetmapi_imtoinet was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

866
							$stream = /** @scrutinizer ignore-call */ mapi_inetmapi_imtoinet($this->session, $addrbook, $embMessage, ['use_tnef' => -1]);
Loading history...
867
						}
868
						$stat = mapi_stream_stat($stream);
1 ignored issue
show
Bug introduced by
The function mapi_stream_stat was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

868
						$stat = /** @scrutinizer ignore-call */ mapi_stream_stat($stream);
Loading history...
869
						$attach->estimatedDataSize = $stat['cb'];
0 ignored issues
show
Bug introduced by
The property estimatedDataSize does not seem to exist on SyncAttachment.
Loading history...
870
					}
871
					else {
872
						$attach->estimatedDataSize = $attachprops[PR_ATTACH_SIZE];
873
					}
874
875
					if (isset($attachprops[PR_ATTACH_CONTENT_ID]) && $attachprops[PR_ATTACH_CONTENT_ID]) {
876
						$attach->contentid = $attachprops[PR_ATTACH_CONTENT_ID];
0 ignored issues
show
Bug introduced by
The property contentid does not exist on SyncAttachment. Did you mean content?
Loading history...
877
					}
878
879
					if (!isset($attach->contentid) && isset($attachprops[PR_ATTACH_CONTENT_ID_W]) && $attachprops[PR_ATTACH_CONTENT_ID_W]) {
880
						$attach->contentid = $attachprops[PR_ATTACH_CONTENT_ID_W];
881
					}
882
883
					if (isset($attachprops[PR_ATTACHMENT_HIDDEN]) && $attachprops[PR_ATTACHMENT_HIDDEN]) {
884
						$attach->isinline = 1;
0 ignored issues
show
Bug introduced by
The property isinline does not seem to exist on SyncAttachment.
Loading history...
885
					}
886
887
					if (isset($attach->contentid, $attachprops[PR_ATTACH_FLAGS]) && $attachprops[PR_ATTACH_FLAGS] & 4) {
888
						$attach->isinline = 1;
889
					}
890
891
					if (!isset($message->asattachments)) {
892
						$message->asattachments = [];
893
					}
894
895
					array_push($message->asattachments, $attach);
896
				}
897
				else {
898
					$attach->attsize = $attachprops[PR_ATTACH_SIZE];
0 ignored issues
show
Bug introduced by
The property attsize does not seem to exist on SyncBaseAttachment.
Loading history...
899
					$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...
900
					if (!isset($message->attachments)) {
901
						$message->attachments = [];
902
					}
903
904
					array_push($message->attachments, $attach);
905
				}
906
			}
907
		}
908
909
		// Get To/Cc as SMTP addresses (this is different from displayto and displaycc because we are putting
910
		// in the SMTP addresses as well, while displayto and displaycc could just contain the display names
911
		$message->to = [];
912
		$message->cc = [];
913
914
		$reciptable = mapi_message_getrecipienttable($mapimessage);
1 ignored issue
show
Bug introduced by
The function mapi_message_getrecipienttable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

914
		$reciptable = /** @scrutinizer ignore-call */ mapi_message_getrecipienttable($mapimessage);
Loading history...
915
		$rows = mapi_table_queryallrows($reciptable, [PR_RECIPIENT_TYPE, PR_DISPLAY_NAME, PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS, PR_ENTRYID, PR_SEARCH_KEY]);
916
917
		foreach ($rows as $row) {
918
			$address = "";
919
			$fulladdr = "";
920
921
			$addrtype = isset($row[PR_ADDRTYPE]) ? $row[PR_ADDRTYPE] : "";
922
923
			if (isset($row[PR_SMTP_ADDRESS])) {
924
				$address = $row[PR_SMTP_ADDRESS];
925
			}
926
			elseif ($addrtype == "SMTP" && isset($row[PR_EMAIL_ADDRESS])) {
927
				$address = $row[PR_EMAIL_ADDRESS];
928
			}
929
			elseif ($addrtype == "ZARAFA" && isset($row[PR_ENTRYID])) {
930
				$address = $this->getSMTPAddressFromEntryID($row[PR_ENTRYID]);
931
			}
932
933
			// if the user was not found, do a fallback to PR_SEARCH_KEY
934
			// @see https://jira.z-hub.io/browse/ZP-1178
935
			if (empty($address) && isset($row[PR_SEARCH_KEY])) {
936
				$address = $this->getEmailAddressFromSearchKey($row[PR_SEARCH_KEY]);
937
			}
938
939
			$name = isset($row[PR_DISPLAY_NAME]) ? $row[PR_DISPLAY_NAME] : "";
940
941
			if ($name == "" || $name == $address) {
942
				$fulladdr = w2u($address);
943
			}
944
			else {
945
				if (substr($name, 0, 1) != '"' && substr($name, -1) != '"') {
946
					$fulladdr = "\"" . w2u($name) . "\" <" . w2u($address) . ">";
0 ignored issues
show
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

946
					$fulladdr = "\"" . /** @scrutinizer ignore-type */ w2u($name) . "\" <" . w2u($address) . ">";
Loading history...
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

946
					$fulladdr = "\"" . w2u($name) . "\" <" . /** @scrutinizer ignore-type */ w2u($address) . ">";
Loading history...
947
				}
948
				else {
949
					$fulladdr = w2u($name) . "<" . w2u($address) . ">";
950
				}
951
			}
952
953
			if ($row[PR_RECIPIENT_TYPE] == MAPI_TO) {
954
				array_push($message->to, $fulladdr);
955
			}
956
			elseif ($row[PR_RECIPIENT_TYPE] == MAPI_CC) {
957
				array_push($message->cc, $fulladdr);
958
			}
959
		}
960
961
		if (is_array($message->to) && !empty($message->to)) {
962
			$message->to = implode(", ", $message->to);
963
		}
964
		if (is_array($message->cc) && !empty($message->cc)) {
965
			$message->cc = implode(", ", $message->cc);
966
		}
967
968
		// without importance some mobiles assume "0" (low) - Mantis #439
969
		if (!isset($message->importance)) {
970
			$message->importance = IMPORTANCE_NORMAL;
971
		}
972
973
		if (!isset($message->internetcpid)) {
974
			$message->internetcpid = (defined('STORE_INTERNET_CPID')) ? constant('STORE_INTERNET_CPID') : INTERNET_CPID_WINDOWS1252;
975
		}
976
		$this->setFlag($mapimessage, $message);
977
		// TODO checkcontentclass
978
		if (!isset($message->contentclass)) {
979
			$message->contentclass = DEFAULT_EMAIL_CONTENTCLASS;
980
		}
981
982
		if (!isset($message->nativebodytype)) {
983
			$message->nativebodytype = MAPIUtils::GetNativeBodyType($messageprops);
984
		}
985
		elseif ($message->nativebodytype == SYNC_BODYPREFERENCE_UNDEFINED) {
986
			$nbt = MAPIUtils::GetNativeBodyType($messageprops);
987
			SLog::Write(LOGLEVEL_INFO, sprintf("MAPIProvider->getEmail(): native body type is undefined. Set it to %d.", $nbt));
988
			$message->nativebodytype = $nbt;
989
		}
990
991
		// reply, reply to all, forward flags
992
		if (isset($message->lastverbexecuted) && $message->lastverbexecuted) {
993
			$message->lastverbexecuted = Utils::GetLastVerbExecuted($message->lastverbexecuted);
994
		}
995
996
		return $message;
997
	}
998
999
	/**
1000
	 * Reads a note object from MAPI.
1001
	 *
1002
	 * @param mixed             $mapimessage
1003
	 * @param ContentParameters $contentparameters
1004
	 *
1005
	 * @return SyncNote
1006
	 */
1007
	private function getNote($mapimessage, $contentparameters) {
1008
		$message = new SyncNote();
1009
1010
		// Standard one-to-one mappings first
1011
		$this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetNoteMapping());
1012
1013
		// set the body according to contentparameters and supported AS version
1014
		$this->setMessageBody($mapimessage, $contentparameters, $message);
1015
1016
		return $message;
1017
	}
1018
1019
	/**
1020
	 * Creates a SyncFolder from MAPI properties.
1021
	 *
1022
	 * @param mixed $folderprops
1023
	 *
1024
	 * @return SyncFolder
1025
	 */
1026
	public function GetFolder($folderprops) {
1027
		$folder = new SyncFolder();
1028
1029
		$storeprops = $this->GetStoreProps();
1030
1031
		// For ZCP 7.0.x we need to retrieve more properties explicitly, see ZP-780
1032
		if (isset($folderprops[PR_SOURCE_KEY]) && !isset($folderprops[PR_ENTRYID]) && !isset($folderprops[PR_CONTAINER_CLASS])) {
1033
			$entryid = mapi_msgstore_entryidfromsourcekey($this->store, $folderprops[PR_SOURCE_KEY]);
1 ignored issue
show
Bug introduced by
The function mapi_msgstore_entryidfromsourcekey was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1033
			$entryid = /** @scrutinizer ignore-call */ mapi_msgstore_entryidfromsourcekey($this->store, $folderprops[PR_SOURCE_KEY]);
Loading history...
1034
			$mapifolder = mapi_msgstore_openentry($this->store, $entryid);
1 ignored issue
show
Bug introduced by
The function mapi_msgstore_openentry was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1034
			$mapifolder = /** @scrutinizer ignore-call */ mapi_msgstore_openentry($this->store, $entryid);
Loading history...
1035
			$folderprops = mapi_getprops($mapifolder, [PR_DISPLAY_NAME, PR_PARENT_ENTRYID, PR_ENTRYID, PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY, PR_CONTAINER_CLASS, PR_ATTR_HIDDEN, PR_EXTENDED_FOLDER_FLAGS]);
1 ignored issue
show
Bug introduced by
The function mapi_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1035
			$folderprops = /** @scrutinizer ignore-call */ 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]);
Loading history...
1036
			SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->GetFolder(): received insufficient of data from ICS. Fetching required data.");
1037
		}
1038
1039
		if (!isset(
1040
				$folderprops[PR_DISPLAY_NAME],
1041
				$folderprops[PR_PARENT_ENTRYID],
1042
				$folderprops[PR_SOURCE_KEY],
1043
				$folderprops[PR_ENTRYID],
1044
				$folderprops[PR_PARENT_SOURCE_KEY],
1045
				$storeprops[PR_IPM_SUBTREE_ENTRYID])) {
1046
			SLog::Write(LOGLEVEL_ERROR, "MAPIProvider->GetFolder(): invalid folder. Missing properties");
1047
1048
			return false;
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...
1049
		}
1050
1051
		// ignore hidden folders
1052
		if (isset($folderprops[PR_ATTR_HIDDEN]) && $folderprops[PR_ATTR_HIDDEN] != false) {
1053
			SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->GetFolder(): invalid folder '%s' as it is a hidden folder (PR_ATTR_HIDDEN)", $folderprops[PR_DISPLAY_NAME]));
1054
1055
			return false;
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...
1056
		}
1057
1058
		// ignore certain undesired folders, like "RSS Feeds" and "Suggested contacts"
1059
		if ((isset($folderprops[PR_CONTAINER_CLASS]) && $folderprops[PR_CONTAINER_CLASS] == "IPF.Note.OutlookHomepage") ||
1060
				in_array($folderprops[PR_ENTRYID], $this->getSpecialFoldersData())) {
1061
			SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->GetFolder(): folder '%s' should not be synchronized", $folderprops[PR_DISPLAY_NAME]));
1062
1063
			return false;
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...
1064
		}
1065
1066
		$folder->BackendId = bin2hex($folderprops[PR_SOURCE_KEY]);
1067
		$folderOrigin = DeviceManager::FLD_ORIGIN_USER;
1068
		if (GSync::GetBackend()->GetImpersonatedUser()) {
1069
			$folderOrigin = DeviceManager::FLD_ORIGIN_IMPERSONATED;
1070
		}
1071
		$folder->serverid = GSync::GetDeviceManager()->GetFolderIdForBackendId($folder->BackendId, true, $folderOrigin, $folderprops[PR_DISPLAY_NAME]);
1072
		if ($folderprops[PR_PARENT_ENTRYID] == $storeprops[PR_IPM_SUBTREE_ENTRYID] || $folderprops[PR_PARENT_ENTRYID] == $storeprops[PR_IPM_PUBLIC_FOLDERS_ENTRYID]) {
1073
			$folder->parentid = "0";
1074
		}
1075
		else {
1076
			$folder->parentid = GSync::GetDeviceManager()->GetFolderIdForBackendId(bin2hex($folderprops[PR_PARENT_SOURCE_KEY]));
1077
		}
1078
		$folder->displayname = w2u($folderprops[PR_DISPLAY_NAME]);
1079
		$folder->type = $this->GetFolderType($folderprops[PR_ENTRYID], isset($folderprops[PR_CONTAINER_CLASS]) ? $folderprops[PR_CONTAINER_CLASS] : false);
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

1079
		$folder->type = $this->GetFolderType($folderprops[PR_ENTRYID], /** @scrutinizer ignore-type */ isset($folderprops[PR_CONTAINER_CLASS]) ? $folderprops[PR_CONTAINER_CLASS] : false);
Loading history...
1080
1081
		return $folder;
1082
	}
1083
1084
	/**
1085
	 * Returns the foldertype for an entryid
1086
	 * Gets the folder type by checking the default folders in MAPI.
1087
	 *
1088
	 * @param string $entryid
1089
	 * @param string $class   (opt)
1090
	 *
1091
	 * @return long
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...
1092
	 */
1093
	public function GetFolderType($entryid, $class = false) {
1094
		$storeprops = $this->GetStoreProps();
1095
		$inboxprops = $this->GetInboxProps();
1096
1097
		if ($entryid == $storeprops[PR_IPM_WASTEBASKET_ENTRYID]) {
1098
			return SYNC_FOLDER_TYPE_WASTEBASKET;
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...
1099
		}
1100
		if ($entryid == $storeprops[PR_IPM_SENTMAIL_ENTRYID]) {
1101
			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...
1102
		}
1103
		if ($entryid == $storeprops[PR_IPM_OUTBOX_ENTRYID]) {
1104
			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...
1105
		}
1106
1107
		// Public folders do not have inboxprops
1108
		// @see https://jira.z-hub.io/browse/ZP-995
1109
		if (!empty($inboxprops)) {
1110
			if ($entryid == $inboxprops[PR_ENTRYID]) {
1111
				return SYNC_FOLDER_TYPE_INBOX;
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...
1112
			}
1113
			if ($entryid == $inboxprops[PR_IPM_DRAFTS_ENTRYID]) {
1114
				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...
1115
			}
1116
			if ($entryid == $inboxprops[PR_IPM_TASK_ENTRYID]) {
1117
				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...
1118
			}
1119
			if ($entryid == $inboxprops[PR_IPM_APPOINTMENT_ENTRYID]) {
1120
				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...
1121
			}
1122
			if ($entryid == $inboxprops[PR_IPM_CONTACT_ENTRYID]) {
1123
				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...
1124
			}
1125
			if ($entryid == $inboxprops[PR_IPM_NOTE_ENTRYID]) {
1126
				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...
1127
			}
1128
			if ($entryid == $inboxprops[PR_IPM_JOURNAL_ENTRYID]) {
1129
				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...
1130
			}
1131
		}
1132
1133
		// user created folders
1134
		if ($class == "IPF.Note") {
1135
			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...
1136
		}
1137
		if ($class == "IPF.Task") {
1138
			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...
1139
		}
1140
		if ($class == "IPF.Appointment") {
1141
			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...
1142
		}
1143
		if ($class == "IPF.Contact") {
1144
			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...
1145
		}
1146
		if ($class == "IPF.StickyNote") {
1147
			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...
1148
		}
1149
		if ($class == "IPF.Journal") {
1150
			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...
1151
		}
1152
1153
		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...
1154
	}
1155
1156
	/**
1157
	 * Indicates if the entry id is a default MAPI folder.
1158
	 *
1159
	 * @param string $entryid
1160
	 *
1161
	 * @return bool
1162
	 */
1163
	public function IsMAPIDefaultFolder($entryid) {
1164
		$msgstore_props = mapi_getprops($this->store, [PR_ENTRYID, PR_DISPLAY_NAME, PR_IPM_SUBTREE_ENTRYID, PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID, PR_MDB_PROVIDER, PR_IPM_PUBLIC_FOLDERS_ENTRYID, PR_IPM_FAVORITES_ENTRYID, PR_MAILBOX_OWNER_ENTRYID]);
1 ignored issue
show
Bug introduced by
The function mapi_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1164
		$msgstore_props = /** @scrutinizer ignore-call */ 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]);
Loading history...
1165
1166
		$inboxProps = [];
1167
		$inbox = mapi_msgstore_getreceivefolder($this->store);
1 ignored issue
show
Bug introduced by
The function mapi_msgstore_getreceivefolder was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1167
		$inbox = /** @scrutinizer ignore-call */ mapi_msgstore_getreceivefolder($this->store);
Loading history...
1168
		if (!mapi_last_hresult()) {
1 ignored issue
show
Bug introduced by
The function mapi_last_hresult was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1168
		if (!/** @scrutinizer ignore-call */ mapi_last_hresult()) {
Loading history...
1169
			$inboxProps = mapi_getprops($inbox, [PR_ENTRYID]);
1170
		}
1171
1172
		$root = mapi_msgstore_openentry($this->store, null); // TODO use getRootProps()
1 ignored issue
show
Bug introduced by
The function mapi_msgstore_openentry was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1172
		$root = /** @scrutinizer ignore-call */ mapi_msgstore_openentry($this->store, null); // TODO use getRootProps()
Loading history...
1173
		$rootProps = mapi_getprops($root, [PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_JOURNAL_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_TASK_ENTRYID, PR_ADDITIONAL_REN_ENTRYIDS]);
1174
1175
		$additional_ren_entryids = [];
1176
		if (isset($rootProps[PR_ADDITIONAL_REN_ENTRYIDS])) {
1177
			$additional_ren_entryids = $rootProps[PR_ADDITIONAL_REN_ENTRYIDS];
1178
		}
1179
1180
		$defaultfolders = [
1181
			"inbox" => ["inbox" => PR_ENTRYID],
1182
			"outbox" => ["store" => PR_IPM_OUTBOX_ENTRYID],
1183
			"sent" => ["store" => PR_IPM_SENTMAIL_ENTRYID],
1184
			"wastebasket" => ["store" => PR_IPM_WASTEBASKET_ENTRYID],
1185
			"favorites" => ["store" => PR_IPM_FAVORITES_ENTRYID],
1186
			"publicfolders" => ["store" => PR_IPM_PUBLIC_FOLDERS_ENTRYID],
1187
			"calendar" => ["root" => PR_IPM_APPOINTMENT_ENTRYID],
1188
			"contact" => ["root" => PR_IPM_CONTACT_ENTRYID],
1189
			"drafts" => ["root" => PR_IPM_DRAFTS_ENTRYID],
1190
			"journal" => ["root" => PR_IPM_JOURNAL_ENTRYID],
1191
			"note" => ["root" => PR_IPM_NOTE_ENTRYID],
1192
			"task" => ["root" => PR_IPM_TASK_ENTRYID],
1193
			"junk" => ["additional" => 4],
1194
			"syncissues" => ["additional" => 1],
1195
			"conflicts" => ["additional" => 0],
1196
			"localfailures" => ["additional" => 2],
1197
			"serverfailures" => ["additional" => 3],
1198
		];
1199
1200
		foreach ($defaultfolders as $key => $prop) {
1201
			$tag = reset($prop);
1202
			$from = key($prop);
1203
1204
			switch ($from) {
1205
				case "inbox":
1206
					if (isset($inboxProps[$tag]) && $entryid == $inboxProps[$tag]) {
1207
						SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Inbox found, key '%s'", $key));
1208
1209
						return true;
1210
					}
1211
					break;
1212
1213
				case "store":
1214
					if (isset($msgstore_props[$tag]) && $entryid == $msgstore_props[$tag]) {
1215
						SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Store folder found, key '%s'", $key));
1216
1217
						return true;
1218
					}
1219
					break;
1220
1221
				case "root":
1222
					if (isset($rootProps[$tag]) && $entryid == $rootProps[$tag]) {
1223
						SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Root folder found, key '%s'", $key));
1224
1225
						return true;
1226
					}
1227
					break;
1228
1229
				case "additional":
1230
					if (isset($additional_ren_entryids[$tag]) && $entryid == $additional_ren_entryids[$tag]) {
1231
						SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Additional folder found, key '%s'", $key));
1232
1233
						return true;
1234
					}
1235
					break;
1236
			}
1237
		}
1238
1239
		return false;
1240
	}
1241
1242
	/*----------------------------------------------------------------------------------------------------------
1243
	 * SETTER
1244
	 */
1245
1246
	/**
1247
	 * Writes a SyncObject to MAPI
1248
	 * Depending on the message class, a contact, appointment, task or email is written.
1249
	 *
1250
	 * @param mixed      $mapimessage
1251
	 * @param SyncObject $message
1252
	 *
1253
	 * @return bool
1254
	 */
1255
	public function SetMessage($mapimessage, $message) {
1256
		// TODO check with instanceof
1257
		switch (strtolower(get_class($message))) {
1258
			case "synccontact":
1259
				return $this->setContact($mapimessage, $message);
1260
1261
			case "syncappointment":
1262
				return $this->setAppointment($mapimessage, $message);
1263
1264
			case "synctask":
1265
				return $this->setTask($mapimessage, $message);
1266
1267
			case "syncnote":
1268
				return $this->setNote($mapimessage, $message);
1269
1270
			default:
1271
				// for emails only flag (read and todo) changes are possible
1272
				return $this->setEmail($mapimessage, $message);
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...
1273
		}
1274
	}
1275
1276
	/**
1277
	 * Writes SyncMail to MAPI (actually flags only).
1278
	 *
1279
	 * @param mixed    $mapimessage
1280
	 * @param SyncMail $message
1281
	 */
1282
	private function setEmail($mapimessage, $message) {
1283
		// update categories
1284
		if (!isset($message->categories)) {
1285
			$message->categories = [];
1286
		}
1287
		$emailmap = MAPIMapping::GetEmailMapping();
1288
		$this->setPropsInMAPI($mapimessage, $message, ["categories" => $emailmap["categories"]]);
1289
1290
		$flagmapping = MAPIMapping::GetMailFlagsMapping();
1291
		$flagprops = MAPIMapping::GetMailFlagsProperties();
1292
		$flagprops = array_merge($this->getPropIdsFromStrings($flagmapping), $this->getPropIdsFromStrings($flagprops));
1293
		// flag specific properties to be set
1294
		$props = $delprops = [];
1295
		// unset message flags if:
1296
		// flag is not set
1297
		if (empty($message->flag) ||
1298
			// flag status is not set
1299
			!isset($message->flag->flagstatus) ||
1300
			// flag status is 0 or empty
1301
			(isset($message->flag->flagstatus) && ($message->flag->flagstatus == 0 || $message->flag->flagstatus == ""))) {
1302
			// if message flag is empty, some properties need to be deleted
1303
			// and some set to 0 or false
1304
1305
			$props[$flagprops["todoitemsflags"]] = 0;
1306
			$props[$flagprops["status"]] = 0;
1307
			$props[$flagprops["completion"]] = 0.0;
1308
			$props[$flagprops["flagtype"]] = "";
1309
			$props[$flagprops["ordinaldate"]] = 0x7FFFFFFF; // ordinal date is 12am 1.1.4501, set it to max possible value
1310
			$props[$flagprops["subordinaldate"]] = "";
1311
			$props[$flagprops["replyrequested"]] = false;
1312
			$props[$flagprops["responserequested"]] = false;
1313
			$props[$flagprops["reminderset"]] = false;
1314
			$props[$flagprops["complete"]] = false;
1315
1316
			$delprops[] = $flagprops["todotitle"];
1317
			$delprops[] = $flagprops["duedate"];
1318
			$delprops[] = $flagprops["startdate"];
1319
			$delprops[] = $flagprops["datecompleted"];
1320
			$delprops[] = $flagprops["utcstartdate"];
1321
			$delprops[] = $flagprops["utcduedate"];
1322
			$delprops[] = $flagprops["completetime"];
1323
			$delprops[] = $flagprops["flagstatus"];
1324
			$delprops[] = $flagprops["flagicon"];
1325
		}
1326
		else {
1327
			$this->setPropsInMAPI($mapimessage, $message->flag, $flagmapping);
1328
			$props[$flagprops["todoitemsflags"]] = 1;
1329
			if (isset($message->subject) && strlen($message->subject) > 0) {
1330
				$props[$flagprops["todotitle"]] = $message->subject;
1331
			}
1332
			// ordinal date is utc current time
1333
			if (!isset($message->flag->ordinaldate) || empty($message->flag->ordinaldate)) {
1334
				$props[$flagprops["ordinaldate"]] = time();
1335
			}
1336
			// the default value
1337
			if (!isset($message->flag->subordinaldate) || empty($message->flag->subordinaldate)) {
1338
				$props[$flagprops["subordinaldate"]] = "5555555";
1339
			}
1340
			$props[$flagprops["flagicon"]] = 6; // red flag icon
1341
			$props[$flagprops["replyrequested"]] = true;
1342
			$props[$flagprops["responserequested"]] = true;
1343
1344
			if ($message->flag->flagstatus == SYNC_FLAGSTATUS_COMPLETE) {
1345
				$props[$flagprops["status"]] = olTaskComplete;
1346
				$props[$flagprops["completion"]] = 1.0;
1347
				$props[$flagprops["complete"]] = true;
1348
				$props[$flagprops["replyrequested"]] = false;
1349
				$props[$flagprops["responserequested"]] = false;
1350
				unset($props[$flagprops["flagicon"]]);
1351
				$delprops[] = $flagprops["flagicon"];
1352
			}
1353
		}
1354
1355
		if (!empty($props)) {
1356
			mapi_setprops($mapimessage, $props);
1 ignored issue
show
Bug introduced by
The function mapi_setprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1356
			/** @scrutinizer ignore-call */ 
1357
   mapi_setprops($mapimessage, $props);
Loading history...
1357
		}
1358
		if (!empty($delprops)) {
1359
			mapi_deleteprops($mapimessage, $delprops);
1 ignored issue
show
Bug introduced by
The function mapi_deleteprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1359
			/** @scrutinizer ignore-call */ 
1360
   mapi_deleteprops($mapimessage, $delprops);
Loading history...
1360
		}
1361
	}
1362
1363
	/**
1364
	 * Writes a SyncAppointment to MAPI.
1365
	 *
1366
	 * @param mixed           $mapimessage
1367
	 * @param SyncAppointment $message
1368
	 * @param mixed           $appointment
1369
	 *
1370
	 * @return bool
1371
	 */
1372
	private function setAppointment($mapimessage, $appointment) {
1373
		// Get timezone info
1374
		if (isset($appointment->timezone)) {
1375
			$tz = $this->getTZFromSyncBlob(base64_decode($appointment->timezone));
1376
		}
1377
		else {
1378
			$tz = false;
1379
		}
1380
1381
		// start and end time may not be set - try to get them from the existing appointment for further calculation - see https://jira.z-hub.io/browse/ZP-983
1382
		if (!isset($appointment->starttime) || !isset($appointment->endtime)) {
1383
			$amapping = MAPIMapping::GetAppointmentMapping();
1384
			$amapping = $this->getPropIdsFromStrings($amapping);
1385
			$existingstartendpropsmap = [$amapping["starttime"], $amapping["endtime"]];
1386
			$existingstartendprops = $this->getProps($mapimessage, $existingstartendpropsmap);
1387
1388
			if (isset($existingstartendprops[$amapping["starttime"]]) && !isset($appointment->starttime)) {
1389
				$appointment->starttime = $existingstartendprops[$amapping["starttime"]];
1390
				SLog::Write(LOGLEVEL_WBXML, sprintf("MAPIProvider->setAppointment(): Parameter 'starttime' was not set, using value from MAPI %d (%s).", $appointment->starttime, gmstrftime("%Y%m%dT%H%M%SZ", $appointment->starttime)));
1391
			}
1392
			if (isset($existingstartendprops[$amapping["endtime"]]) && !isset($appointment->endtime)) {
1393
				$appointment->endtime = $existingstartendprops[$amapping["endtime"]];
1394
				SLog::Write(LOGLEVEL_WBXML, sprintf("MAPIProvider->setAppointment(): Parameter 'endtime' was not set, using value from MAPI %d (%s).", $appointment->endtime, gmstrftime("%Y%m%dT%H%M%SZ", $appointment->endtime)));
1395
			}
1396
		}
1397
		if (!isset($appointment->starttime) || !isset($appointment->endtime)) {
1398
			throw new StatusException("MAPIProvider->setAppointment(): Error, start and/or end time not set and can not be retrieved from MAPI.", SYNC_STATUS_SYNCCANNOTBECOMPLETED);
1399
		}
1400
1401
		// calculate duration because without it some webaccess views are broken. duration is in min
1402
		$localstart = $this->getLocaltimeByTZ($appointment->starttime, $tz);
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

1402
		$localstart = $this->getLocaltimeByTZ($appointment->starttime, /** @scrutinizer ignore-type */ $tz);
Loading history...
1403
		$localend = $this->getLocaltimeByTZ($appointment->endtime, $tz);
1404
		$duration = ($localend - $localstart) / 60;
1405
1406
		// nokia sends an yearly event with 0 mins duration but as all day event,
1407
		// so make it end next day
1408
		if ($appointment->starttime == $appointment->endtime && isset($appointment->alldayevent) && $appointment->alldayevent) {
1409
			$duration = 1440;
1410
			$appointment->endtime = $appointment->starttime + 24 * 60 * 60;
1411
			$localend = $localstart + 24 * 60 * 60;
1412
		}
1413
1414
		// is the transmitted UID OL compatible?
1415
		// if not, encapsulate the transmitted uid
1416
		$appointment->uid = Utils::GetOLUidFromICalUid($appointment->uid);
1417
1418
		mapi_setprops($mapimessage, [PR_MESSAGE_CLASS => "IPM.Appointment"]);
1 ignored issue
show
Bug introduced by
The function mapi_setprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1418
		/** @scrutinizer ignore-call */ 
1419
  mapi_setprops($mapimessage, [PR_MESSAGE_CLASS => "IPM.Appointment"]);
Loading history...
1419
1420
		$appointmentmapping = MAPIMapping::GetAppointmentMapping();
1421
		$this->setPropsInMAPI($mapimessage, $appointment, $appointmentmapping);
1422
		$appointmentprops = MAPIMapping::GetAppointmentProperties();
1423
		$appointmentprops = array_merge($this->getPropIdsFromStrings($appointmentmapping), $this->getPropIdsFromStrings($appointmentprops));
1424
		// appointment specific properties to be set
1425
		$props = [];
1426
1427
		// sensitivity is not enough to mark an appointment as private, so we use another mapi tag
1428
		$private = (isset($appointment->sensitivity) && $appointment->sensitivity >= SENSITIVITY_PRIVATE) ? true : false;
1429
1430
		// Set commonstart/commonend to start/end and remindertime to start, duration, private and cleanGlobalObjectId
1431
		$props[$appointmentprops["commonstart"]] = $appointment->starttime;
1432
		$props[$appointmentprops["commonend"]] = $appointment->endtime;
1433
		$props[$appointmentprops["reminderstart"]] = $appointment->starttime;
1434
		// Set reminder boolean to 'true' if reminder is set
1435
		$props[$appointmentprops["reminderset"]] = isset($appointment->reminder) ? true : false;
1436
		$props[$appointmentprops["duration"]] = $duration;
1437
		$props[$appointmentprops["private"]] = $private;
1438
		$props[$appointmentprops["uid"]] = $appointment->uid;
1439
		// Set named prop 8510, unknown property, but enables deleting a single occurrence of a recurring
1440
		// type in OLK2003.
1441
		$props[$appointmentprops["sideeffects"]] = 369;
1442
1443
		if (isset($appointment->reminder) && $appointment->reminder >= 0) {
1444
			// Set 'flagdueby' to correct value (start - reminderminutes)
1445
			$props[$appointmentprops["flagdueby"]] = $appointment->starttime - $appointment->reminder * 60;
1446
			$props[$appointmentprops["remindertime"]] = $appointment->reminder;
1447
		}
1448
		// unset the reminder
1449
		else {
1450
			$props[$appointmentprops["reminderset"]] = false;
1451
		}
1452
1453
		if (isset($appointment->asbody)) {
1454
			$this->setASbody($appointment->asbody, $props, $appointmentprops);
1455
		}
1456
1457
		if ($tz !== false) {
1458
			$props[$appointmentprops["timezonetag"]] = $this->getMAPIBlobFromTZ($tz);
1459
		}
1460
1461
		if (isset($appointment->recurrence)) {
1462
			// Set PR_ICON_INDEX to 1025 to show correct icon in category view
1463
			$props[$appointmentprops["icon"]] = 1025;
1464
1465
			// if there aren't any exceptions, use the 'old style' set recurrence
1466
			$noexceptions = true;
1467
1468
			$recurrence = new Recurrence($this->store, $mapimessage);
1469
			$recur = [];
1470
			$this->setRecurrence($appointment, $recur);
1471
1472
			// set the recurrence type to that of the MAPI
1473
			$props[$appointmentprops["recurrencetype"]] = $recur["recurrencetype"];
1474
1475
			$starttime = $this->gmtime($localstart);
1476
			$endtime = $this->gmtime($localend);
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

1476
			$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...
1477
1478
			// set recurrence start here because it's calculated differently for tasks and appointments
1479
			$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

1479
			$recur["start"] = $this->getDayStartOfTimestamp($this->getGMTTimeByTZ($localstart, /** @scrutinizer ignore-type */ $tz));
Loading history...
1480
1481
			$recur["startocc"] = $starttime["tm_hour"] * 60 + $starttime["tm_min"];
1482
			$recur["endocc"] = $recur["startocc"] + $duration; // Note that this may be > 24*60 if multi-day
1483
1484
			// only tasks can regenerate
1485
			$recur["regen"] = false;
1486
1487
			// Process exceptions. The PDA will send all exceptions for this recurring item.
1488
			if (isset($appointment->exceptions)) {
1489
				foreach ($appointment->exceptions as $exception) {
1490
					// we always need the base date
1491
					if (!isset($exception->exceptionstarttime)) {
1492
						continue;
1493
					}
1494
1495
					$basedate = $this->getDayStartOfTimestamp($exception->exceptionstarttime);
1496
					if (isset($exception->deleted) && $exception->deleted) {
1497
						$noexceptions = false;
1498
						// Delete exception
1499
						$recurrence->createException([], $basedate, true);
1500
					}
1501
					else {
1502
						// Change exception
1503
						$mapiexception = ["basedate" => $basedate];
1504
						// other exception properties which are not handled in recurrence
1505
						$exceptionprops = [];
1506
1507
						if (isset($exception->starttime)) {
1508
							$mapiexception["start"] = $this->getLocaltimeByTZ($exception->starttime, $tz);
1509
							$exceptionprops[$appointmentprops["starttime"]] = $exception->starttime;
1510
						}
1511
						if (isset($exception->endtime)) {
1512
							$mapiexception["end"] = $this->getLocaltimeByTZ($exception->endtime, $tz);
1513
							$exceptionprops[$appointmentprops["endtime"]] = $exception->endtime;
1514
						}
1515
						if (isset($exception->subject)) {
1516
							$exceptionprops[$appointmentprops["subject"]] = $mapiexception["subject"] = u2w($exception->subject);
1517
						}
1518
						if (isset($exception->location)) {
1519
							$exceptionprops[$appointmentprops["location"]] = $mapiexception["location"] = u2w($exception->location);
1520
						}
1521
						if (isset($exception->busystatus)) {
1522
							$exceptionprops[$appointmentprops["busystatus"]] = $mapiexception["busystatus"] = $exception->busystatus;
1523
						}
1524
						if (isset($exception->reminder)) {
1525
							$exceptionprops[$appointmentprops["reminderset"]] = $mapiexception["reminder_set"] = 1;
1526
							$exceptionprops[$appointmentprops["remindertime"]] = $mapiexception["remind_before"] = $exception->reminder;
1527
						}
1528
						if (isset($exception->alldayevent)) {
1529
							$exceptionprops[$appointmentprops["alldayevent"]] = $mapiexception["alldayevent"] = $exception->alldayevent;
1530
						}
1531
1532
						if (!isset($recur["changed_occurrences"])) {
1533
							$recur["changed_occurrences"] = [];
1534
						}
1535
1536
						if (isset($exception->body)) {
1537
							$exceptionprops[$appointmentprops["body"]] = u2w($exception->body);
1538
						}
1539
1540
						if (isset($exception->asbody)) {
1541
							$this->setASbody($exception->asbody, $exceptionprops, $appointmentprops);
1542
							$mapiexception["body"] = $exceptionprops[$appointmentprops["body"]] =
1543
								(isset($exceptionprops[$appointmentprops["body"]])) ? $exceptionprops[$appointmentprops["body"]] :
1544
								((isset($exceptionprops[$appointmentprops["html"]])) ? $exceptionprops[$appointmentprops["html"]] : "");
1545
						}
1546
1547
						array_push($recur["changed_occurrences"], $mapiexception);
1548
1549
						if (!empty($exceptionprops)) {
1550
							$noexceptions = false;
1551
							if ($recurrence->isException($basedate)) {
1552
								$recurrence->modifyException($exceptionprops, $basedate);
1553
							}
1554
							else {
1555
								$recurrence->createException($exceptionprops, $basedate);
1556
							}
1557
						}
1558
					}
1559
				}
1560
			}
1561
1562
			// setRecurrence deletes the attachments from an appointment
1563
			if ($noexceptions) {
1564
				$recurrence->setRecurrence($tz, $recur);
1565
			}
1566
		}
1567
		else {
1568
			$props[$appointmentprops["isrecurring"]] = false;
1569
		}
1570
1571
		// always set the PR_SENT_REPRESENTING_* props so that the attendee status update also works with the webaccess
1572
		$p = [$appointmentprops["representingentryid"], $appointmentprops["representingname"], $appointmentprops["sentrepresentingaddt"],
1573
			$appointmentprops["sentrepresentingemail"], $appointmentprops["sentrepresentinsrchk"], $appointmentprops["responsestatus"], ];
1574
		$representingprops = $this->getProps($mapimessage, $p);
1575
1576
		if (!isset($representingprops[$appointmentprops["representingentryid"]])) {
1577
			// TODO use GetStoreProps
1578
			$storeProps = mapi_getprops($this->store, [PR_MAILBOX_OWNER_ENTRYID]);
1 ignored issue
show
Bug introduced by
The function mapi_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1578
			$storeProps = /** @scrutinizer ignore-call */ mapi_getprops($this->store, [PR_MAILBOX_OWNER_ENTRYID]);
Loading history...
1579
			$props[$appointmentprops["representingentryid"]] = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
1580
			$displayname = $this->getFullnameFromEntryID($storeProps[PR_MAILBOX_OWNER_ENTRYID]);
1581
1582
			$props[$appointmentprops["representingname"]] = ($displayname !== false) ? $displayname : Request::GetUser();
0 ignored issues
show
introduced by
The condition $displayname !== false is always true.
Loading history...
1583
			$props[$appointmentprops["sentrepresentingemail"]] = Request::GetUser();
1584
			$props[$appointmentprops["sentrepresentingaddt"]] = "ZARAFA";
1585
			$props[$appointmentprops["sentrepresentinsrchk"]] = $props[$appointmentprops["sentrepresentingaddt"]] . ":" . $props[$appointmentprops["sentrepresentingemail"]];
1586
1587
			if (isset($appointment->attendees) && is_array($appointment->attendees) && !empty($appointment->attendees)) {
1588
				$props[$appointmentprops["icon"]] = 1026;
1589
				// the user is the organizer
1590
				// set these properties to show tracking tab in webapp
1591
1592
				$props[$appointmentprops["mrwassent"]] = true;
1593
				$props[$appointmentprops["responsestatus"]] = olResponseOrganized;
1594
				$props[$appointmentprops["meetingstatus"]] = olMeeting;
1595
			}
1596
		}
1597
		// we also have to set the responsestatus and not only meetingstatus, so we use another mapi tag
1598
		if (!isset($props[$appointmentprops["responsestatus"]])) {
1599
			if (isset($appointment->responsetype)) {
1600
				$props[$appointmentprops["responsestatus"]] = $appointment->responsetype;
1601
			}
1602
			// only set responsestatus to none if it is not set on the server
1603
			elseif (!isset($representingprops[$appointmentprops["responsestatus"]])) {
1604
				$props[$appointmentprops["responsestatus"]] = olResponseNone;
1605
			}
1606
		}
1607
1608
		// Do attendees
1609
		if (isset($appointment->attendees) && is_array($appointment->attendees)) {
1610
			$recips = [];
1611
1612
			// Outlook XP requires organizer in the attendee list as well
1613
			$org = [];
1614
			$org[PR_ENTRYID] = isset($representingprops[$appointmentprops["representingentryid"]]) ? $representingprops[$appointmentprops["representingentryid"]] : $props[$appointmentprops["representingentryid"]];
1615
			$org[PR_DISPLAY_NAME] = isset($representingprops[$appointmentprops["representingname"]]) ? $representingprops[$appointmentprops["representingname"]] : $props[$appointmentprops["representingname"]];
1616
			$org[PR_ADDRTYPE] = isset($representingprops[$appointmentprops["sentrepresentingaddt"]]) ? $representingprops[$appointmentprops["sentrepresentingaddt"]] : $props[$appointmentprops["sentrepresentingaddt"]];
1617
			$org[PR_SMTP_ADDRESS] = $org[PR_EMAIL_ADDRESS] = isset($representingprops[$appointmentprops["sentrepresentingemail"]]) ? $representingprops[$appointmentprops["sentrepresentingemail"]] : $props[$appointmentprops["sentrepresentingemail"]];
1618
			$org[PR_SEARCH_KEY] = isset($representingprops[$appointmentprops["sentrepresentinsrchk"]]) ? $representingprops[$appointmentprops["sentrepresentinsrchk"]] : $props[$appointmentprops["sentrepresentinsrchk"]];
1619
			$org[PR_RECIPIENT_FLAGS] = recipOrganizer | recipSendable;
1620
			$org[PR_RECIPIENT_TYPE] = MAPI_ORIG;
1621
1622
			array_push($recips, $org);
1623
1624
			// Open address book for user resolve
1625
			$addrbook = $this->getAddressbook();
1626
			foreach ($appointment->attendees as $attendee) {
1627
				$recip = [];
1628
				$recip[PR_EMAIL_ADDRESS] = u2w($attendee->email);
1629
				$recip[PR_SMTP_ADDRESS] = u2w($attendee->email);
1630
1631
				// lookup information in GAB if possible so we have up-to-date name for given address
1632
				$userinfo = [[PR_DISPLAY_NAME => $recip[PR_EMAIL_ADDRESS]]];
1633
				$userinfo = mapi_ab_resolvename($addrbook, $userinfo, EMS_AB_ADDRESS_LOOKUP);
1 ignored issue
show
Bug introduced by
The function mapi_ab_resolvename was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1633
				$userinfo = /** @scrutinizer ignore-call */ mapi_ab_resolvename($addrbook, $userinfo, EMS_AB_ADDRESS_LOOKUP);
Loading history...
1634
				if (mapi_last_hresult() == NOERROR) {
1 ignored issue
show
Bug introduced by
The function mapi_last_hresult was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1634
				if (/** @scrutinizer ignore-call */ mapi_last_hresult() == NOERROR) {
Loading history...
1635
					$recip[PR_DISPLAY_NAME] = $userinfo[0][PR_DISPLAY_NAME];
1636
					$recip[PR_EMAIL_ADDRESS] = $userinfo[0][PR_EMAIL_ADDRESS];
1637
					$recip[PR_SEARCH_KEY] = $userinfo[0][PR_SEARCH_KEY];
1638
					$recip[PR_ADDRTYPE] = $userinfo[0][PR_ADDRTYPE];
1639
					$recip[PR_ENTRYID] = $userinfo[0][PR_ENTRYID];
1640
					$recip[PR_RECIPIENT_TYPE] = isset($attendee->attendeetype) ? $attendee->attendeetype : MAPI_TO;
1641
					$recip[PR_RECIPIENT_FLAGS] = recipSendable;
1642
					$recip[PR_RECIPIENT_TRACKSTATUS] = isset($attendee->attendeestatus) ? $attendee->attendeestatus : olResponseNone;
1643
				}
1644
				else {
1645
					$recip[PR_DISPLAY_NAME] = u2w($attendee->name);
1646
					$recip[PR_SEARCH_KEY] = "SMTP:" . $recip[PR_EMAIL_ADDRESS] . "\0";
1647
					$recip[PR_ADDRTYPE] = "SMTP";
1648
					$recip[PR_RECIPIENT_TYPE] = isset($attendee->attendeetype) ? $attendee->attendeetype : MAPI_TO;
1649
					$recip[PR_ENTRYID] = mapi_createoneoff($recip[PR_DISPLAY_NAME], $recip[PR_ADDRTYPE], $recip[PR_EMAIL_ADDRESS]);
1 ignored issue
show
Bug introduced by
The function mapi_createoneoff was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1649
					$recip[PR_ENTRYID] = /** @scrutinizer ignore-call */ mapi_createoneoff($recip[PR_DISPLAY_NAME], $recip[PR_ADDRTYPE], $recip[PR_EMAIL_ADDRESS]);
Loading history...
1650
				}
1651
1652
				array_push($recips, $recip);
1653
			}
1654
1655
			mapi_message_modifyrecipients($mapimessage, 0, $recips);
1 ignored issue
show
Bug introduced by
The function mapi_message_modifyrecipients was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1655
			/** @scrutinizer ignore-call */ 
1656
   mapi_message_modifyrecipients($mapimessage, 0, $recips);
Loading history...
1656
		}
1657
		mapi_setprops($mapimessage, $props);
1658
1659
		return true;
1660
	}
1661
1662
	/**
1663
	 * Writes a SyncContact to MAPI.
1664
	 *
1665
	 * @param mixed       $mapimessage
1666
	 * @param SyncContact $contact
1667
	 *
1668
	 * @return bool
1669
	 */
1670
	private function setContact($mapimessage, $contact) {
1671
		mapi_setprops($mapimessage, [PR_MESSAGE_CLASS => "IPM.Contact"]);
1 ignored issue
show
Bug introduced by
The function mapi_setprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1671
		/** @scrutinizer ignore-call */ 
1672
  mapi_setprops($mapimessage, [PR_MESSAGE_CLASS => "IPM.Contact"]);
Loading history...
1672
1673
		// normalize email addresses
1674
		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...
1675
			unset($contact->email1address);
1676
		}
1677
1678
		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...
1679
			unset($contact->email2address);
1680
		}
1681
1682
		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...
1683
			unset($contact->email3address);
1684
		}
1685
1686
		$contactmapping = MAPIMapping::GetContactMapping();
1687
		$contactprops = MAPIMapping::GetContactProperties();
1688
		$this->setPropsInMAPI($mapimessage, $contact, $contactmapping);
1689
1690
		// /set display name from contact's properties
1691
		$cname = $this->composeDisplayName($contact);
1692
1693
		// get contact specific mapi properties and merge them with the AS properties
1694
		$contactprops = array_merge($this->getPropIdsFromStrings($contactmapping), $this->getPropIdsFromStrings($contactprops));
1695
1696
		// contact specific properties to be set
1697
		$props = [];
1698
1699
		// need to be set in order to show contacts properly in outlook and wa
1700
		$nremails = [];
1701
		$abprovidertype = 0;
1702
1703
		if (isset($contact->email1address)) {
1704
			$this->setEmailAddress($contact->email1address, $cname, 1, $props, $contactprops, $nremails, $abprovidertype);
1705
		}
1706
		if (isset($contact->email2address)) {
1707
			$this->setEmailAddress($contact->email2address, $cname, 2, $props, $contactprops, $nremails, $abprovidertype);
1708
		}
1709
		if (isset($contact->email3address)) {
1710
			$this->setEmailAddress($contact->email3address, $cname, 3, $props, $contactprops, $nremails, $abprovidertype);
1711
		}
1712
1713
		$props[$contactprops["addressbooklong"]] = $abprovidertype;
1714
		$props[$contactprops["displayname"]] = $props[$contactprops["subject"]] = $cname;
1715
1716
		// pda multiple e-mail addresses bug fix for the contact
1717
		if (!empty($nremails)) {
1718
			$props[$contactprops["addressbookmv"]] = $nremails;
1719
		}
1720
1721
		// set addresses
1722
		$this->setAddress("home", $contact->homecity, $contact->homecountry, $contact->homepostalcode, $contact->homestate, $contact->homestreet, $props, $contactprops);
1723
		$this->setAddress("business", $contact->businesscity, $contact->businesscountry, $contact->businesspostalcode, $contact->businessstate, $contact->businessstreet, $props, $contactprops);
1724
		$this->setAddress("other", $contact->othercity, $contact->othercountry, $contact->otherpostalcode, $contact->otherstate, $contact->otherstreet, $props, $contactprops);
1725
1726
		// set the mailing address and its type
1727
		if (isset($props[$contactprops["businessaddress"]])) {
1728
			$props[$contactprops["mailingaddress"]] = 2;
1729
			$this->setMailingAddress($contact->businesscity, $contact->businesscountry, $contact->businesspostalcode, $contact->businessstate, $contact->businessstreet, $props[$contactprops["businessaddress"]], $props, $contactprops);
1730
		}
1731
		elseif (isset($props[$contactprops["homeaddress"]])) {
1732
			$props[$contactprops["mailingaddress"]] = 1;
1733
			$this->setMailingAddress($contact->homecity, $contact->homecountry, $contact->homepostalcode, $contact->homestate, $contact->homestreet, $props[$contactprops["homeaddress"]], $props, $contactprops);
1734
		}
1735
		elseif (isset($props[$contactprops["otheraddress"]])) {
1736
			$props[$contactprops["mailingaddress"]] = 3;
1737
			$this->setMailingAddress($contact->othercity, $contact->othercountry, $contact->otherpostalcode, $contact->otherstate, $contact->otherstreet, $props[$contactprops["otheraddress"]], $props, $contactprops);
1738
		}
1739
1740
		if (isset($contact->picture)) {
1741
			$picbinary = base64_decode($contact->picture);
1742
			$picsize = strlen($picbinary);
1743
			$props[$contactprops["haspic"]] = false;
1744
1745
			// TODO contact picture handling
1746
			// check if contact has already got a picture. delete it first in that case
1747
			// delete it also if it was removed on a mobile
1748
			$picprops = mapi_getprops($mapimessage, [$contactprops["haspic"]]);
1 ignored issue
show
Bug introduced by
The function mapi_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1748
			$picprops = /** @scrutinizer ignore-call */ mapi_getprops($mapimessage, [$contactprops["haspic"]]);
Loading history...
1749
			if (isset($picprops[$contactprops["haspic"]]) && $picprops[$contactprops["haspic"]]) {
1750
				SLog::Write(LOGLEVEL_DEBUG, "Contact already has a picture. Delete it");
1751
1752
				$attachtable = mapi_message_getattachmenttable($mapimessage);
1 ignored issue
show
Bug introduced by
The function mapi_message_getattachmenttable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1752
				$attachtable = /** @scrutinizer ignore-call */ mapi_message_getattachmenttable($mapimessage);
Loading history...
1753
				mapi_table_restrict($attachtable, MAPIUtils::GetContactPicRestriction());
1 ignored issue
show
Bug introduced by
The function mapi_table_restrict was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1753
				/** @scrutinizer ignore-call */ 
1754
    mapi_table_restrict($attachtable, MAPIUtils::GetContactPicRestriction());
Loading history...
1754
				$rows = mapi_table_queryallrows($attachtable, [PR_ATTACH_NUM]);
1 ignored issue
show
Bug introduced by
The function mapi_table_queryallrows was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1754
				$rows = /** @scrutinizer ignore-call */ mapi_table_queryallrows($attachtable, [PR_ATTACH_NUM]);
Loading history...
1755
				if (isset($rows) && is_array($rows)) {
1756
					foreach ($rows as $row) {
1757
						mapi_message_deleteattach($mapimessage, $row[PR_ATTACH_NUM]);
1 ignored issue
show
Bug introduced by
The function mapi_message_deleteattach was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1757
						/** @scrutinizer ignore-call */ 
1758
      mapi_message_deleteattach($mapimessage, $row[PR_ATTACH_NUM]);
Loading history...
1758
					}
1759
				}
1760
			}
1761
1762
			// only set picture if there's data in the request
1763
			if ($picbinary !== false && $picsize > 0) {
1764
				$props[$contactprops["haspic"]] = true;
1765
				$pic = mapi_message_createattach($mapimessage);
1 ignored issue
show
Bug introduced by
The function mapi_message_createattach was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1765
				$pic = /** @scrutinizer ignore-call */ mapi_message_createattach($mapimessage);
Loading history...
1766
				// Set properties of the attachment
1767
				$picprops = [
1768
					PR_ATTACH_LONG_FILENAME => "ContactPicture.jpg",
1769
					PR_DISPLAY_NAME => "ContactPicture.jpg",
1770
					0x7FFF000B => true,
1771
					PR_ATTACHMENT_HIDDEN => false,
1772
					PR_ATTACHMENT_FLAGS => 1,
1773
					PR_ATTACH_METHOD => ATTACH_BY_VALUE,
1774
					PR_ATTACH_EXTENSION => ".jpg",
1775
					PR_ATTACH_NUM => 1,
1776
					PR_ATTACH_SIZE => $picsize,
1777
					PR_ATTACH_DATA_BIN => $picbinary,
1778
				];
1779
1780
				mapi_setprops($pic, $picprops);
1781
				mapi_savechanges($pic);
1 ignored issue
show
Bug introduced by
The function mapi_savechanges was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1781
				/** @scrutinizer ignore-call */ 
1782
    mapi_savechanges($pic);
Loading history...
1782
			}
1783
		}
1784
1785
		if (isset($contact->asbody)) {
1786
			$this->setASbody($contact->asbody, $props, $contactprops);
1787
		}
1788
1789
		// set fileas
1790
		if (defined('FILEAS_ORDER')) {
1791
			$lastname = (isset($contact->lastname)) ? $contact->lastname : "";
1792
			$firstname = (isset($contact->firstname)) ? $contact->firstname : "";
1793
			$middlename = (isset($contact->middlename)) ? $contact->middlename : "";
1794
			$company = (isset($contact->companyname)) ? $contact->companyname : "";
1795
			$props[$contactprops["fileas"]] = Utils::BuildFileAs($lastname, $firstname, $middlename, $company);
1796
		}
1797
		else {
1798
			SLog::Write(LOGLEVEL_DEBUG, "FILEAS_ORDER not defined");
1799
		}
1800
1801
		mapi_setprops($mapimessage, $props);
1802
1803
		return true;
1804
	}
1805
1806
	/**
1807
	 * Writes a SyncTask to MAPI.
1808
	 *
1809
	 * @param mixed    $mapimessage
1810
	 * @param SyncTask $task
1811
	 *
1812
	 * @return bool
1813
	 */
1814
	private function setTask($mapimessage, $task) {
1815
		mapi_setprops($mapimessage, [PR_MESSAGE_CLASS => "IPM.Task"]);
1 ignored issue
show
Bug introduced by
The function mapi_setprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1815
		/** @scrutinizer ignore-call */ 
1816
  mapi_setprops($mapimessage, [PR_MESSAGE_CLASS => "IPM.Task"]);
Loading history...
1816
1817
		$taskmapping = MAPIMapping::GetTaskMapping();
1818
		$taskprops = MAPIMapping::GetTaskProperties();
1819
		$this->setPropsInMAPI($mapimessage, $task, $taskmapping);
1820
		$taskprops = array_merge($this->getPropIdsFromStrings($taskmapping), $this->getPropIdsFromStrings($taskprops));
1821
1822
		// task specific properties to be set
1823
		$props = [];
1824
1825
		if (isset($task->asbody)) {
1826
			$this->setASbody($task->asbody, $props, $taskprops);
1827
		}
1828
1829
		if (isset($task->complete)) {
1830
			if ($task->complete) {
1831
				// Set completion to 100%
1832
				// Set status to 'complete'
1833
				$props[$taskprops["completion"]] = 1.0;
1834
				$props[$taskprops["status"]] = 2;
1835
				$props[$taskprops["reminderset"]] = false;
1836
			}
1837
			else {
1838
				// Set completion to 0%
1839
				// Set status to 'not started'
1840
				$props[$taskprops["completion"]] = 0.0;
1841
				$props[$taskprops["status"]] = 0;
1842
			}
1843
		}
1844
		if (isset($task->recurrence) && class_exists('TaskRecurrence')) {
1845
			$deadoccur = false;
1846
			if ((isset($task->recurrence->occurrences) && $task->recurrence->occurrences == 1) ||
1847
				(isset($task->recurrence->deadoccur) && $task->recurrence->deadoccur == 1)) { // ios5 sends deadoccur inside the recurrence
1848
				$deadoccur = true;
1849
			}
1850
1851
			// Set PR_ICON_INDEX to 1281 to show correct icon in category view
1852
			$props[$taskprops["icon"]] = 1281;
1853
			// dead occur - false if new occurrences should be generated from the task
1854
			// true - if it is the last occurrence of the task
1855
			$props[$taskprops["deadoccur"]] = $deadoccur;
1856
			$props[$taskprops["isrecurringtag"]] = true;
1857
1858
			$recurrence = new TaskRecurrence($this->store, $mapimessage);
1859
			$recur = [];
1860
			$this->setRecurrence($task, $recur);
1861
1862
			// task specific recurrence properties which we need to set here
1863
			// "start" and "end" are in GMT when passing to class.recurrence
1864
			// set recurrence start here because it's calculated differently for tasks and appointments
1865
			$recur["start"] = $task->recurrence->start;
1866
			$recur["regen"] = (isset($task->recurrence->regenerate) && $task->recurrence->regenerate) ? 1 : 0;
1867
			// OL regenerates recurring task itself, but setting deleteOccurrence is required so that PHP-MAPI doesn't regenerate
1868
			// completed occurrence of a task.
1869
			if ($recur["regen"] == 0) {
1870
				$recur["deleteOccurrence"] = 0;
1871
			}
1872
			// Also add dates to $recur
1873
			$recur["duedate"] = $task->duedate;
1874
			$recur["complete"] = (isset($task->complete) && $task->complete) ? 1 : 0;
1875
			if (isset($task->datecompleted)) {
1876
				$recur["datecompleted"] = $task->datecompleted;
1877
			}
1878
			$recurrence->setRecurrence($recur);
1879
		}
1880
1881
		$props[$taskprops["private"]] = (isset($task->sensitivity) && $task->sensitivity >= SENSITIVITY_PRIVATE) ? true : false;
1882
1883
		// Open address book for user resolve to set the owner
1884
		$addrbook = $this->getAddressbook();
0 ignored issues
show
Unused Code introduced by
The assignment to $addrbook is dead and can be removed.
Loading history...
1885
1886
		// check if there is already an owner for the task, set current user if not
1887
		$p = [$taskprops["owner"]];
1888
		$owner = $this->getProps($mapimessage, $p);
1889
		if (!isset($owner[$taskprops["owner"]])) {
1890
			$userinfo = nsp_getuserinfo(Request::GetUser());
1 ignored issue
show
Bug introduced by
The function nsp_getuserinfo was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1890
			$userinfo = /** @scrutinizer ignore-call */ nsp_getuserinfo(Request::GetUser());
Loading history...
1891
			if (mapi_last_hresult() == NOERROR && isset($userinfo["fullname"])) {
1 ignored issue
show
Bug introduced by
The function mapi_last_hresult was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1891
			if (/** @scrutinizer ignore-call */ mapi_last_hresult() == NOERROR && isset($userinfo["fullname"])) {
Loading history...
1892
				$props[$taskprops["owner"]] = $userinfo["fullname"];
1893
			}
1894
		}
1895
		mapi_setprops($mapimessage, $props);
1896
1897
		return true;
1898
	}
1899
1900
	/**
1901
	 * Writes a SyncNote to MAPI.
1902
	 *
1903
	 * @param mixed    $mapimessage
1904
	 * @param SyncNote $note
1905
	 *
1906
	 * @return bool
1907
	 */
1908
	private function setNote($mapimessage, $note) {
1909
		// Touchdown does not send categories if all are unset or there is none.
1910
		// Setting it to an empty array will unset the property in KC as well
1911
		if (!isset($note->categories)) {
1912
			$note->categories = [];
1913
		}
1914
1915
		// update icon index to correspond to the color
1916
		if (isset($note->Color) && $note->Color > -1 && $note->Color < 5) {
1917
			$note->Iconindex = 768 + $note->Color;
0 ignored issues
show
Bug introduced by
The property Iconindex does not seem to exist on SyncNote.
Loading history...
1918
		}
1919
1920
		$this->setPropsInMAPI($mapimessage, $note, MAPIMapping::GetNoteMapping());
1921
1922
		$noteprops = MAPIMapping::GetNoteProperties();
1923
		$noteprops = $this->getPropIdsFromStrings($noteprops);
1924
1925
		// note specific properties to be set
1926
		$props = [];
1927
		$props[$noteprops["messageclass"]] = "IPM.StickyNote";
1928
		// set body otherwise the note will be "broken" when editing it in outlook
1929
		if (isset($note->asbody)) {
1930
			$this->setASbody($note->asbody, $props, $noteprops);
1931
		}
1932
1933
		$props[$noteprops["internetcpid"]] = INTERNET_CPID_UTF8;
1934
		mapi_setprops($mapimessage, $props);
1 ignored issue
show
Bug introduced by
The function mapi_setprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1934
		/** @scrutinizer ignore-call */ 
1935
  mapi_setprops($mapimessage, $props);
Loading history...
1935
1936
		return true;
1937
	}
1938
1939
	/*----------------------------------------------------------------------------------------------------------
1940
	 * HELPER
1941
	 */
1942
1943
	/**
1944
	 * Returns the timestamp offset.
1945
	 *
1946
	 * @param string $ts
1947
	 *
1948
	 * @return long
1949
	 */
1950
	private function GetTZOffset($ts) {
1951
		$Offset = date("O", $ts);
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

1951
		$Offset = date("O", /** @scrutinizer ignore-type */ $ts);
Loading history...
1952
1953
		$Parity = $Offset < 0 ? -1 : 1;
1954
		$Offset = $Parity * $Offset;
1955
		$Offset = ($Offset - ($Offset % 100)) / 100 * 60 + $Offset % 100;
1956
1957
		return $Parity * $Offset;
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...
1958
	}
1959
1960
	/**
1961
	 * Localtime of the timestamp.
1962
	 *
1963
	 * @param long $time
1964
	 *
1965
	 * @return array
1966
	 */
1967
	private function gmtime($time) {
1968
		$TZOffset = $this->GetTZOffset($time);
1969
1970
		$t_time = $time - $TZOffset * 60; # Counter adjust for localtime()
1971
1972
		return localtime($t_time, 1);
1973
	}
1974
1975
	/**
1976
	 * Sets the properties in a MAPI object according to an Sync object and a property mapping.
1977
	 *
1978
	 * @param mixed      $mapimessage
1979
	 * @param SyncObject $message
1980
	 * @param array      $mapping
1981
	 *
1982
	 * @return
1983
	 */
1984
	private function setPropsInMAPI($mapimessage, $message, $mapping) {
1985
		$mapiprops = $this->getPropIdsFromStrings($mapping);
1986
		$unsetVars = $message->getUnsetVars();
1987
		$propsToDelete = [];
1988
		$propsToSet = [];
1989
1990
		foreach ($mapiprops as $asprop => $mapiprop) {
1991
			if (isset($message->{$asprop})) {
1992
				// UTF8->windows1252.. this is ok for all numerical values
1993
				if (mapi_prop_type($mapiprop) != PT_BINARY && mapi_prop_type($mapiprop) != PT_MV_BINARY) {
1 ignored issue
show
Bug introduced by
The function mapi_prop_type was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1993
				if (/** @scrutinizer ignore-call */ mapi_prop_type($mapiprop) != PT_BINARY && mapi_prop_type($mapiprop) != PT_MV_BINARY) {
Loading history...
1994
					if (is_array($message->{$asprop})) {
1995
						$value = array_map("u2wi", $message->{$asprop});
1996
					}
1997
					else {
1998
						$value = u2wi($message->{$asprop});
1999
					}
2000
				}
2001
				else {
2002
					$value = $message->{$asprop};
2003
				}
2004
2005
				// Make sure the php values are the correct type
2006
				switch (mapi_prop_type($mapiprop)) {
2007
					case PT_BINARY:
2008
					case PT_STRING8:
2009
						settype($value, "string");
2010
						break;
2011
2012
					case PT_BOOLEAN:
2013
						settype($value, "boolean");
2014
						break;
2015
2016
					case PT_SYSTIME:
2017
					case PT_LONG:
2018
						settype($value, "integer");
2019
						break;
2020
				}
2021
2022
				// decode base64 value
2023
				if ($mapiprop == PR_RTF_COMPRESSED) {
2024
					$value = base64_decode($value);
2025
					if (strlen($value) == 0) {
2026
						continue;
2027
					} // PDA will sometimes give us an empty RTF, which we'll ignore.
2028
2029
					// Note that you can still remove notes because when you remove notes it gives
2030
					// a valid compressed RTF with nothing in it.
2031
				}
2032
				// if an "empty array" is to be saved, it the mvprop should be deleted - fixes Mantis #468
2033
				if (is_array($value) && empty($value)) {
2034
					$propsToDelete[] = $mapiprop;
2035
					SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->setPropsInMAPI(): Property '%s' to be deleted as it is an empty array", $asprop));
2036
				}
2037
				else {
2038
					// all properties will be set at once
2039
					$propsToSet[$mapiprop] = $value;
2040
				}
2041
			}
2042
			elseif (in_array($asprop, $unsetVars)) {
2043
				$propsToDelete[] = $mapiprop;
2044
			}
2045
		}
2046
2047
		mapi_setprops($mapimessage, $propsToSet);
1 ignored issue
show
Bug introduced by
The function mapi_setprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2047
		/** @scrutinizer ignore-call */ 
2048
  mapi_setprops($mapimessage, $propsToSet);
Loading history...
2048
		if (mapi_last_hresult()) {
1 ignored issue
show
Bug introduced by
The function mapi_last_hresult was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2048
		if (/** @scrutinizer ignore-call */ mapi_last_hresult()) {
Loading history...
2049
			SLog::Write(LOGLEVEL_WARN, sprintf("Failed to set properties, trying to set them separately. Error code was:%x", mapi_last_hresult()));
2050
			$this->setPropsIndividually($mapimessage, $propsToSet, $mapiprops);
2051
		}
2052
2053
		mapi_deleteprops($mapimessage, $propsToDelete);
1 ignored issue
show
Bug introduced by
The function mapi_deleteprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2053
		/** @scrutinizer ignore-call */ 
2054
  mapi_deleteprops($mapimessage, $propsToDelete);
Loading history...
2054
2055
		// clean up
2056
		unset($unsetVars, $propsToDelete);
2057
	}
2058
2059
	/**
2060
	 * Sets the properties one by one in a MAPI object.
2061
	 *
2062
	 * @param mixed &$mapimessage
2063
	 * @param array &$propsToSet
2064
	 * @param array &$mapiprops
2065
	 *
2066
	 * @return
2067
	 */
2068
	private function setPropsIndividually(&$mapimessage, &$propsToSet, &$mapiprops) {
2069
		foreach ($propsToSet as $prop => $value) {
2070
			mapi_setprops($mapimessage, [$prop => $value]);
1 ignored issue
show
Bug introduced by
The function mapi_setprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2070
			/** @scrutinizer ignore-call */ 
2071
   mapi_setprops($mapimessage, [$prop => $value]);
Loading history...
2071
			if (mapi_last_hresult()) {
1 ignored issue
show
Bug introduced by
The function mapi_last_hresult was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2071
			if (/** @scrutinizer ignore-call */ mapi_last_hresult()) {
Loading history...
2072
				SLog::Write(LOGLEVEL_ERROR, sprintf("Failed setting property [%s] with value [%s], error code was:%x", array_search($prop, $mapiprops), $value, mapi_last_hresult()));
2073
			}
2074
		}
2075
	}
2076
2077
	/**
2078
	 * Gets the properties from a MAPI object and sets them in the Sync object according to mapping.
2079
	 *
2080
	 * @param SyncObject &$message
2081
	 * @param mixed      $mapimessage
2082
	 * @param array      $mapping
2083
	 *
2084
	 * @return
2085
	 */
2086
	private function getPropsFromMAPI(&$message, $mapimessage, $mapping) {
2087
		$messageprops = $this->getProps($mapimessage, $mapping);
2088
		foreach ($mapping as $asprop => $mapiprop) {
2089
			// Get long strings via openproperty
2090
			if (isset($messageprops[mapi_prop_tag(PT_ERROR, mapi_prop_id($mapiprop))])) {
2 ignored issues
show
Bug introduced by
The function mapi_prop_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2090
			if (isset($messageprops[mapi_prop_tag(PT_ERROR, /** @scrutinizer ignore-call */ mapi_prop_id($mapiprop))])) {
Loading history...
Bug introduced by
The function mapi_prop_tag was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2090
			if (isset($messageprops[/** @scrutinizer ignore-call */ mapi_prop_tag(PT_ERROR, mapi_prop_id($mapiprop))])) {
Loading history...
2091
				if ($messageprops[mapi_prop_tag(PT_ERROR, mapi_prop_id($mapiprop))] == MAPI_E_NOT_ENOUGH_MEMORY_32BIT ||
2092
					$messageprops[mapi_prop_tag(PT_ERROR, mapi_prop_id($mapiprop))] == MAPI_E_NOT_ENOUGH_MEMORY_64BIT) {
2093
					$messageprops[$mapiprop] = MAPIUtils::readPropStream($mapimessage, $mapiprop);
2094
				}
2095
			}
2096
2097
			if (isset($messageprops[$mapiprop])) {
2098
				if (mapi_prop_type($mapiprop) == PT_BOOLEAN) {
1 ignored issue
show
Bug introduced by
The function mapi_prop_type was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2098
				if (/** @scrutinizer ignore-call */ mapi_prop_type($mapiprop) == PT_BOOLEAN) {
Loading history...
2099
					// Force to actual '0' or '1'
2100
					if ($messageprops[$mapiprop]) {
2101
						$message->{$asprop} = 1;
2102
					}
2103
					else {
2104
						$message->{$asprop} = 0;
2105
					}
2106
				}
2107
				else {
2108
					// Special handling for PR_MESSAGE_FLAGS
2109
					if ($mapiprop == PR_MESSAGE_FLAGS) {
2110
						$message->{$asprop} = $messageprops[$mapiprop] & 1;
2111
					} // only look at 'read' flag
2112
					elseif ($mapiprop == PR_RTF_COMPRESSED) {
2113
						// do not send rtf to the mobile
2114
						continue;
2115
					}
2116
					elseif (is_array($messageprops[$mapiprop])) {
2117
						$message->{$asprop} = array_map("w2u", $messageprops[$mapiprop]);
2118
					}
2119
					else {
2120
						if (mapi_prop_type($mapiprop) != PT_BINARY && mapi_prop_type($mapiprop) != PT_MV_BINARY) {
2121
							$message->{$asprop} = w2u($messageprops[$mapiprop]);
2122
						}
2123
						else {
2124
							$message->{$asprop} = $messageprops[$mapiprop];
2125
						}
2126
					}
2127
				}
2128
			}
2129
		}
2130
	}
2131
2132
	/**
2133
	 * Wraps getPropIdsFromStrings() calls.
2134
	 *
2135
	 * @param mixed &$mapiprops
2136
	 *
2137
	 * @return
2138
	 */
2139
	private function getPropIdsFromStrings(&$mapiprops) {
2140
		return getPropIdsFromStrings($this->store, $mapiprops);
2141
	}
2142
2143
	/**
2144
	 * Wraps mapi_getprops() calls.
2145
	 *
2146
	 * @param mixed &$mapiprops
2147
	 * @param mixed $mapimessage
2148
	 * @param mixed $mapiproperties
2149
	 *
2150
	 * @return
2151
	 */
2152
	protected function getProps($mapimessage, &$mapiproperties) {
2153
		$mapiproperties = $this->getPropIdsFromStrings($mapiproperties);
2154
2155
		return mapi_getprops($mapimessage, $mapiproperties);
1 ignored issue
show
Bug introduced by
The function mapi_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2155
		return /** @scrutinizer ignore-call */ mapi_getprops($mapimessage, $mapiproperties);
Loading history...
2156
	}
2157
2158
	/**
2159
	 * Returns an GMT timezone array.
2160
	 *
2161
	 * @return array
2162
	 */
2163
	private function getGMTTZ() {
2164
		return [
2165
			"bias" => 0,
2166
			"tzname" => "",
2167
			"dstendyear" => 0,
2168
			"dstendmonth" => 10,
2169
			"dstendday" => 0,
2170
			"dstendweek" => 5,
2171
			"dstendhour" => 2,
2172
			"dstendminute" => 0,
2173
			"dstendsecond" => 0,
2174
			"dstendmillis" => 0,
2175
			"stdbias" => 0,
2176
			"tznamedst" => "",
2177
			"dststartyear" => 0,
2178
			"dststartmonth" => 3,
2179
			"dststartday" => 0,
2180
			"dststartweek" => 5,
2181
			"dststarthour" => 1,
2182
			"dststartminute" => 0,
2183
			"dststartsecond" => 0,
2184
			"dststartmillis" => 0,
2185
			"dstbias" => -60,
2186
		];
2187
	}
2188
2189
	/**
2190
	 * Unpack timezone info from MAPI.
2191
	 *
2192
	 * @param string $data
2193
	 *
2194
	 * @return array
2195
	 */
2196
	private function getTZFromMAPIBlob($data) {
2197
		return unpack("lbias/lstdbias/ldstbias/" .
2198
						   "vconst1/vdstendyear/vdstendmonth/vdstendday/vdstendweek/vdstendhour/vdstendminute/vdstendsecond/vdstendmillis/" .
2199
						   "vconst2/vdststartyear/vdststartmonth/vdststartday/vdststartweek/vdststarthour/vdststartminute/vdststartsecond/vdststartmillis", $data);
2200
	}
2201
2202
	/**
2203
	 * Unpack timezone info from Sync.
2204
	 *
2205
	 * @param string $data
2206
	 *
2207
	 * @return array
2208
	 */
2209
	private function getTZFromSyncBlob($data) {
2210
		$tz = unpack("lbias/a64tzname/vdstendyear/vdstendmonth/vdstendday/vdstendweek/vdstendhour/vdstendminute/vdstendsecond/vdstendmillis/" .
2211
						"lstdbias/a64tznamedst/vdststartyear/vdststartmonth/vdststartday/vdststartweek/vdststarthour/vdststartminute/vdststartsecond/vdststartmillis/" .
2212
						"ldstbias", $data);
2213
2214
		// Make the structure compatible with class.recurrence.php
2215
		$tz["timezone"] = $tz["bias"];
2216
		$tz["timezonedst"] = $tz["dstbias"];
2217
2218
		return $tz;
2219
	}
2220
2221
	/**
2222
	 * Pack timezone info for MAPI.
2223
	 *
2224
	 * @param array $tz
2225
	 *
2226
	 * @return string
2227
	 */
2228
	private function getMAPIBlobFromTZ($tz) {
2229
		return pack(
2230
			"lll" . "vvvvvvvvv" . "vvvvvvvvv",
2231
			$tz["bias"],
2232
			$tz["stdbias"],
2233
			$tz["dstbias"],
2234
			0,
2235
			0,
2236
			$tz["dstendmonth"],
2237
			$tz["dstendday"],
2238
			$tz["dstendweek"],
2239
			$tz["dstendhour"],
2240
			$tz["dstendminute"],
2241
			$tz["dstendsecond"],
2242
			$tz["dstendmillis"],
2243
			0,
2244
			0,
2245
			$tz["dststartmonth"],
2246
			$tz["dststartday"],
2247
			$tz["dststartweek"],
2248
			$tz["dststarthour"],
2249
			$tz["dststartminute"],
2250
			$tz["dststartsecond"],
2251
			$tz["dststartmillis"]
2252
		);
2253
	}
2254
2255
	/**
2256
	 * Checks the date to see if it is in DST, and returns correct GMT date accordingly.
2257
	 *
2258
	 * @param long  $localtime
2259
	 * @param array $tz
2260
	 *
2261
	 * @return long
2262
	 */
2263
	private function getGMTTimeByTZ($localtime, $tz) {
2264
		if (!isset($tz) || !is_array($tz)) {
2265
			return $localtime;
2266
		}
2267
2268
		if ($this->isDST($localtime, $tz)) {
2269
			return $localtime + $tz["bias"] * 60 + $tz["dstbias"] * 60;
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...
2270
		}
2271
2272
		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...
2273
	}
2274
2275
	/**
2276
	 * Returns the local time for the given GMT time, taking account of the given timezone.
2277
	 *
2278
	 * @param long  $gmttime
2279
	 * @param array $tz
2280
	 *
2281
	 * @return long
2282
	 */
2283
	private function getLocaltimeByTZ($gmttime, $tz) {
2284
		if (!isset($tz) || !is_array($tz)) {
2285
			return $gmttime;
2286
		}
2287
2288
		if ($this->isDST($gmttime - $tz["bias"] * 60, $tz)) { // may bug around the switch time because it may have to be 'gmttime - bias - dstbias'
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

2288
		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...
2289
			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...
2290
		}
2291
2292
		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...
2293
	}
2294
2295
	/**
2296
	 * Returns TRUE if it is the summer and therefore DST is in effect.
2297
	 *
2298
	 * @param long  $localtime
2299
	 * @param array $tz
2300
	 *
2301
	 * @return bool
2302
	 */
2303
	private function isDST($localtime, $tz) {
2304
		if (!isset($tz) || !is_array($tz) ||
2305
			!isset($tz["dstbias"]) || $tz["dstbias"] == 0 ||
2306
			!isset($tz["dststartmonth"]) || $tz["dststartmonth"] == 0 ||
2307
			!isset($tz["dstendmonth"]) || $tz["dstendmonth"] == 0) {
2308
			return false;
2309
		}
2310
2311
		$year = gmdate("Y", $localtime);
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

2311
		$year = gmdate("Y", /** @scrutinizer ignore-type */ $localtime);
Loading history...
2312
		$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

2312
		$start = $this->getTimestampOfWeek(/** @scrutinizer ignore-type */ $year, $tz["dststartmonth"], $tz["dststartweek"], $tz["dststartday"], $tz["dststarthour"], $tz["dststartminute"], $tz["dststartsecond"]);
Loading history...
2313
		$end = $this->getTimestampOfWeek($year, $tz["dstendmonth"], $tz["dstendweek"], $tz["dstendday"], $tz["dstendhour"], $tz["dstendminute"], $tz["dstendsecond"]);
2314
2315
		if ($start < $end) {
2316
			// northern hemisphere (july = dst)
2317
			if ($localtime >= $start && $localtime < $end) {
2318
				$dst = true;
2319
			}
2320
			else {
2321
				$dst = false;
2322
			}
2323
		}
2324
		else {
2325
			// southern hemisphere (january = dst)
2326
			if ($localtime >= $end && $localtime < $start) {
2327
				$dst = false;
2328
			}
2329
			else {
2330
				$dst = true;
2331
			}
2332
		}
2333
2334
		return $dst;
2335
	}
2336
2337
	/**
2338
	 * Returns the local timestamp for the $week'th $wday of $month in $year at $hour:$minute:$second.
2339
	 *
2340
	 * @param int $year
2341
	 * @param int $month
2342
	 * @param int $week
2343
	 * @param int $wday
2344
	 * @param int $hour
2345
	 * @param int $minute
2346
	 * @param int $second
2347
	 *
2348
	 * @return long
2349
	 */
2350
	private function getTimestampOfWeek($year, $month, $week, $wday, $hour, $minute, $second) {
2351
		if ($month == 0) {
2352
			return;
2353
		}
2354
2355
		$date = gmmktime($hour, $minute, $second, $month, 1, $year);
2356
2357
		// Find first day in month which matches day of the week
2358
		while (1) {
2359
			$wdaynow = gmdate("w", $date);
2360
			if ($wdaynow == $wday) {
2361
				break;
2362
			}
2363
			$date += 24 * 60 * 60;
2364
		}
2365
2366
		// Forward $week weeks (may 'overflow' into the next month)
2367
		$date = $date + $week * (24 * 60 * 60 * 7);
2368
2369
		// Reverse 'overflow'. Eg week '10' will always be the last week of the month in which the
2370
		// specified weekday exists
2371
		while (1) {
2372
			$monthnow = gmdate("n", $date); // gmdate returns 1-12
2373
			if ($monthnow > $month) {
2374
				$date = $date - (24 * 7 * 60 * 60);
2375
			}
2376
			else {
2377
				break;
2378
			}
2379
		}
2380
2381
		return $date;
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...
2382
	}
2383
2384
	/**
2385
	 * Normalize the given timestamp to the start of the day.
2386
	 *
2387
	 * @param long $timestamp
2388
	 *
2389
	 * @return long
2390
	 */
2391
	private function getDayStartOfTimestamp($timestamp) {
2392
		return $timestamp - ($timestamp % (60 * 60 * 24));
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...
2393
	}
2394
2395
	/**
2396
	 * Returns an SMTP address from an entry id.
2397
	 *
2398
	 * @param string $entryid
2399
	 *
2400
	 * @return string
2401
	 */
2402
	private function getSMTPAddressFromEntryID($entryid) {
2403
		$addrbook = $this->getAddressbook();
2404
2405
		$mailuser = mapi_ab_openentry($addrbook, $entryid);
1 ignored issue
show
Bug introduced by
The function mapi_ab_openentry was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2405
		$mailuser = /** @scrutinizer ignore-call */ mapi_ab_openentry($addrbook, $entryid);
Loading history...
2406
		if (!$mailuser) {
2407
			return "";
2408
		}
2409
2410
		$props = mapi_getprops($mailuser, [PR_ADDRTYPE, PR_SMTP_ADDRESS, PR_EMAIL_ADDRESS]);
1 ignored issue
show
Bug introduced by
The function mapi_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2410
		$props = /** @scrutinizer ignore-call */ mapi_getprops($mailuser, [PR_ADDRTYPE, PR_SMTP_ADDRESS, PR_EMAIL_ADDRESS]);
Loading history...
2411
2412
		$addrtype = isset($props[PR_ADDRTYPE]) ? $props[PR_ADDRTYPE] : "";
2413
2414
		if (isset($props[PR_SMTP_ADDRESS])) {
2415
			return $props[PR_SMTP_ADDRESS];
2416
		}
2417
2418
		if ($addrtype == "SMTP" && isset($props[PR_EMAIL_ADDRESS])) {
2419
			return $props[PR_EMAIL_ADDRESS];
2420
		}
2421
		if ($addrtype == "ZARAFA" && isset($props[PR_EMAIL_ADDRESS])) {
2422
			$userinfo = nsp_getuserinfo($props[PR_EMAIL_ADDRESS]);
1 ignored issue
show
Bug introduced by
The function nsp_getuserinfo was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2422
			$userinfo = /** @scrutinizer ignore-call */ nsp_getuserinfo($props[PR_EMAIL_ADDRESS]);
Loading history...
2423
			if (is_array($userinfo) && isset($userinfo["primary_email"])) {
2424
				return $userinfo["primary_email"];
2425
			}
2426
		}
2427
2428
		return "";
2429
	}
2430
2431
	/**
2432
	 * Returns fullname from an entryid.
2433
	 *
2434
	 * @param binary $entryid
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...
2435
	 *
2436
	 * @return string fullname or false on error
2437
	 */
2438
	private function getFullnameFromEntryID($entryid) {
2439
		$addrbook = $this->getAddressbook();
2440
		$mailuser = mapi_ab_openentry($addrbook, $entryid);
1 ignored issue
show
Bug introduced by
The function mapi_ab_openentry was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2440
		$mailuser = /** @scrutinizer ignore-call */ mapi_ab_openentry($addrbook, $entryid);
Loading history...
2441
		if (!$mailuser) {
2442
			SLog::Write(LOGLEVEL_ERROR, sprintf("Unable to get mailuser for getFullnameFromEntryID (0x%X)", mapi_last_hresult()));
1 ignored issue
show
Bug introduced by
The function mapi_last_hresult was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2442
			SLog::Write(LOGLEVEL_ERROR, sprintf("Unable to get mailuser for getFullnameFromEntryID (0x%X)", /** @scrutinizer ignore-call */ mapi_last_hresult()));
Loading history...
2443
2444
			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...
2445
		}
2446
2447
		$props = mapi_getprops($mailuser, [PR_DISPLAY_NAME]);
1 ignored issue
show
Bug introduced by
The function mapi_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2447
		$props = /** @scrutinizer ignore-call */ mapi_getprops($mailuser, [PR_DISPLAY_NAME]);
Loading history...
2448
		if (isset($props[PR_DISPLAY_NAME])) {
2449
			return $props[PR_DISPLAY_NAME];
2450
		}
2451
		SLog::Write(LOGLEVEL_ERROR, sprintf("Unable to get fullname for getFullnameFromEntryID (0x%X)", mapi_last_hresult()));
2452
2453
		return false;
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...
2454
	}
2455
2456
	/**
2457
	 * Builds a displayname from several separated values.
2458
	 *
2459
	 * @param SyncContact $contact
2460
	 *
2461
	 * @return string
2462
	 */
2463
	private function composeDisplayName(&$contact) {
2464
		// Set display name and subject to a combined value of firstname and lastname
2465
		$cname = (isset($contact->prefix)) ? u2w($contact->prefix) . " " : "";
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

2465
		$cname = (isset($contact->prefix)) ? /** @scrutinizer ignore-type */ u2w($contact->prefix) . " " : "";
Loading history...
2466
		$cname .= u2w($contact->firstname);
2467
		$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

2467
		$cname .= (isset($contact->middlename)) ? " " . /** @scrutinizer ignore-type */ u2w($contact->middlename) : "";
Loading history...
2468
		$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

2468
		$cname .= " " . /** @scrutinizer ignore-type */ u2w($contact->lastname);
Loading history...
2469
		$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

2469
		$cname .= (isset($contact->suffix)) ? " " . /** @scrutinizer ignore-type */ u2w($contact->suffix) : "";
Loading history...
2470
2471
		return trim($cname);
2472
	}
2473
2474
	/**
2475
	 * Sets all dependent properties for an email address.
2476
	 *
2477
	 * @param string $emailAddress
2478
	 * @param string $displayName
2479
	 * @param int    $cnt
2480
	 * @param array  &$props
2481
	 * @param array  &$properties
2482
	 * @param array  &$nremails
2483
	 * @param int    &$abprovidertype
2484
	 *
2485
	 * @return
2486
	 */
2487
	private function setEmailAddress($emailAddress, $displayName, $cnt, &$props, &$properties, &$nremails, &$abprovidertype) {
2488
		if (isset($emailAddress)) {
2489
			$name = (isset($displayName)) ? $displayName : $emailAddress;
2490
2491
			$props[$properties["emailaddress{$cnt}"]] = $emailAddress;
2492
			$props[$properties["emailaddressdemail{$cnt}"]] = $emailAddress;
2493
			$props[$properties["emailaddressdname{$cnt}"]] = $name;
2494
			$props[$properties["emailaddresstype{$cnt}"]] = "SMTP";
2495
			$props[$properties["emailaddressentryid{$cnt}"]] = mapi_createoneoff($name, "SMTP", $emailAddress);
1 ignored issue
show
Bug introduced by
The function mapi_createoneoff was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

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

2658
		$recur["end"] = $this->getDayStartOfTimestamp(/** @scrutinizer ignore-type */ 0x7FFFFFFF); // Maximum GMT value for end by default
Loading history...
2659
2660
		if (isset($message->recurrence->until)) {
2661
			$recur["term"] = 0x21;
2662
			$recur["end"] = $message->recurrence->until;
2663
		}
2664
		elseif (isset($message->recurrence->occurrences)) {
2665
			$recur["term"] = 0x22;
2666
			$recur["numoccur"] = $message->recurrence->occurrences;
2667
		}
2668
		else {
2669
			$recur["term"] = 0x23;
2670
		}
2671
2672
		if (isset($message->recurrence->dayofweek)) {
2673
			$recur["weekdays"] = $message->recurrence->dayofweek;
2674
		}
2675
		if (isset($message->recurrence->weekofmonth)) {
2676
			$recur["nday"] = $message->recurrence->weekofmonth;
2677
		}
2678
		if (isset($message->recurrence->monthofyear)) {
2679
			// MAPI stores months as the amount of minutes until the beginning of the month in a
2680
			// non-leapyear. Why this is, is totally unclear.
2681
			$monthminutes = [0, 44640, 84960, 129600, 172800, 217440, 260640, 305280, 348480, 393120, 437760, 480960];
2682
			$recur["month"] = $monthminutes[$message->recurrence->monthofyear - 1];
2683
		}
2684
		if (isset($message->recurrence->dayofmonth)) {
2685
			$recur["monthday"] = $message->recurrence->dayofmonth;
2686
		}
2687
	}
2688
2689
	/**
2690
	 * Extracts the email address (mailbox@host) from an email address because
2691
	 * some devices send email address as "Firstname Lastname" <[email protected]>.
2692
	 *
2693
	 *  @see http://developer.berlios.de/mantis/view.php?id=486
2694
	 *
2695
	 *  @param string           $email
2696
	 *
2697
	 *  @return string or false on error
2698
	 */
2699
	private function extractEmailAddress($email) {
2700
		if (!isset($this->zRFC822)) {
2701
			$this->zRFC822 = new Mail_RFC822();
2702
		}
2703
		$parsedAddress = $this->zRFC822->parseAddressList($email);
2704
		if (!isset($parsedAddress[0]->mailbox) || !isset($parsedAddress[0]->host)) {
2705
			return false;
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...
2706
		}
2707
2708
		return $parsedAddress[0]->mailbox . '@' . $parsedAddress[0]->host;
2709
	}
2710
2711
	/**
2712
	 * Returns the message body for a required format.
2713
	 *
2714
	 * @param MAPIMessage $mapimessage
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...
2715
	 * @param int         $bpReturnType
2716
	 * @param SyncObject  $message
2717
	 *
2718
	 * @return bool
2719
	 */
2720
	private function setMessageBodyForType($mapimessage, $bpReturnType, &$message) {
2721
		$truncateHtmlSafe = false;
2722
		// default value is PR_BODY
2723
		$property = PR_BODY;
2724
2725
		switch ($bpReturnType) {
2726
			case SYNC_BODYPREFERENCE_HTML:
2727
				$property = PR_HTML;
2728
				$truncateHtmlSafe = true;
2729
				break;
2730
2731
			case SYNC_BODYPREFERENCE_RTF:
2732
				$property = PR_RTF_COMPRESSED;
2733
				break;
2734
2735
			case SYNC_BODYPREFERENCE_MIME:
2736
				$stat = $this->imtoinet($mapimessage, $message);
2737
				if (isset($message->asbody)) {
2738
					$message->asbody->type = $bpReturnType;
2739
				}
2740
2741
				return $stat;
2742
		}
2743
2744
		$stream = mapi_openproperty($mapimessage, $property, IID_IStream, 0, 0);
1 ignored issue
show
Bug introduced by
The function mapi_openproperty was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2744
		$stream = /** @scrutinizer ignore-call */ mapi_openproperty($mapimessage, $property, IID_IStream, 0, 0);
Loading history...
2745
		if ($stream) {
2746
			$stat = mapi_stream_stat($stream);
1 ignored issue
show
Bug introduced by
The function mapi_stream_stat was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2746
			$stat = /** @scrutinizer ignore-call */ mapi_stream_stat($stream);
Loading history...
2747
			$streamsize = $stat['cb'];
2748
		}
2749
		else {
2750
			$streamsize = 0;
2751
		}
2752
2753
		// set the properties according to supported AS version
2754
		if (Request::GetProtocolVersion() >= 12.0) {
2755
			$message->asbody = new SyncBaseBody();
2756
			$message->asbody->type = $bpReturnType;
2757
			if ($bpReturnType == SYNC_BODYPREFERENCE_RTF) {
2758
				$body = $this->mapiReadStream($stream, $streamsize);
2759
				$message->asbody->data = StringStreamWrapper::Open(base64_encode($body));
2760
			}
2761
			elseif (isset($message->internetcpid) && $bpReturnType == SYNC_BODYPREFERENCE_HTML) {
2762
				// if PR_HTML is UTF-8 we can stream it directly, else we have to convert to UTF-8 & wrap it
2763
				if ($message->internetcpid == INTERNET_CPID_UTF8) {
2764
					$message->asbody->data = MAPIStreamWrapper::Open($stream, $truncateHtmlSafe);
2765
				}
2766
				else {
2767
					$body = $this->mapiReadStream($stream, $streamsize);
2768
					$message->asbody->data = StringStreamWrapper::Open(Utils::ConvertCodepageStringToUtf8($message->internetcpid, $body), $truncateHtmlSafe);
2769
					$message->internetcpid = INTERNET_CPID_UTF8;
2770
				}
2771
			}
2772
			else {
2773
				$message->asbody->data = MAPIStreamWrapper::Open($stream);
2774
			}
2775
			$message->asbody->estimatedDataSize = $streamsize;
2776
		}
2777
		else {
2778
			$body = $this->mapiReadStream($stream, $streamsize);
2779
			$message->body = str_replace("\n", "\r\n", w2u(str_replace("\r", "", $body)));
2780
			$message->bodysize = $streamsize;
2781
			$message->bodytruncated = 0;
2782
		}
2783
2784
		return true;
2785
	}
2786
2787
	/**
2788
	 * Reads from a mapi stream, if it's set. If not, returns an empty string.
2789
	 *
2790
	 * @param resource $stream
2791
	 * @param int      $size
2792
	 *
2793
	 * @return string
2794
	 */
2795
	private function mapiReadStream($stream, $size) {
2796
		if (!$stream || $size == 0) {
0 ignored issues
show
introduced by
$stream is of type resource, thus it always evaluated to false.
Loading history...
2797
			return "";
2798
		}
2799
2800
		return mapi_stream_read($stream, $size);
1 ignored issue
show
Bug introduced by
The function mapi_stream_read was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2800
		return /** @scrutinizer ignore-call */ mapi_stream_read($stream, $size);
Loading history...
2801
	}
2802
2803
	/**
2804
	 * A wrapper for mapi_inetmapi_imtoinet function.
2805
	 *
2806
	 * @param MAPIMessage $mapimessage
2807
	 * @param SyncObject  $message
2808
	 *
2809
	 * @return bool
2810
	 */
2811
	private function imtoinet($mapimessage, &$message) {
2812
		$mapiEmail = mapi_getprops($mapimessage, [PR_EC_IMAP_EMAIL]);
1 ignored issue
show
Bug introduced by
The function mapi_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2812
		$mapiEmail = /** @scrutinizer ignore-call */ mapi_getprops($mapimessage, [PR_EC_IMAP_EMAIL]);
Loading history...
2813
		$stream = false;
2814
		if (isset($mapiEmail[PR_EC_IMAP_EMAIL]) || MAPIUtils::GetError(PR_EC_IMAP_EMAIL, $mapiEmail) == MAPI_E_NOT_ENOUGH_MEMORY) {
2815
			$stream = mapi_openproperty($mapimessage, PR_EC_IMAP_EMAIL, IID_IStream, 0, 0);
1 ignored issue
show
Bug introduced by
The function mapi_openproperty was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2815
			$stream = /** @scrutinizer ignore-call */ mapi_openproperty($mapimessage, PR_EC_IMAP_EMAIL, IID_IStream, 0, 0);
Loading history...
2816
			SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->imtoinet(): using PR_EC_IMAP_EMAIL as full RFC822 message");
2817
		}
2818
		else {
2819
			$addrbook = $this->getAddressbook();
2820
			$stream = mapi_inetmapi_imtoinet($this->session, $addrbook, $mapimessage, ['use_tnef' => -1, 'ignore_missing_attachments' => 1]);
1 ignored issue
show
Bug introduced by
The function mapi_inetmapi_imtoinet was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2820
			$stream = /** @scrutinizer ignore-call */ mapi_inetmapi_imtoinet($this->session, $addrbook, $mapimessage, ['use_tnef' => -1, 'ignore_missing_attachments' => 1]);
Loading history...
2821
		}
2822
		if (is_resource($stream)) {
2823
			$mstreamstat = mapi_stream_stat($stream);
1 ignored issue
show
Bug introduced by
The function mapi_stream_stat was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2823
			$mstreamstat = /** @scrutinizer ignore-call */ mapi_stream_stat($stream);
Loading history...
2824
			$streamsize = $mstreamstat["cb"];
2825
			if (isset($streamsize)) {
2826
				if (Request::GetProtocolVersion() >= 12.0) {
2827
					if (!isset($message->asbody)) {
2828
						$message->asbody = new SyncBaseBody();
2829
					}
2830
					$message->asbody->data = MAPIStreamWrapper::Open($stream);
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

2830
					$message->asbody->data = MAPIStreamWrapper::Open(/** @scrutinizer ignore-type */ $stream);
Loading history...
2831
					$message->asbody->estimatedDataSize = $streamsize;
2832
					$message->asbody->truncated = 0;
2833
				}
2834
				else {
2835
					$message->mimedata = MAPIStreamWrapper::Open($stream);
2836
					$message->mimesize = $streamsize;
2837
					$message->mimetruncated = 0;
2838
				}
2839
				unset($message->body, $message->bodytruncated);
2840
2841
				return true;
2842
			}
2843
		}
2844
		SLog::Write(LOGLEVEL_ERROR, "MAPIProvider->imtoinet(): got no stream or content from mapi_inetmapi_imtoinet()");
2845
2846
		return false;
2847
	}
2848
2849
	/**
2850
	 * Sets the message body.
2851
	 *
2852
	 * @param MAPIMessage       $mapimessage
2853
	 * @param ContentParameters $contentparameters
2854
	 * @param SyncObject        $message
2855
	 */
2856
	private function setMessageBody($mapimessage, $contentparameters, &$message) {
2857
		// get the available body preference types
2858
		$bpTypes = $contentparameters->GetBodyPreference();
2859
		if ($bpTypes !== false) {
2860
			SLog::Write(LOGLEVEL_DEBUG, sprintf("BodyPreference types: %s", implode(', ', $bpTypes)));
2861
			// do not send mime data if the client requests it
2862
			if (($contentparameters->GetMimeSupport() == SYNC_MIMESUPPORT_NEVER) && ($key = array_search(SYNC_BODYPREFERENCE_MIME, $bpTypes) !== false)) {
2863
				unset($bpTypes[$key]);
2864
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Remove mime body preference type because the device required no mime support. BodyPreference types: %s", implode(', ', $bpTypes)));
2865
			}
2866
			// get the best fitting preference type
2867
			$bpReturnType = Utils::GetBodyPreferenceBestMatch($bpTypes);
2868
			SLog::Write(LOGLEVEL_DEBUG, sprintf("GetBodyPreferenceBestMatch: %d", $bpReturnType));
2869
			$bpo = $contentparameters->BodyPreference($bpReturnType);
2870
			SLog::Write(LOGLEVEL_DEBUG, sprintf("bpo: truncation size:'%d', allornone:'%d', preview:'%d'", $bpo->GetTruncationSize(), $bpo->GetAllOrNone(), $bpo->GetPreview()));
0 ignored issues
show
Bug introduced by
The method GetPreview() does not exist on BodyPreference. 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

2870
			SLog::Write(LOGLEVEL_DEBUG, sprintf("bpo: truncation size:'%d', allornone:'%d', preview:'%d'", $bpo->GetTruncationSize(), $bpo->GetAllOrNone(), $bpo->/** @scrutinizer ignore-call */ GetPreview()));
Loading history...
Bug introduced by
The method GetAllOrNone() does not exist on BodyPreference. 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

2870
			SLog::Write(LOGLEVEL_DEBUG, sprintf("bpo: truncation size:'%d', allornone:'%d', preview:'%d'", $bpo->GetTruncationSize(), $bpo->/** @scrutinizer ignore-call */ GetAllOrNone(), $bpo->GetPreview()));
Loading history...
Bug introduced by
The method GetTruncationSize() does not exist on BodyPreference. 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

2870
			SLog::Write(LOGLEVEL_DEBUG, sprintf("bpo: truncation size:'%d', allornone:'%d', preview:'%d'", $bpo->/** @scrutinizer ignore-call */ GetTruncationSize(), $bpo->GetAllOrNone(), $bpo->GetPreview()));
Loading history...
2871
2872
			// Android Blackberry expects a full mime message for signed emails
2873
			// @see https://jira.z-hub.io/projects/ZP/issues/ZP-1154
2874
			// @TODO change this when refactoring
2875
			$props = mapi_getprops($mapimessage, [PR_MESSAGE_CLASS]);
1 ignored issue
show
Bug introduced by
The function mapi_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2875
			$props = /** @scrutinizer ignore-call */ mapi_getprops($mapimessage, [PR_MESSAGE_CLASS]);
Loading history...
2876
			if (isset($props[PR_MESSAGE_CLASS]) &&
2877
					stripos($props[PR_MESSAGE_CLASS], 'IPM.Note.SMIME.MultipartSigned') !== false &&
2878
					($key = array_search(SYNC_BODYPREFERENCE_MIME, $bpTypes) !== false)) {
0 ignored issues
show
Unused Code introduced by
The assignment to $key is dead and can be removed.
Loading history...
2879
				SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->setMessageBody(): enforcing SYNC_BODYPREFERENCE_MIME type for a signed message"));
2880
				$bpReturnType = SYNC_BODYPREFERENCE_MIME;
2881
			}
2882
2883
			$this->setMessageBodyForType($mapimessage, $bpReturnType, $message);
2884
			// only set the truncation size data if device set it in request
2885
			if ($bpo->GetTruncationSize() != false &&
2886
					$bpReturnType != SYNC_BODYPREFERENCE_MIME &&
2887
					$message->asbody->estimatedDataSize > $bpo->GetTruncationSize()
2888
				) {
2889
				// Truncated plaintext requests are used on iOS for the preview in the email list. All images and links should be removed - see https://jira.z-hub.io/browse/ZP-1025
2890
				if ($bpReturnType == SYNC_BODYPREFERENCE_PLAIN) {
2891
					SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->setMessageBody(): truncated plain-text body requested, stripping all links and images");
2892
					// Get more data because of the filtering it's most probably going down in size. It's going to be truncated to the correct size below.
2893
					$plainbody = stream_get_contents($message->asbody->data, $bpo->GetTruncationSize() * 5);
2894
					$message->asbody->data = StringStreamWrapper::Open(preg_replace('/<http(s){0,1}:\/\/.*?>/i', '', $plainbody));
2895
				}
2896
2897
				// truncate data stream
2898
				ftruncate($message->asbody->data, $bpo->GetTruncationSize());
0 ignored issues
show
Bug introduced by
It seems like $bpo->GetTruncationSize() can also be of type true; however, parameter $size of ftruncate() does only seem to accept integer, 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

2898
				ftruncate($message->asbody->data, /** @scrutinizer ignore-type */ $bpo->GetTruncationSize());
Loading history...
2899
				$message->asbody->truncated = 1;
2900
			}
2901
			// set the preview or windows phones won't show the preview of an email
2902
			if (Request::GetProtocolVersion() >= 14.0 && $bpo->GetPreview()) {
2903
				$message->asbody->preview = Utils::Utf8_truncate(MAPIUtils::readPropStream($mapimessage, PR_BODY), $bpo->GetPreview());
0 ignored issues
show
Bug introduced by
It seems like $bpo->GetPreview() can also be of type true; however, parameter $length of Utils::Utf8_truncate() 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

2903
				$message->asbody->preview = Utils::Utf8_truncate(MAPIUtils::readPropStream($mapimessage, PR_BODY), /** @scrutinizer ignore-type */ $bpo->GetPreview());
Loading history...
2904
			}
2905
		}
2906
		else {
2907
			// Override 'body' for truncation
2908
			$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

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

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

2987
		$this->addressbook = /** @scrutinizer ignore-call */ mapi_openaddressbook($this->session);
Loading history...
2988
		$result = mapi_last_hresult();
1 ignored issue
show
Bug introduced by
The function mapi_last_hresult was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2988
		$result = /** @scrutinizer ignore-call */ mapi_last_hresult();
Loading history...
2989
		if ($result && $this->addressbook === false) {
2990
			SLog::Write(LOGLEVEL_ERROR, sprintf("MAPIProvider->getAddressbook error opening addressbook 0x%X", $result));
2991
2992
			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...
2993
		}
2994
2995
		return $this->addressbook;
2996
	}
2997
2998
	/**
2999
	 * Gets the required store properties.
3000
	 *
3001
	 * @return array
3002
	 */
3003
	public function GetStoreProps() {
3004
		if (!isset($this->storeProps) || empty($this->storeProps)) {
3005
			SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->GetStoreProps(): Getting store properties.");
3006
			$this->storeProps = mapi_getprops($this->store, [PR_IPM_SUBTREE_ENTRYID, PR_IPM_OUTBOX_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_ENTRYID, PR_IPM_PUBLIC_FOLDERS_ENTRYID, PR_IPM_FAVORITES_ENTRYID, PR_MAILBOX_OWNER_ENTRYID]);
1 ignored issue
show
Bug introduced by
The function mapi_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

3006
			$this->storeProps = /** @scrutinizer ignore-call */ 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]);
Loading history...
3007
			// make sure all properties are set
3008
			if (!isset($this->storeProps[PR_IPM_WASTEBASKET_ENTRYID])) {
3009
				$this->storeProps[PR_IPM_WASTEBASKET_ENTRYID] = false;
3010
			}
3011
			if (!isset($this->storeProps[PR_IPM_SENTMAIL_ENTRYID])) {
3012
				$this->storeProps[PR_IPM_SENTMAIL_ENTRYID] = false;
3013
			}
3014
			if (!isset($this->storeProps[PR_IPM_OUTBOX_ENTRYID])) {
3015
				$this->storeProps[PR_IPM_OUTBOX_ENTRYID] = false;
3016
			}
3017
			if (!isset($this->storeProps[PR_IPM_PUBLIC_FOLDERS_ENTRYID])) {
3018
				$this->storeProps[PR_IPM_PUBLIC_FOLDERS_ENTRYID] = false;
3019
			}
3020
		}
3021
3022
		return $this->storeProps;
3023
	}
3024
3025
	/**
3026
	 * Gets the required inbox properties.
3027
	 *
3028
	 * @return array
3029
	 */
3030
	public function GetInboxProps() {
3031
		if (!isset($this->inboxProps) || empty($this->inboxProps)) {
3032
			SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->GetInboxProps(): Getting inbox properties.");
3033
			$this->inboxProps = [];
3034
			$inbox = mapi_msgstore_getreceivefolder($this->store);
1 ignored issue
show
Bug introduced by
The function mapi_msgstore_getreceivefolder was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

3034
			$inbox = /** @scrutinizer ignore-call */ mapi_msgstore_getreceivefolder($this->store);
Loading history...
3035
			if ($inbox) {
3036
				$this->inboxProps = mapi_getprops($inbox, [PR_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_TASK_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_JOURNAL_ENTRYID]);
1 ignored issue
show
Bug introduced by
The function mapi_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

3036
				$this->inboxProps = /** @scrutinizer ignore-call */ 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]);
Loading history...
3037
				// make sure all properties are set
3038
				if (!isset($this->inboxProps[PR_ENTRYID])) {
3039
					$this->inboxProps[PR_ENTRYID] = false;
3040
				}
3041
				if (!isset($this->inboxProps[PR_IPM_DRAFTS_ENTRYID])) {
3042
					$this->inboxProps[PR_IPM_DRAFTS_ENTRYID] = false;
3043
				}
3044
				if (!isset($this->inboxProps[PR_IPM_TASK_ENTRYID])) {
3045
					$this->inboxProps[PR_IPM_TASK_ENTRYID] = false;
3046
				}
3047
				if (!isset($this->inboxProps[PR_IPM_APPOINTMENT_ENTRYID])) {
3048
					$this->inboxProps[PR_IPM_APPOINTMENT_ENTRYID] = false;
3049
				}
3050
				if (!isset($this->inboxProps[PR_IPM_CONTACT_ENTRYID])) {
3051
					$this->inboxProps[PR_IPM_CONTACT_ENTRYID] = false;
3052
				}
3053
				if (!isset($this->inboxProps[PR_IPM_NOTE_ENTRYID])) {
3054
					$this->inboxProps[PR_IPM_NOTE_ENTRYID] = false;
3055
				}
3056
				if (!isset($this->inboxProps[PR_IPM_JOURNAL_ENTRYID])) {
3057
					$this->inboxProps[PR_IPM_JOURNAL_ENTRYID] = false;
3058
				}
3059
			}
3060
		}
3061
3062
		return $this->inboxProps;
3063
	}
3064
3065
	/**
3066
	 * Gets the required store root properties.
3067
	 *
3068
	 * @return array
3069
	 */
3070
	private function getRootProps() {
3071
		if (!isset($this->rootProps)) {
3072
			$root = mapi_msgstore_openentry($this->store, null);
1 ignored issue
show
Bug introduced by
The function mapi_msgstore_openentry was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

3072
			$root = /** @scrutinizer ignore-call */ mapi_msgstore_openentry($this->store, null);
Loading history...
3073
			$this->rootProps = mapi_getprops($root, [PR_IPM_OL2007_ENTRYIDS]);
1 ignored issue
show
Bug introduced by
The function mapi_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

3073
			$this->rootProps = /** @scrutinizer ignore-call */ mapi_getprops($root, [PR_IPM_OL2007_ENTRYIDS]);
Loading history...
3074
		}
3075
3076
		return $this->rootProps;
3077
	}
3078
3079
	/**
3080
	 * Returns an array with entryids of some special folders.
3081
	 *
3082
	 * @return array
3083
	 */
3084
	private function getSpecialFoldersData() {
3085
		// The persist data of an entry in PR_IPM_OL2007_ENTRYIDS consists of:
3086
		//      PersistId - e.g. RSF_PID_SUGGESTED_CONTACTS (2 bytes)
3087
		//      DataElementsSize - size of DataElements field (2 bytes)
3088
		//      DataElements - array of PersistElement structures (variable size)
3089
		//          PersistElement Structure consists of
3090
		//              ElementID - e.g. RSF_ELID_ENTRYID (2 bytes)
3091
		//              ElementDataSize - size of ElementData (2 bytes)
3092
		//              ElementData - The data for the special folder identified by the PersistID (variable size)
3093
		if (empty($this->specialFoldersData)) {
3094
			$this->specialFoldersData = [];
3095
			$rootProps = $this->getRootProps();
3096
			if (isset($rootProps[PR_IPM_OL2007_ENTRYIDS])) {
3097
				$persistData = $rootProps[PR_IPM_OL2007_ENTRYIDS];
3098
				while (strlen($persistData) > 0) {
3099
					// PERSIST_SENTINEL marks the end of the persist data
3100
					if (strlen($persistData) == 4 && $persistData == PERSIST_SENTINEL) {
3101
						break;
3102
					}
3103
					$unpackedData = unpack("vdataSize/velementID/velDataSize", substr($persistData, 2, 6));
3104
					if (isset($unpackedData['dataSize'], $unpackedData['elementID']) && $unpackedData['elementID'] == RSF_ELID_ENTRYID && isset($unpackedData['elDataSize'])) {
3105
						$this->specialFoldersData[] = substr($persistData, 8, $unpackedData['elDataSize']);
3106
						// Add PersistId and DataElementsSize lengths to the data size as they're not part of it
3107
						$persistData = substr($persistData, $unpackedData['dataSize'] + 4);
3108
					}
3109
					else {
3110
						SLog::Write(LOGLEVEL_INFO, "MAPIProvider->getSpecialFoldersData(): persistent data is not valid");
3111
						break;
3112
					}
3113
				}
3114
			}
3115
		}
3116
3117
		return $this->specialFoldersData;
3118
	}
3119
3120
	/**
3121
	 * Extracts email address from PR_SEARCH_KEY property if possible.
3122
	 *
3123
	 * @param string $searchKey
3124
	 *
3125
	 * @see https://jira.z-hub.io/browse/ZP-1178
3126
	 *
3127
	 * @return string
3128
	 */
3129
	private function getEmailAddressFromSearchKey($searchKey) {
3130
		if (strpos($searchKey, ':') !== false && strpos($searchKey, '@') !== false) {
3131
			SLog::Write(LOGLEVEL_INFO, "MAPIProvider->getEmailAddressFromSearchKey(): fall back to PR_SEARCH_KEY or PR_SENT_REPRESENTING_SEARCH_KEY to resolve user and get email address");
3132
3133
			return trim(strtolower(explode(':', $searchKey)[1]));
3134
		}
3135
3136
		return "";
3137
	}
3138
3139
	/**
3140
	 * Returns categories for a message.
3141
	 *
3142
	 * @param binary $parentsourcekey
3143
	 * @param binary $sourcekey
3144
	 *
3145
	 * @return array or false on failure
3146
	 */
3147
	public function GetMessageCategories($parentsourcekey, $sourcekey) {
3148
		$entryid = mapi_msgstore_entryidfromsourcekey($this->store, $parentsourcekey, $sourcekey);
1 ignored issue
show
Bug introduced by
The function mapi_msgstore_entryidfromsourcekey was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

3148
		$entryid = /** @scrutinizer ignore-call */ mapi_msgstore_entryidfromsourcekey($this->store, $parentsourcekey, $sourcekey);
Loading history...
3149
		if (!$entryid) {
3150
			SLog::Write(LOGLEVEL_INFO, sprintf("MAPIProvider->GetMessageCategories(): Couldn't retrieve message, sourcekey: '%s', parentsourcekey: '%s'", bin2hex($sourcekey), bin2hex($parentsourcekey)));
3151
3152
			return false;
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...
3153
		}
3154
		$mapimessage = mapi_msgstore_openentry($this->store, $entryid);
1 ignored issue
show
Bug introduced by
The function mapi_msgstore_openentry was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

3154
		$mapimessage = /** @scrutinizer ignore-call */ mapi_msgstore_openentry($this->store, $entryid);
Loading history...
3155
		$emailMapping = MAPIMapping::GetEmailMapping();
3156
		$emailMapping = ["categories" => $emailMapping["categories"]];
3157
		$messageCategories = $this->getProps($mapimessage, $emailMapping);
3158
		if (isset($messageCategories[$emailMapping["categories"]]) && is_array($messageCategories[$emailMapping["categories"]])) {
3159
			return $messageCategories[$emailMapping["categories"]];
3160
		}
3161
3162
		return false;
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...
3163
	}
3164
}
3165