Passed
Branch master (f497d2)
by Mike
03:18
created

MAPIProvider::GetFolder()   C

Complexity

Conditions 14
Paths 14

Size

Total Lines 56
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 35
nc 14
nop 1
dl 0
loc 56
rs 6.2666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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