Passed
Push — master ( 9aa611...cfa144 )
by
unknown
17:17 queued 03:11
created

MAPIProvider::getAbPropsFromEntryID()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 10
rs 10
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-2024 grommunio GmbH
6
 */
7
8
class MAPIProvider {
9
	private $session;
10
	private $store;
11
	private $zRFC822;
12
	private $addressbook;
13
	private $storeProps;
14
	private $inboxProps;
15
	private $rootProps;
16
	private $specialFoldersData;
17
18
	/**
19
	 * Constructor of the MAPI Provider
20
	 * Almost all methods of this class require a MAPI session and/or store.
21
	 *
22
	 * @param resource $session
23
	 * @param resource $store
24
	 */
25
	public function __construct($session, $store) {
26
		$this->session = $session;
27
		$this->store = $store;
28
	}
29
30
	/*----------------------------------------------------------------------------------------------------------
31
	 * GETTER
32
	 */
33
34
	/**
35
	 * Reads a message from MAPI
36
	 * Depending on the message class, a contact, appointment, task or email is read.
37
	 *
38
	 * @param mixed             $mapimessage
39
	 * @param ContentParameters $contentparameters
40
	 *
41
	 * @return SyncObject
42
	 */
43
	public function GetMessage($mapimessage, $contentparameters) {
44
		// Gets the Sync object from a MAPI object according to its message class
45
46
		$props = mapi_getprops($mapimessage, [PR_MESSAGE_CLASS]);
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
47
		if (isset($props[PR_MESSAGE_CLASS])) {
48
			$messageclass = $props[PR_MESSAGE_CLASS];
49
		}
50
		else {
51
			$messageclass = "IPM";
52
		}
53
54
		if (strpos($messageclass, "IPM.Contact") === 0) {
55
			return $this->getContact($mapimessage, $contentparameters);
56
		}
57
		if (strpos($messageclass, "IPM.Appointment") === 0) {
58
			return $this->getAppointment($mapimessage, $contentparameters);
59
		}
60
		if (strpos($messageclass, "IPM.Task") === 0 && strpos($messageclass, "IPM.TaskRequest") === false) {
61
			return $this->getTask($mapimessage, $contentparameters);
62
		}
63
		if (strpos($messageclass, "IPM.StickyNote") === 0) {
64
			return $this->getNote($mapimessage, $contentparameters);
65
		}
66
67
		return $this->getEmail($mapimessage, $contentparameters);
68
	}
69
70
	/**
71
	 * Reads a contact object from MAPI.
72
	 *
73
	 * @param mixed             $mapimessage
74
	 * @param ContentParameters $contentparameters
75
	 *
76
	 * @return SyncContact
77
	 */
78
	private function getContact($mapimessage, $contentparameters) {
79
		$message = new SyncContact();
80
81
		// Standard one-to-one mappings first
82
		$this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetContactMapping());
83
84
		// Contact specific props
85
		$contactproperties = MAPIMapping::GetContactProperties();
86
		$messageprops = $this->getProps($mapimessage, $contactproperties);
87
88
		// set the body according to contentparameters and supported AS version
89
		$this->setMessageBody($mapimessage, $contentparameters, $message);
90
91
		// check the picture
92
		if (isset($messageprops[$contactproperties["haspic"]]) && $messageprops[$contactproperties["haspic"]]) {
93
			// Add attachments
94
			$attachtable = mapi_message_getattachmenttable($mapimessage);
95
			mapi_table_restrict($attachtable, MAPIUtils::GetContactPicRestriction());
96
			$rows = mapi_table_queryallrows($attachtable, [PR_ATTACH_NUM, PR_ATTACH_SIZE]);
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_SIZE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_NUM was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
97
98
			foreach ($rows as $row) {
99
				if (isset($row[PR_ATTACH_NUM])) {
100
					$mapiattach = mapi_message_openattach($mapimessage, $row[PR_ATTACH_NUM]);
101
					$message->picture = base64_encode(mapi_attach_openbin($mapiattach, PR_ATTACH_DATA_BIN));
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_DATA_BIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
102
				}
103
			}
104
		}
105
106
		return $message;
107
	}
108
109
	/**
110
	 * Reads a task object from MAPI.
111
	 *
112
	 * @param mixed             $mapimessage
113
	 * @param ContentParameters $contentparameters
114
	 *
115
	 * @return SyncTask
116
	 */
117
	private function getTask($mapimessage, $contentparameters) {
118
		$message = new SyncTask();
119
120
		// Standard one-to-one mappings first
121
		$this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetTaskMapping());
122
123
		// Task specific props
124
		$taskproperties = MAPIMapping::GetTaskProperties();
125
		$messageprops = $this->getProps($mapimessage, $taskproperties);
126
127
		// set the body according to contentparameters and supported AS version
128
		$this->setMessageBody($mapimessage, $contentparameters, $message);
129
130
		// task with deadoccur is an occurrence of a recurring task and does not need to be handled as recurring
131
		// webaccess does not set deadoccur for the initial recurring task
132
		if (isset($messageprops[$taskproperties["isrecurringtag"]]) &&
133
			$messageprops[$taskproperties["isrecurringtag"]] &&
134
			(!isset($messageprops[$taskproperties["deadoccur"]]) ||
135
			(isset($messageprops[$taskproperties["deadoccur"]]) &&
136
			!$messageprops[$taskproperties["deadoccur"]]))) {
137
			// Process recurrence
138
			$message->recurrence = new SyncTaskRecurrence();
139
			$this->getRecurrence($mapimessage, $messageprops, $message, $message->recurrence, false, $taskproperties);
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, $taskproperties);
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 (!isset($messageprops[$appointmentprops["remindertime"]]) || $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
			// if no embedded vCal-Uid is found use hexed GOID
191
			$message->uid = getUidFromGoid($message->uid) ?? strtoupper(bin2hex($message->uid));
0 ignored issues
show
Bug introduced by
The function getUidFromGoid 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

191
			$message->uid = /** @scrutinizer ignore-call */ getUidFromGoid($message->uid) ?? strtoupper(bin2hex($message->uid));
Loading history...
192
		}
193
194
		// Always set organizer information because some devices do not work properly without it
195
		if (isset($messageprops[$appointmentprops["representingentryid"]], $messageprops[$appointmentprops["representingname"]])) {
196
			$message->organizeremail = $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 = $messageprops[$appointmentprops["representingname"]];
202
		}
203
204
		if (!empty($messageprops[$appointmentprops["timezonetag"]])) {
205
			$tz = $this->getTZFromMAPIBlob($messageprops[$appointmentprops["timezonetag"]]);
206
			$message->timezone = base64_encode(TimezoneUtil::GetSyncBlobFromTZ($tz));
207
		}
208
		elseif (!empty($messageprops[$appointmentprops["tzdefstart"]])) {
209
			$tzDefStart = TimezoneUtil::CreateTimezoneDefinitionObject($messageprops[$appointmentprops["tzdefstart"]]);
210
			$tz = TimezoneUtil::GetTzFromTimezoneDef($tzDefStart);
211
			$message->timezone = base64_encode(TimezoneUtil::GetSyncBlobFromTZ($tz));
212
		}
213
		elseif (!empty($messageprops[$appointmentprops["timezonedesc"]])) {
214
			// Windows uses UTC in timezone description in opposite to mstzones in TimezoneUtil which uses GMT
215
			$wintz = str_replace("UTC", "GMT", $messageprops[$appointmentprops["timezonedesc"]]);
216
			$tz = TimezoneUtil::GetFullTZFromTZName(TimezoneUtil::GetTZNameFromWinTZ($wintz));
217
			$message->timezone = base64_encode(TimezoneUtil::GetSyncBlobFromTZ($tz));
218
		}
219
		else {
220
			// set server default timezone (correct timezone should be configured!)
221
			$tz = TimezoneUtil::GetFullTZ();
222
		}
223
224
		if (isset($messageprops[$appointmentprops["isrecurring"]]) && $messageprops[$appointmentprops["isrecurring"]]) {
225
			// Process recurrence
226
			$message->recurrence = new SyncRecurrence();
227
			$this->getRecurrence($mapimessage, $messageprops, $message, $message->recurrence, $tz, $appointmentprops);
228
229
			if (empty($message->alldayevent)) {
230
				$message->timezone = base64_encode(TimezoneUtil::GetSyncBlobFromTZ($tz));
231
			}
232
		}
233
234
		// Do attendees
235
		$reciptable = mapi_message_getrecipienttable($mapimessage);
236
		// Only get first 256 recipients, to prevent possible load issues.
237
		$rows = mapi_table_queryrows($reciptable, [PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS, PR_ADDRTYPE, PR_RECIPIENT_TRACKSTATUS, PR_RECIPIENT_TYPE, PR_SEARCH_KEY], 0, 256);
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SMTP_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_RECIPIENT_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_RECIPIENT_TRACKSTATUS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
238
239
		// Exception: we do not synchronize appointments with more than 250 attendees
240
		if (count($rows) > 250) {
241
			$message->id = bin2hex($messageprops[$appointmentprops["sourcekey"]]);
242
			$mbe = new SyncObjectBrokenException("Appointment has too many attendees");
243
			$mbe->SetSyncObject($message);
244
245
			throw $mbe;
246
		}
247
248
		if (count($rows) > 0) {
249
			$message->attendees = [];
250
		}
251
252
		foreach ($rows as $row) {
253
			$attendee = new SyncAttendee();
254
255
			$attendee->name = $row[PR_DISPLAY_NAME];
256
			// smtp address is always a proper email address
257
			if (isset($row[PR_SMTP_ADDRESS])) {
258
				$attendee->email = $row[PR_SMTP_ADDRESS];
259
			}
260
			elseif (isset($row[PR_ADDRTYPE], $row[PR_EMAIL_ADDRESS])) {
261
				// if address type is SMTP, it's also a proper email address
262
				if ($row[PR_ADDRTYPE] == "SMTP") {
263
					$attendee->email = $row[PR_EMAIL_ADDRESS];
264
				}
265
				// if address type is ZARAFA, the PR_EMAIL_ADDRESS contains username
266
				elseif ($row[PR_ADDRTYPE] == "ZARAFA") {
267
					$userinfo = @nsp_getuserinfo($row[PR_EMAIL_ADDRESS]);
268
					if (is_array($userinfo) && isset($userinfo["primary_email"])) {
269
						$attendee->email = $userinfo["primary_email"];
270
					}
271
					// if the user was not found, do a fallback to PR_SEARCH_KEY
272
					elseif (isset($row[PR_SEARCH_KEY])) {
273
						$attendee->email = $this->getEmailAddressFromSearchKey($row[PR_SEARCH_KEY]);
274
					}
275
					else {
276
						SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->getAppointment: The attendee '%s' of type ZARAFA can not be resolved. Code: 0x%X", $row[PR_EMAIL_ADDRESS], mapi_last_hresult()));
277
					}
278
				}
279
			}
280
281
			// set attendee's status and type if they're available and if we are the organizer
282
			$storeprops = $this->GetStoreProps();
283
			if (isset($row[PR_RECIPIENT_TRACKSTATUS], $messageprops[$appointmentprops["representingentryid"]], $storeprops[PR_MAILBOX_OWNER_ENTRYID]) &&
0 ignored issues
show
Bug introduced by
The constant PR_MAILBOX_OWNER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
284
					$messageprops[$appointmentprops["representingentryid"]] == $storeprops[PR_MAILBOX_OWNER_ENTRYID]) {
285
				$attendee->attendeestatus = $row[PR_RECIPIENT_TRACKSTATUS];
286
			}
287
			if (isset($row[PR_RECIPIENT_TYPE])) {
288
				$attendee->attendeetype = $row[PR_RECIPIENT_TYPE];
289
			}
290
			// Some attendees have no email or name (eg resources), and if you
291
			// don't send one of those fields, the phone will give an error ... so
292
			// we don't send it in that case.
293
			// also ignore the "attendee" if the email is equal to the organizers' email
294
			if (isset($attendee->name, $attendee->email) && $attendee->email != "" && (!isset($message->organizeremail) || (isset($message->organizeremail) && $attendee->email != $message->organizeremail))) {
295
				array_push($message->attendees, $attendee);
296
			}
297
		}
298
299
		// Status 0 = no meeting, status 1 = organizer, status 2/3/4/5 = tentative/accepted/declined/notresponded
300
		if (isset($messageprops[$appointmentprops["meetingstatus"]]) && $messageprops[$appointmentprops["meetingstatus"]] > 1) {
301
			if (!isset($message->attendees) || !is_array($message->attendees)) {
302
				$message->attendees = [];
303
			}
304
			// Work around iOS6 cancellation issue when there are no attendees for this meeting. Just add ourselves as the sole attendee.
305
			if (count($message->attendees) == 0) {
306
				SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->getAppointment: adding ourself as an attendee for iOS6 workaround"));
307
				$attendee = new SyncAttendee();
308
309
				$meinfo = nsp_getuserinfo(Request::GetUser());
310
311
				if (is_array($meinfo)) {
312
					$attendee->email = $meinfo["primary_email"];
313
					$attendee->name = $meinfo["fullname"];
314
					$attendee->attendeetype = MAPI_TO;
0 ignored issues
show
Bug introduced by
The constant MAPI_TO was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
315
316
					array_push($message->attendees, $attendee);
317
				}
318
			}
319
			$message->responsetype = $messageprops[$appointmentprops["responsestatus"]];
320
		}
321
322
		// If it's an appointment which doesn't have any attendees, we have to make sure that
323
		// the user is the owner or it will not work properly with android devices
324
		if (isset($messageprops[$appointmentprops["meetingstatus"]]) && $messageprops[$appointmentprops["meetingstatus"]] == olNonMeeting && empty($message->attendees)) {
0 ignored issues
show
Bug introduced by
The constant olNonMeeting was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
325
			$meinfo = nsp_getuserinfo(Request::GetUser());
326
327
			if (is_array($meinfo)) {
328
				$message->organizeremail = $meinfo["primary_email"];
329
				$message->organizername = $meinfo["fullname"];
330
				SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->getAppointment(): setting ourself as the organizer for an appointment without attendees.");
331
			}
332
		}
333
334
		if (!isset($message->nativebodytype)) {
335
			$message->nativebodytype = MAPIUtils::GetNativeBodyType($messageprops);
336
		}
337
		elseif ($message->nativebodytype == SYNC_BODYPREFERENCE_UNDEFINED) {
338
			$nbt = MAPIUtils::GetNativeBodyType($messageprops);
339
			SLog::Write(LOGLEVEL_INFO, sprintf("MAPIProvider->getAppointment(): native body type is undefined. Set it to %d.", $nbt));
340
			$message->nativebodytype = $nbt;
341
		}
342
343
		// If the user is working from a location other than the office the busystatus should be interpreted as free.
344
		if (isset($message->busystatus) && $message->busystatus == fbWorkingElsewhere) {
0 ignored issues
show
Bug introduced by
The constant fbWorkingElsewhere was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
345
			$message->busystatus = fbFree;
0 ignored issues
show
Bug introduced by
The constant fbFree was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
346
		}
347
348
		// If the busystatus has the value of -1, we should be interpreted as tentative (1)
349
		if (isset($message->busystatus) && $message->busystatus == -1) {
350
			$message->busystatus = fbTentative;
0 ignored issues
show
Bug introduced by
The constant fbTentative was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
351
		}
352
353
		// All-day events might appear as 24h (or multiple of it) long when they start not exactly at midnight (+/- bias of the timezone)
354
		if (isset($message->alldayevent) && $message->alldayevent) {
355
			// Adjust all day events for the appointments timezone
356
			$duration = $message->endtime - $message->starttime;
357
			// AS pre 16: time in local timezone - convert if it isn't on midnight
358
			if (Request::GetProtocolVersion() < 16.0) {
359
				$localStartTime = localtime($message->starttime, 1);
360
				if ($localStartTime['tm_hour'] || $localStartTime['tm_min']) {
361
					SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->getAppointment(): all-day event starting not midnight - convert to local time");
362
					$serverTz = TimezoneUtil::GetFullTZ();
363
					$message->starttime = $this->getGMTTimeByTZ($this->getLocaltimeByTZ($message->starttime, $tz), $serverTz);
364
					if (!$message->timezone) {
365
						$message->timezone = base64_encode(TimezoneUtil::GetSyncBlobFromTZ($tz));
366
					}
367
				}
368
			}
369
			else {
370
				// AS 16: apply timezone as this MUST result in midnight (to be sent to the client)
371
				$message->starttime = $this->getLocaltimeByTZ($message->starttime, $tz);
372
			}
373
			$message->endtime = $message->starttime + $duration;
374
			if (Request::GetProtocolVersion() >= 16.0) {
375
				// no timezone information should be sent
376
				unset($message->timezone);
377
			}
378
		}
379
380
		// Add attachments to message for AS 16.0 and higher
381
		if (Request::GetProtocolVersion() >= 16.0) {
382
			// add attachments
383
			$entryid = bin2hex($messageprops[$appointmentprops["entryid"]]);
384
			$parentSourcekey = bin2hex($messageprops[$appointmentprops["parentsourcekey"]]);
385
			$this->setAttachment($mapimessage, $message, $entryid, $parentSourcekey);
386
			// add location
387
			$message->location2 = new SyncLocation();
388
			$this->getASlocation($mapimessage, $message->location2, $appointmentprops);
389
		}
390
391
		return $message;
392
	}
393
394
	/**
395
	 * Reads recurrence information from MAPI.
396
	 *
397
	 * @param mixed      $mapimessage
398
	 * @param array      $recurprops
399
	 * @param SyncObject &$syncMessage     the message
400
	 * @param SyncObject &$syncRecurrence  the  recurrence message
401
	 * @param array      $tz               timezone information
402
	 * @param array      $appointmentprops property definitions
403
	 */
404
	private function getRecurrence($mapimessage, $recurprops, &$syncMessage, &$syncRecurrence, $tz, $appointmentprops) {
405
		if ($syncRecurrence instanceof SyncTaskRecurrence) {
406
			$recurrence = new TaskRecurrence($this->store, $mapimessage);
0 ignored issues
show
Bug introduced by
The type TaskRecurrence 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...
407
		}
408
		else {
409
			$recurrence = new Recurrence($this->store, $mapimessage);
0 ignored issues
show
Bug introduced by
The type Recurrence 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...
410
		}
411
412
		switch ($recurrence->recur["type"]) {
413
			case 10: // daily
414
				switch ($recurrence->recur["subtype"]) {
415
					default:
416
						$syncRecurrence->type = 0;
417
						break;
418
419
					case 1:
420
						$syncRecurrence->type = 0;
421
						$syncRecurrence->dayofweek = 62; // mon-fri
422
						$syncRecurrence->interval = 1;
423
						break;
424
				}
425
				break;
426
427
			case 11: // weekly
428
				$syncRecurrence->type = 1;
429
				break;
430
431
			case 12: // monthly
432
				switch ($recurrence->recur["subtype"]) {
433
					default:
434
						$syncRecurrence->type = 2;
435
						break;
436
437
					case 3:
438
						$syncRecurrence->type = 3;
439
						break;
440
				}
441
				break;
442
443
			case 13: // yearly
444
				switch ($recurrence->recur["subtype"]) {
445
					default:
446
						$syncRecurrence->type = 4;
447
						break;
448
449
					case 2:
450
						$syncRecurrence->type = 5;
451
						break;
452
453
					case 3:
454
						$syncRecurrence->type = 6;
455
						break;
456
				}
457
		}
458
459
		// Termination
460
		switch ($recurrence->recur["term"]) {
461
			case 0x21:
462
				$syncRecurrence->until = $recurrence->recur["end"];
463
				// fixes Mantis #350 : recur-end does not consider timezones - use ClipEnd if available
464
				if (isset($recurprops[$recurrence->proptags["enddate_recurring"]])) {
465
					$syncRecurrence->until = $recurprops[$recurrence->proptags["enddate_recurring"]];
466
				}
467
				// add one day (minus 1 sec) to the end time to make sure the last occurrence is covered
468
				$syncRecurrence->until += 86399;
469
				break;
470
471
			case 0x22:
472
				$syncRecurrence->occurrences = $recurrence->recur["numoccur"];
473
				break;
474
475
			case 0x23:
476
				// never ends
477
				break;
478
		}
479
480
		// Correct 'alldayevent' because outlook fails to set it on recurring items of 24 hours or longer
481
		if (isset($recurrence->recur["endocc"], $recurrence->recur["startocc"]) && ($recurrence->recur["endocc"] - $recurrence->recur["startocc"] >= 1440)) {
482
			$syncMessage->alldayevent = true;
483
		}
484
485
		// Interval is different according to the type/subtype
486
		switch ($recurrence->recur["type"]) {
487
			case 10:
488
				if ($recurrence->recur["subtype"] == 0) {
489
					$syncRecurrence->interval = (int) ($recurrence->recur["everyn"] / 1440);
490
				}  // minutes
491
				break;
492
493
			case 11:
494
			case 12:
495
				$syncRecurrence->interval = $recurrence->recur["everyn"];
496
				break; // months / weeks
497
498
			case 13:
499
				$syncRecurrence->interval = (int) ($recurrence->recur["everyn"] / 12);
500
				break; // months
501
		}
502
503
		if (isset($recurrence->recur["weekdays"])) {
504
			$syncRecurrence->dayofweek = $recurrence->recur["weekdays"];
505
		} // bitmask of days (1 == sunday, 128 == saturday
506
		if (isset($recurrence->recur["nday"])) {
507
			$syncRecurrence->weekofmonth = $recurrence->recur["nday"];
508
		} // N'th {DAY} of {X} (0-5)
509
		if (isset($recurrence->recur["month"])) {
510
			$syncRecurrence->monthofyear = (int) ($recurrence->recur["month"] / (60 * 24 * 29)) + 1;
511
		} // works ok due to rounding. see also $monthminutes below (1-12)
512
		if (isset($recurrence->recur["monthday"])) {
513
			$syncRecurrence->dayofmonth = $recurrence->recur["monthday"];
514
		} // day of month (1-31)
515
516
		// All changed exceptions are appointments within the 'exceptions' array. They contain the same items as a normal appointment
517
		foreach ($recurrence->recur["changed_occurrences"] as $change) {
518
			$exception = new SyncAppointmentException();
519
520
			// start, end, basedate, subject, remind_before, reminderset, location, busystatus, alldayevent, label
521
			if (isset($change["start"])) {
522
				$exception->starttime = $this->getGMTTimeByTZ($change["start"], $tz);
523
			}
524
			if (isset($change["end"])) {
525
				$exception->endtime = $this->getGMTTimeByTZ($change["end"], $tz);
526
			}
527
			if (isset($change["basedate"])) {
528
				// depending on the AS version the streamer is going to send the correct value
529
				$exception->exceptionstarttime = $exception->instanceid = $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

529
				$exception->exceptionstarttime = $exception->instanceid = $this->getGMTTimeByTZ(/** @scrutinizer ignore-type */ $this->getDayStartOfTimestamp($change["basedate"]) + $recurrence->recur["startocc"] * 60, $tz);
Loading history...
530
531
				// open body because getting only property might not work because of memory limit
532
				$exceptionatt = $recurrence->getExceptionAttachment($change["basedate"]);
533
				if ($exceptionatt) {
534
					$exceptionobj = mapi_attach_openobj($exceptionatt, 0);
535
					$this->setMessageBodyForType($exceptionobj, SYNC_BODYPREFERENCE_PLAIN, $exception);
536
					if (Request::GetProtocolVersion() >= 16.0) {
537
						// add attachment
538
						$data = mapi_message_getprops($mapimessage, [PR_ENTRYID, PR_PARENT_SOURCE_KEY]);
0 ignored issues
show
Bug introduced by
The function mapi_message_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

538
						$data = /** @scrutinizer ignore-call */ mapi_message_getprops($mapimessage, [PR_ENTRYID, PR_PARENT_SOURCE_KEY]);
Loading history...
Bug introduced by
The constant PR_PARENT_SOURCE_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
539
						$this->setAttachment($exceptionobj, $exception, bin2hex($data[PR_ENTRYID]), bin2hex($data[PR_PARENT_SOURCE_KEY]), bin2hex($change["basedate"]));
540
						// add location
541
						$exception->location2 = new SyncLocation();
542
						$this->getASlocation($exceptionobj, $exception->location2, $appointmentprops);
543
					}
544
				}
545
			}
546
			if (isset($change["subject"])) {
547
				$exception->subject = $change["subject"];
548
			}
549
			if (isset($change["reminder_before"]) && $change["reminder_before"]) {
550
				$exception->reminder = $change["remind_before"];
551
			}
552
			if (isset($change["location"])) {
553
				$exception->location = $change["location"];
554
			}
555
			if (isset($change["busystatus"])) {
556
				$exception->busystatus = $change["busystatus"];
557
			}
558
			if (isset($change["alldayevent"])) {
559
				$exception->alldayevent = $change["alldayevent"];
560
			}
561
562
			// set some data from the original appointment
563
			if (isset($syncMessage->uid)) {
564
				$exception->uid = $syncMessage->uid;
565
			}
566
			if (isset($syncMessage->organizername)) {
567
				$exception->organizername = $syncMessage->organizername;
568
			}
569
			if (isset($syncMessage->organizeremail)) {
570
				$exception->organizeremail = $syncMessage->organizeremail;
571
			}
572
573
			if (!isset($syncMessage->exceptions)) {
574
				$syncMessage->exceptions = [];
575
			}
576
577
			// If the user is working from a location other than the office the busystatus should be interpreted as free.
578
			if (isset($exception->busystatus) && $exception->busystatus == fbWorkingElsewhere) {
0 ignored issues
show
Bug introduced by
The constant fbWorkingElsewhere was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
579
				$exception->busystatus = fbFree;
0 ignored issues
show
Bug introduced by
The constant fbFree was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
580
			}
581
582
			// If the busystatus has the value of -1, we should be interpreted as tentative (1)
583
			if (isset($exception->busystatus) && $exception->busystatus == -1) {
584
				$exception->busystatus = fbTentative;
0 ignored issues
show
Bug introduced by
The constant fbTentative was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
585
			}
586
587
			// if an exception lasts 24 hours and the series are an allday events, set also the exception to allday event,
588
			// otherwise it will be a 24 hour long event on some mobiles.
589
			if (isset($exception->starttime, $exception->endtime) && ($exception->endtime - $exception->starttime == 86400) && $syncMessage->alldayevent) {
590
				$exception->alldayevent = 1;
591
			}
592
			array_push($syncMessage->exceptions, $exception);
593
		}
594
595
		// Deleted appointments contain only the original date (basedate) and a 'deleted' tag
596
		foreach ($recurrence->recur["deleted_occurrences"] as $deleted) {
597
			$exception = new SyncAppointmentException();
598
599
			// depending on the AS version the streamer is going to send the correct value
600
			$exception->exceptionstarttime = $exception->instanceid = $this->getGMTTimeByTZ($this->getDayStartOfTimestamp($deleted) + $recurrence->recur["startocc"] * 60, $tz);
601
			$exception->deleted = "1";
602
603
			if (!isset($syncMessage->exceptions)) {
604
				$syncMessage->exceptions = [];
605
			}
606
607
			array_push($syncMessage->exceptions, $exception);
608
		}
609
610
		if (isset($syncMessage->complete) && $syncMessage->complete) {
611
			$syncRecurrence->complete = $syncMessage->complete;
612
		}
613
	}
614
615
	/**
616
	 * Reads an email object from MAPI.
617
	 *
618
	 * @param mixed             $mapimessage
619
	 * @param ContentParameters $contentparameters
620
	 *
621
	 * @return SyncEmail
622
	 */
623
	private function getEmail($mapimessage, $contentparameters) {
624
		// FIXME: It should be properly fixed when refactoring.
625
		$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

625
		$bpReturnType = Utils::GetBodyPreferenceBestMatch(/** @scrutinizer ignore-type */ $contentparameters->GetBodyPreference());
Loading history...
626
		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

626
		if (($contentparameters->/** @scrutinizer ignore-call */ GetMimeSupport() == SYNC_MIMESUPPORT_NEVER) ||
Loading history...
627
				($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

627
				($key = array_search(SYNC_BODYPREFERENCE_MIME, /** @scrutinizer ignore-type */ $contentparameters->GetBodyPreference()) === false) ||
Loading history...
628
				$bpReturnType != SYNC_BODYPREFERENCE_MIME) {
629
			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

629
			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

629
			MAPIUtils::ParseSmime(/** @scrutinizer ignore-type */ $this->session, $this->store, $this->getAddressbook(), $mapimessage);
Loading history...
630
		}
631
632
		$message = new SyncMail();
633
634
		$this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetEmailMapping());
635
636
		$emailproperties = MAPIMapping::GetEmailProperties();
637
		$messageprops = $this->getProps($mapimessage, $emailproperties);
638
639
		if (isset($messageprops[PR_SOURCE_KEY])) {
0 ignored issues
show
Bug introduced by
The constant PR_SOURCE_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
640
			$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...
641
		}
642
		else {
643
			$mbe = new SyncObjectBrokenException("The message doesn't have a sourcekey");
644
			$mbe->SetSyncObject($message);
645
646
			throw $mbe;
647
		}
648
649
		// set the body according to contentparameters and supported AS version
650
		$this->setMessageBody($mapimessage, $contentparameters, $message);
651
652
		$fromname = $fromaddr = "";
653
654
		if (isset($messageprops[$emailproperties["representingname"]])) {
655
			// remove encapsulating double quotes from the representingname
656
			$fromname = preg_replace('/^\"(.*)\"$/', "\${1}", $messageprops[$emailproperties["representingname"]]);
657
		}
658
		if (isset($messageprops[$emailproperties["representingsendersmtpaddress"]])) {
659
			$fromaddr = $messageprops[$emailproperties["representingsendersmtpaddress"]];
660
		}
661
		if ($fromaddr == "" && isset($messageprops[$emailproperties["representingentryid"]])) {
662
			$fromaddr = $this->getSMTPAddressFromEntryID($messageprops[$emailproperties["representingentryid"]]);
663
		}
664
665
		// if the email address can't be resolved, fall back to PR_SENT_REPRESENTING_SEARCH_KEY
666
		if ($fromaddr == "" && isset($messageprops[$emailproperties["representingsearchkey"]])) {
667
			$fromaddr = $this->getEmailAddressFromSearchKey($messageprops[$emailproperties["representingsearchkey"]]);
668
		}
669
670
		// if we couldn't still not get any $fromaddr, fall back to PR_SENDER_EMAIL_ADDRESS
671
		if ($fromaddr == "" && isset($messageprops[$emailproperties["senderemailaddress"]])) {
672
			$fromaddr = $messageprops[$emailproperties["senderemailaddress"]];
673
		}
674
675
		// there is some name, but no email address (e.g. mails from System Administrator) - use a generic invalid address
676
		if ($fromname != "" && $fromaddr == "") {
677
			$fromaddr = "invalid@invalid";
678
		}
679
680
		if ($fromname == $fromaddr) {
681
			$fromname = "";
682
		}
683
684
		if ($fromname) {
685
			$from = "\"" . $fromname . "\" <" . $fromaddr . ">";
686
		}
687
		else { // START CHANGED dw2412 HTC shows "error" if sender name is unknown
688
			$from = "\"" . $fromaddr . "\" <" . $fromaddr . ">";
689
		}
690
		// END CHANGED dw2412 HTC shows "error" if sender name is unknown
691
692
		$message->from = $from;
693
694
		// process Meeting Requests
695
		if (isset($message->messageclass) && strpos($message->messageclass, "IPM.Schedule.Meeting") === 0) {
696
			$message->meetingrequest = new SyncMeetingRequest();
697
			$this->getPropsFromMAPI($message->meetingrequest, $mapimessage, MAPIMapping::GetMeetingRequestMapping());
698
699
			$meetingrequestproperties = MAPIMapping::GetMeetingRequestProperties();
700
			$props = $this->getProps($mapimessage, $meetingrequestproperties);
701
702
			// Get the GOID
703
			if (isset($props[$meetingrequestproperties["goidtag"]])) {
704
				$message->meetingrequest->globalobjid = base64_encode($props[$meetingrequestproperties["goidtag"]]);
705
			}
706
707
			// Set Timezone
708
			if (isset($props[$meetingrequestproperties["timezonetag"]])) {
709
				$tz = $this->getTZFromMAPIBlob($props[$meetingrequestproperties["timezonetag"]]);
710
			}
711
			else {
712
				$tz = TimezoneUtil::GetFullTZ();
713
			}
714
715
			$message->meetingrequest->timezone = base64_encode(TimezoneUtil::GetSyncBlobFromTZ($tz));
716
717
			// send basedate if exception
718
			if (isset($props[$meetingrequestproperties["recReplTime"]]) ||
719
				(isset($props[$meetingrequestproperties["lidIsException"]]) && $props[$meetingrequestproperties["lidIsException"]] == true)) {
720
				if (isset($props[$meetingrequestproperties["recReplTime"]])) {
721
					$basedate = $props[$meetingrequestproperties["recReplTime"]];
722
					$message->meetingrequest->recurrenceid = $this->getGMTTimeByTZ($basedate, TimezoneUtil::GetGMTTz());
723
				}
724
				else {
725
					if (!isset($props[$meetingrequestproperties["goidtag"]]) || !isset($props[$meetingrequestproperties["recurStartTime"]]) || !isset($props[$meetingrequestproperties["timezonetag"]])) {
726
						SLog::Write(LOGLEVEL_WARN, "Missing property to set correct basedate for exception");
727
					}
728
					else {
729
						$basedate = Utils::ExtractBaseDate($props[$meetingrequestproperties["goidtag"]], $props[$meetingrequestproperties["recurStartTime"]]);
730
						$message->meetingrequest->recurrenceid = $this->getGMTTimeByTZ($basedate, $tz);
731
					}
732
				}
733
			}
734
735
			// Organizer is the sender
736
			if (strpos($message->messageclass, "IPM.Schedule.Meeting.Resp") === 0) {
737
				$message->meetingrequest->organizer = $message->to;
738
			}
739
			else {
740
				$message->meetingrequest->organizer = $message->from;
741
			}
742
743
			// Process recurrence
744
			if (isset($props[$meetingrequestproperties["isrecurringtag"]]) && $props[$meetingrequestproperties["isrecurringtag"]]) {
745
				$myrec = new SyncMeetingRequestRecurrence();
746
				// get recurrence -> put $message->meetingrequest as message so the 'alldayevent' is set correctly
747
				$this->getRecurrence($mapimessage, $props, $message->meetingrequest, $myrec, $tz, $meetingrequestproperties);
748
				$message->meetingrequest->recurrences = [$myrec];
749
			}
750
751
			// Force the 'alldayevent' in the object at all times. (non-existent == 0)
752
			if (!isset($message->meetingrequest->alldayevent) || $message->meetingrequest->alldayevent == "") {
753
				$message->meetingrequest->alldayevent = 0;
754
			}
755
756
			// Instancetype
757
			// 0 = single appointment
758
			// 1 = master recurring appointment
759
			// 2 = single instance of recurring appointment
760
			// 3 = exception of recurring appointment
761
			$message->meetingrequest->instancetype = 0;
762
			if (isset($props[$meetingrequestproperties["isrecurringtag"]]) && $props[$meetingrequestproperties["isrecurringtag"]] == 1) {
763
				$message->meetingrequest->instancetype = 1;
764
			}
765
			elseif ((!isset($props[$meetingrequestproperties["isrecurringtag"]]) || $props[$meetingrequestproperties["isrecurringtag"]] == 0) && isset($message->meetingrequest->recurrenceid)) {
766
				if (isset($props[$meetingrequestproperties["appSeqNr"]]) && $props[$meetingrequestproperties["appSeqNr"]] == 0) {
767
					$message->meetingrequest->instancetype = 2;
768
				}
769
				else {
770
					$message->meetingrequest->instancetype = 3;
771
				}
772
			}
773
774
			// Disable reminder if it is off
775
			if (!isset($props[$meetingrequestproperties["reminderset"]]) || $props[$meetingrequestproperties["reminderset"]] == false) {
776
				$message->meetingrequest->reminder = "";
777
			}
778
			// the property saves reminder in minutes, but we need it in secs
779
			else {
780
				// /set the default reminder time to seconds
781
				if ($props[$meetingrequestproperties["remindertime"]] == 0x5AE980E1) {
782
					$message->meetingrequest->reminder = 900;
783
				}
784
				else {
785
					$message->meetingrequest->reminder = $props[$meetingrequestproperties["remindertime"]] * 60;
786
				}
787
			}
788
789
			// Set sensitivity to 0 if missing
790
			if (!isset($message->meetingrequest->sensitivity)) {
791
				$message->meetingrequest->sensitivity = 0;
792
			}
793
794
			// If the user is working from a location other than the office the busystatus should be interpreted as free.
795
			if (isset($message->meetingrequest->busystatus) && $message->meetingrequest->busystatus == fbWorkingElsewhere) {
0 ignored issues
show
Bug introduced by
The constant fbWorkingElsewhere was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
796
				$message->meetingrequest->busystatus = fbFree;
0 ignored issues
show
Bug introduced by
The constant fbFree was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
797
			}
798
799
			// If the busystatus has the value of -1, we should be interpreted as tentative (1)
800
			if (isset($message->meetingrequest->busystatus) && $message->meetingrequest->busystatus == -1) {
801
				$message->meetingrequest->busystatus = fbTentative;
0 ignored issues
show
Bug introduced by
The constant fbTentative was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
802
			}
803
804
			// if a meeting request response hasn't been processed yet,
805
			// do it so that the attendee status is updated on the mobile
806
			if (!isset($messageprops[$emailproperties["processed"]])) {
807
				// check if we are not sending the MR so we can process it
808
				$cuser = GSync::GetBackend()->GetUserDetails(GSync::GetBackend()->GetCurrentUsername());
809
				if (isset($cuser["emailaddress"]) && $cuser["emailaddress"] != $fromaddr) {
810
					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...
811
						$req = new Meetingrequest($this->store, $mapimessage, $this->session);
0 ignored issues
show
Bug introduced by
The type Meetingrequest 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...
812
					}
813
					if ($req->isMeetingRequest() && !$req->isLocalOrganiser() && !$req->isMeetingOutOfDate()) {
814
						$req->doAccept(true, false, false);
815
					}
816
					if ($req->isMeetingRequestResponse()) {
817
						$req->processMeetingRequestResponse();
818
					}
819
					if ($req->isMeetingCancellation()) {
820
						$req->processMeetingCancellation();
821
					}
822
				}
823
			}
824
			$message->contentclass = DEFAULT_CALENDAR_CONTENTCLASS;
825
826
			// MeetingMessageType values
827
			// 0 = A silent update was performed, or the message type is unspecified.
828
			// 1 = Initial meeting request.
829
			// 2 = Full update.
830
			// 3 = Informational update.
831
			// 4 = Outdated. A newer meeting request or meeting update was received after this message.
832
			// 5 = Identifies the delegator's copy of the meeting request.
833
			// 6 = Identifies that the meeting request has been delegated and the meeting request cannot be responded to.
834
			$message->meetingrequest->meetingmessagetype = mtgEmpty;
0 ignored issues
show
Bug introduced by
The constant mtgEmpty was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
835
836
			if (isset($props[$meetingrequestproperties["meetingType"]])) {
837
				switch ($props[$meetingrequestproperties["meetingType"]]) {
838
					case mtgRequest:
0 ignored issues
show
Bug introduced by
The constant mtgRequest was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
839
						$message->meetingrequest->meetingmessagetype = 1;
840
						break;
841
842
					case mtgFull:
0 ignored issues
show
Bug introduced by
The constant mtgFull was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
843
						$message->meetingrequest->meetingmessagetype = 2;
844
						break;
845
846
					case mtgInfo:
0 ignored issues
show
Bug introduced by
The constant mtgInfo was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
847
						$message->meetingrequest->meetingmessagetype = 3;
848
						break;
849
850
					case mtgOutOfDate:
0 ignored issues
show
Bug introduced by
The constant mtgOutOfDate was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
851
						$message->meetingrequest->meetingmessagetype = 4;
852
						break;
853
854
					case mtgDelegatorCopy:
0 ignored issues
show
Bug introduced by
The constant mtgDelegatorCopy was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
855
						$message->meetingrequest->meetingmessagetype = 5;
856
						break;
857
				}
858
			}
859
		}
860
861
		// Add attachments to message
862
		$entryid = bin2hex($messageprops[$emailproperties["entryid"]]);
863
		$parentSourcekey = bin2hex($messageprops[$emailproperties["parentsourcekey"]]);
864
		$this->setAttachment($mapimessage, $message, $entryid, $parentSourcekey);
865
866
		// Get To/Cc as SMTP addresses (this is different from displayto and displaycc because we are putting
867
		// in the SMTP addresses as well, while displayto and displaycc could just contain the display names
868
		$message->to = [];
869
		$message->cc = [];
870
		$message->bcc = [];
871
872
		$reciptable = mapi_message_getrecipienttable($mapimessage);
873
		$rows = mapi_table_queryallrows($reciptable, [PR_RECIPIENT_TYPE, PR_DISPLAY_NAME, PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS, PR_ENTRYID, PR_SEARCH_KEY]);
0 ignored issues
show
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_RECIPIENT_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SMTP_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
874
875
		foreach ($rows as $row) {
876
			$address = "";
877
			$fulladdr = "";
878
879
			$addrtype = isset($row[PR_ADDRTYPE]) ? $row[PR_ADDRTYPE] : "";
880
881
			if (isset($row[PR_SMTP_ADDRESS])) {
882
				$address = $row[PR_SMTP_ADDRESS];
883
			}
884
			elseif ($addrtype == "SMTP" && isset($row[PR_EMAIL_ADDRESS])) {
885
				$address = $row[PR_EMAIL_ADDRESS];
886
			}
887
			elseif ($addrtype == "ZARAFA" && isset($row[PR_ENTRYID])) {
888
				$address = $this->getSMTPAddressFromEntryID($row[PR_ENTRYID]);
889
			}
890
891
			// if the user was not found, do a fallback to PR_SEARCH_KEY
892
			if (empty($address) && isset($row[PR_SEARCH_KEY])) {
893
				$address = $this->getEmailAddressFromSearchKey($row[PR_SEARCH_KEY]);
894
			}
895
896
			$name = isset($row[PR_DISPLAY_NAME]) ? $row[PR_DISPLAY_NAME] : "";
897
898
			if ($name == "" || $name == $address) {
899
				$fulladdr = $address;
900
			}
901
			else {
902
				if (substr($name, 0, 1) != '"' && substr($name, -1) != '"') {
903
					$fulladdr = "\"" . $name . "\" <" . $address . ">";
904
				}
905
				else {
906
					$fulladdr = $name . "<" . $address . ">";
907
				}
908
			}
909
910
			if ($row[PR_RECIPIENT_TYPE] == MAPI_TO) {
0 ignored issues
show
Bug introduced by
The constant MAPI_TO was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
911
				array_push($message->to, $fulladdr);
912
			}
913
			elseif ($row[PR_RECIPIENT_TYPE] == MAPI_CC) {
0 ignored issues
show
Bug introduced by
The constant MAPI_CC was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
914
				array_push($message->cc, $fulladdr);
915
			}
916
			elseif ($row[PR_RECIPIENT_TYPE] == MAPI_BCC) {
0 ignored issues
show
Bug introduced by
The constant MAPI_BCC was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
917
				array_push($message->bcc, $fulladdr);
918
			}
919
		}
920
921
		if (is_array($message->to) && !empty($message->to)) {
922
			$message->to = implode(", ", $message->to);
923
		}
924
		if (is_array($message->cc) && !empty($message->cc)) {
925
			$message->cc = implode(", ", $message->cc);
926
		}
927
		if (is_array($message->bcc) && !empty($message->bcc)) {
928
			$message->bcc = implode(", ", $message->bcc);
929
		}
930
931
		// without importance some mobiles assume "0" (low) - Mantis #439
932
		if (!isset($message->importance)) {
933
			$message->importance = IMPORTANCE_NORMAL;
0 ignored issues
show
Bug introduced by
The constant IMPORTANCE_NORMAL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
934
		}
935
936
		if (!isset($message->internetcpid)) {
937
			$message->internetcpid = (defined('STORE_INTERNET_CPID')) ? constant('STORE_INTERNET_CPID') : INTERNET_CPID_WINDOWS1252;
938
		}
939
		$this->setFlag($mapimessage, $message);
940
		// TODO checkcontentclass
941
		if (!isset($message->contentclass)) {
942
			$message->contentclass = DEFAULT_EMAIL_CONTENTCLASS;
943
		}
944
945
		if (!isset($message->nativebodytype)) {
946
			$message->nativebodytype = MAPIUtils::GetNativeBodyType($messageprops);
947
		}
948
		elseif ($message->nativebodytype == SYNC_BODYPREFERENCE_UNDEFINED) {
949
			$nbt = MAPIUtils::GetNativeBodyType($messageprops);
950
			SLog::Write(LOGLEVEL_INFO, sprintf("MAPIProvider->getEmail(): native body type is undefined. Set it to %d.", $nbt));
951
			$message->nativebodytype = $nbt;
952
		}
953
954
		// reply, reply to all, forward flags
955
		if (isset($message->lastverbexecuted) && $message->lastverbexecuted) {
956
			$message->lastverbexecuted = Utils::GetLastVerbExecuted($message->lastverbexecuted);
957
		}
958
959
		if ($messageprops[$emailproperties["messageflags"]] & MSGFLAG_UNSENT) {
0 ignored issues
show
Bug introduced by
The constant MSGFLAG_UNSENT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
960
			$message->isdraft = true;
961
		}
962
963
		return $message;
964
	}
965
966
	/**
967
	 * Reads a note object from MAPI.
968
	 *
969
	 * @param mixed             $mapimessage
970
	 * @param ContentParameters $contentparameters
971
	 *
972
	 * @return SyncNote
973
	 */
974
	private function getNote($mapimessage, $contentparameters) {
975
		$message = new SyncNote();
976
977
		// Standard one-to-one mappings first
978
		$this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetNoteMapping());
979
980
		// set the body according to contentparameters and supported AS version
981
		$this->setMessageBody($mapimessage, $contentparameters, $message);
982
983
		return $message;
984
	}
985
986
	/**
987
	 * Creates a SyncFolder from MAPI properties.
988
	 *
989
	 * @param mixed $folderprops
990
	 *
991
	 * @return SyncFolder
992
	 */
993
	public function GetFolder($folderprops) {
994
		$folder = new SyncFolder();
995
996
		$storeprops = $this->GetStoreProps();
997
998
		// For ZCP 7.0.x we need to retrieve more properties explicitly
999
		if (isset($folderprops[PR_SOURCE_KEY]) && !isset($folderprops[PR_ENTRYID]) && !isset($folderprops[PR_CONTAINER_CLASS])) {
0 ignored issues
show
Bug introduced by
The constant PR_SOURCE_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_CONTAINER_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1000
			$entryid = mapi_msgstore_entryidfromsourcekey($this->store, $folderprops[PR_SOURCE_KEY]);
1001
			$mapifolder = mapi_msgstore_openentry($this->store, $entryid);
1002
			$folderprops = mapi_getprops($mapifolder, [PR_DISPLAY_NAME, PR_PARENT_ENTRYID, PR_ENTRYID, PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY, PR_CONTAINER_CLASS, PR_ATTR_HIDDEN, PR_EXTENDED_FOLDER_FLAGS]);
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EXTENDED_FOLDER_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTR_HIDDEN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_PARENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_PARENT_SOURCE_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1003
			SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->GetFolder(): received insufficient of data from ICS. Fetching required data.");
1004
		}
1005
1006
		if (!isset(
1007
			$folderprops[PR_DISPLAY_NAME],
1008
			$folderprops[PR_PARENT_ENTRYID],
1009
			$folderprops[PR_SOURCE_KEY],
1010
			$folderprops[PR_ENTRYID],
1011
			$folderprops[PR_PARENT_SOURCE_KEY],
1012
			$storeprops[PR_IPM_SUBTREE_ENTRYID]
0 ignored issues
show
Bug introduced by
The constant PR_IPM_SUBTREE_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1013
		)) {
1014
			SLog::Write(LOGLEVEL_ERROR, "MAPIProvider->GetFolder(): invalid folder. Missing properties");
1015
1016
			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...
1017
		}
1018
1019
		// ignore hidden folders
1020
		if (isset($folderprops[PR_ATTR_HIDDEN]) && $folderprops[PR_ATTR_HIDDEN] != false) {
1021
			SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->GetFolder(): invalid folder '%s' as it is a hidden folder (PR_ATTR_HIDDEN)", $folderprops[PR_DISPLAY_NAME]));
1022
1023
			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...
1024
		}
1025
1026
		// ignore certain undesired folders, like "RSS Feeds", "Suggested contacts" and Journal
1027
		if ((isset($folderprops[PR_CONTAINER_CLASS]) && (
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (IssetNode && $folderpro...etSpecialFoldersData()), Probably Intended Meaning: IssetNode && ($folderpro...tSpecialFoldersData()))
Loading history...
1028
			$folderprops[PR_CONTAINER_CLASS] == "IPF.Note.OutlookHomepage" || $folderprops[PR_CONTAINER_CLASS] == "IPF.Journal")) ||
1029
			in_array($folderprops[PR_ENTRYID], $this->getSpecialFoldersData())
1030
		) {
1031
			SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->GetFolder(): folder '%s' should not be synchronized", $folderprops[PR_DISPLAY_NAME]));
1032
1033
			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...
1034
		}
1035
1036
		$folder->BackendId = bin2hex($folderprops[PR_SOURCE_KEY]);
1037
		$folderOrigin = DeviceManager::FLD_ORIGIN_USER;
1038
		if (GSync::GetBackend()->GetImpersonatedUser()) {
1039
			$folderOrigin = DeviceManager::FLD_ORIGIN_IMPERSONATED;
1040
		}
1041
		$folder->serverid = GSync::GetDeviceManager()->GetFolderIdForBackendId($folder->BackendId, true, $folderOrigin, $folderprops[PR_DISPLAY_NAME]);
1042
		if ($folderprops[PR_PARENT_ENTRYID] == $storeprops[PR_IPM_SUBTREE_ENTRYID] || $folderprops[PR_PARENT_ENTRYID] == $storeprops[PR_IPM_PUBLIC_FOLDERS_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_PUBLIC_FOLDERS_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1043
			$folder->parentid = "0";
1044
		}
1045
		else {
1046
			$folder->parentid = GSync::GetDeviceManager()->GetFolderIdForBackendId(bin2hex($folderprops[PR_PARENT_SOURCE_KEY]));
1047
		}
1048
		$folder->displayname = $folderprops[PR_DISPLAY_NAME];
1049
		$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

1049
		$folder->type = $this->GetFolderType($folderprops[PR_ENTRYID], /** @scrutinizer ignore-type */ isset($folderprops[PR_CONTAINER_CLASS]) ? $folderprops[PR_CONTAINER_CLASS] : false);
Loading history...
1050
1051
		return $folder;
1052
	}
1053
1054
	/**
1055
	 * Returns the foldertype for an entryid
1056
	 * Gets the folder type by checking the default folders in MAPI.
1057
	 *
1058
	 * @param string $entryid
1059
	 * @param string $class   (opt)
1060
	 *
1061
	 * @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...
1062
	 */
1063
	public function GetFolderType($entryid, $class = false) {
1064
		$storeprops = $this->GetStoreProps();
1065
		$inboxprops = $this->GetInboxProps();
1066
1067
		if ($entryid == $storeprops[PR_IPM_WASTEBASKET_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_WASTEBASKET_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1068
			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...
1069
		}
1070
		if ($entryid == $storeprops[PR_IPM_SENTMAIL_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_SENTMAIL_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1071
			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...
1072
		}
1073
		if ($entryid == $storeprops[PR_IPM_OUTBOX_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_OUTBOX_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1074
			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...
1075
		}
1076
1077
		// Public folders do not have inboxprops
1078
		if (!empty($inboxprops)) {
1079
			if ($entryid == $inboxprops[PR_ENTRYID]) {
1080
				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...
1081
			}
1082
			if ($entryid == $inboxprops[PR_IPM_DRAFTS_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_DRAFTS_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1083
				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...
1084
			}
1085
			if ($entryid == $inboxprops[PR_IPM_TASK_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_TASK_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1086
				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...
1087
			}
1088
			if ($entryid == $inboxprops[PR_IPM_APPOINTMENT_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_APPOINTMENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1089
				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...
1090
			}
1091
			if ($entryid == $inboxprops[PR_IPM_CONTACT_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_CONTACT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1092
				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...
1093
			}
1094
			if ($entryid == $inboxprops[PR_IPM_NOTE_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_NOTE_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1095
				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...
1096
			}
1097
			if ($entryid == $inboxprops[PR_IPM_JOURNAL_ENTRYID]) {
0 ignored issues
show
Bug introduced by
The constant PR_IPM_JOURNAL_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1098
				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...
1099
			}
1100
		}
1101
1102
		// user created folders
1103
		if ($class == "IPF.Note") {
1104
			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...
1105
		}
1106
		if ($class == "IPF.Task") {
1107
			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...
1108
		}
1109
		if ($class == "IPF.Appointment") {
1110
			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...
1111
		}
1112
		if ($class == "IPF.Contact") {
1113
			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...
1114
		}
1115
		if ($class == "IPF.StickyNote") {
1116
			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...
1117
		}
1118
		if ($class == "IPF.Journal") {
1119
			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...
1120
		}
1121
1122
		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...
1123
	}
1124
1125
	/**
1126
	 * Indicates if the entry id is a default MAPI folder.
1127
	 *
1128
	 * @param string $entryid
1129
	 *
1130
	 * @return bool
1131
	 */
1132
	public function IsMAPIDefaultFolder($entryid) {
1133
		$msgstore_props = mapi_getprops($this->store, [PR_ENTRYID, PR_DISPLAY_NAME, PR_IPM_SUBTREE_ENTRYID, PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID, PR_MDB_PROVIDER, PR_IPM_PUBLIC_FOLDERS_ENTRYID, PR_IPM_FAVORITES_ENTRYID, PR_MAILBOX_OWNER_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_OUTBOX_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_MDB_PROVIDER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_MAILBOX_OWNER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_WASTEBASKET_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_FAVORITES_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_PUBLIC_FOLDERS_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_SUBTREE_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_SENTMAIL_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1134
1135
		$inboxProps = [];
1136
		$inbox = mapi_msgstore_getreceivefolder($this->store);
1137
		if (!mapi_last_hresult()) {
1138
			$inboxProps = mapi_getprops($inbox, [PR_ENTRYID]);
1139
		}
1140
1141
		$root = mapi_msgstore_openentry($this->store, null); // TODO use getRootProps()
1142
		$rootProps = mapi_getprops($root, [PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_JOURNAL_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_TASK_ENTRYID, PR_ADDITIONAL_REN_ENTRYIDS]);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_DRAFTS_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_TASK_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_CONTACT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_JOURNAL_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_NOTE_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ADDITIONAL_REN_ENTRYIDS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_APPOINTMENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1143
1144
		$additional_ren_entryids = [];
1145
		if (isset($rootProps[PR_ADDITIONAL_REN_ENTRYIDS])) {
1146
			$additional_ren_entryids = $rootProps[PR_ADDITIONAL_REN_ENTRYIDS];
1147
		}
1148
1149
		$defaultfolders = [
1150
			"inbox" => ["inbox" => PR_ENTRYID],
1151
			"outbox" => ["store" => PR_IPM_OUTBOX_ENTRYID],
1152
			"sent" => ["store" => PR_IPM_SENTMAIL_ENTRYID],
1153
			"wastebasket" => ["store" => PR_IPM_WASTEBASKET_ENTRYID],
1154
			"favorites" => ["store" => PR_IPM_FAVORITES_ENTRYID],
1155
			"publicfolders" => ["store" => PR_IPM_PUBLIC_FOLDERS_ENTRYID],
1156
			"calendar" => ["root" => PR_IPM_APPOINTMENT_ENTRYID],
1157
			"contact" => ["root" => PR_IPM_CONTACT_ENTRYID],
1158
			"drafts" => ["root" => PR_IPM_DRAFTS_ENTRYID],
1159
			"journal" => ["root" => PR_IPM_JOURNAL_ENTRYID],
1160
			"note" => ["root" => PR_IPM_NOTE_ENTRYID],
1161
			"task" => ["root" => PR_IPM_TASK_ENTRYID],
1162
			"junk" => ["additional" => 4],
1163
			"syncissues" => ["additional" => 1],
1164
			"conflicts" => ["additional" => 0],
1165
			"localfailures" => ["additional" => 2],
1166
			"serverfailures" => ["additional" => 3],
1167
		];
1168
1169
		foreach ($defaultfolders as $key => $prop) {
1170
			$tag = reset($prop);
1171
			$from = key($prop);
1172
1173
			switch ($from) {
1174
				case "inbox":
1175
					if (isset($inboxProps[$tag]) && $entryid == $inboxProps[$tag]) {
1176
						SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Inbox found, key '%s'", $key));
1177
1178
						return true;
1179
					}
1180
					break;
1181
1182
				case "store":
1183
					if (isset($msgstore_props[$tag]) && $entryid == $msgstore_props[$tag]) {
1184
						SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Store folder found, key '%s'", $key));
1185
1186
						return true;
1187
					}
1188
					break;
1189
1190
				case "root":
1191
					if (isset($rootProps[$tag]) && $entryid == $rootProps[$tag]) {
1192
						SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Root folder found, key '%s'", $key));
1193
1194
						return true;
1195
					}
1196
					break;
1197
1198
				case "additional":
1199
					if (isset($additional_ren_entryids[$tag]) && $entryid == $additional_ren_entryids[$tag]) {
1200
						SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Additional folder found, key '%s'", $key));
1201
1202
						return true;
1203
					}
1204
					break;
1205
			}
1206
		}
1207
1208
		return false;
1209
	}
1210
1211
	/*----------------------------------------------------------------------------------------------------------
1212
	 * PreDeleteMessage
1213
	 */
1214
1215
	/**
1216
	 * Performs any actions before a message is imported for deletion.
1217
	 *
1218
	 * @param mixed $mapimessage
1219
	 */
1220
	public function PreDeleteMessage($mapimessage) {
1221
		if ($mapimessage === false) {
1222
			return;
1223
		}
1224
		// Currently this is relevant only for MeetingRequests so cancellation emails can be sent to attendees.
1225
		$props = mapi_getprops($mapimessage, [PR_MESSAGE_CLASS]);
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1226
		$messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false;
1227
1228
		if ($messageClass !== false && stripos($messageClass, 'ipm.appointment') === 0) {
1229
			SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->PreDeleteMessage(): Appointment message");
1230
			$mr = new Meetingrequest($this->store, $mapimessage, $this->session);
1231
			$mr->doCancelInvitation();
1232
		}
1233
	}
1234
1235
	/*----------------------------------------------------------------------------------------------------------
1236
	 * SETTER
1237
	 */
1238
1239
	/**
1240
	 * Writes a SyncObject to MAPI
1241
	 * Depending on the message class, a contact, appointment, task or email is written.
1242
	 *
1243
	 * @param mixed      $mapimessage
1244
	 * @param SyncObject $message
1245
	 *
1246
	 * @return SyncObject
1247
	 */
1248
	public function SetMessage($mapimessage, $message) {
1249
		// TODO check with instanceof
1250
		switch (strtolower(get_class($message))) {
1251
			case "synccontact":
1252
				return $this->setContact($mapimessage, $message);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->setContact($mapimessage, $message) returns the type boolean which is incompatible with the documented return type SyncObject.
Loading history...
1253
1254
			case "syncappointment":
1255
				return $this->setAppointment($mapimessage, $message);
1256
1257
			case "synctask":
1258
				return $this->setTask($mapimessage, $message);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->setTask($mapimessage, $message) returns the type boolean which is incompatible with the documented return type SyncObject.
Loading history...
1259
1260
			case "syncnote":
1261
				return $this->setNote($mapimessage, $message);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->setNote($mapimessage, $message) returns the type boolean which is incompatible with the documented return type SyncObject.
Loading history...
1262
1263
			default:
1264
				// for emails only flag (read and todo) changes are possible
1265
				return $this->setEmail($mapimessage, $message);
1266
		}
1267
	}
1268
1269
	/**
1270
	 * Writes SyncMail to MAPI (actually flags only).
1271
	 *
1272
	 * @param mixed    $mapimessage
1273
	 * @param SyncMail $message
1274
	 *
1275
	 * @return SyncObject
1276
	 */
1277
	private function setEmail($mapimessage, $message) {
1278
		$response = new SyncMailResponse();
1279
		// update categories
1280
		if (!isset($message->categories)) {
1281
			$message->categories = [];
1282
		}
1283
		$emailmap = MAPIMapping::GetEmailMapping();
1284
		$emailprops = MAPIMapping::GetEmailProperties();
1285
		$this->setPropsInMAPI($mapimessage, $message, ["categories" => $emailmap["categories"]]);
1286
1287
		$flagmapping = MAPIMapping::GetMailFlagsMapping();
1288
		$flagprops = MAPIMapping::GetMailFlagsProperties();
1289
		$flagprops = array_merge($this->getPropIdsFromStrings($flagmapping), $this->getPropIdsFromStrings($flagprops));
1290
		// flag specific properties to be set
1291
		$props = $delprops = [];
1292
1293
		// save DRAFTs
1294
		if (isset($message->asbody) && $message->asbody instanceof SyncBaseBody) {
1295
			// iOS+Nine send a RFC822 message
1296
			if (isset($message->asbody->type) && $message->asbody->type == SYNC_BODYPREFERENCE_MIME) {
1297
				SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->setEmail(): Use the mapi_inetmapi_imtomapi function to save draft email");
1298
				$mime = stream_get_contents($message->asbody->data);
1299
				$ab = mapi_openaddressbook($this->session);
1300
				mapi_inetmapi_imtomapi($this->session, $this->store, $ab, $mapimessage, $mime, []);
1301
			}
1302
			else {
1303
				$props[$emailmap["messageclass"]] = "IPM.Note";
1304
				$this->setPropsInMAPI($mapimessage, $message, $emailmap);
1305
			}
1306
			$props[$emailprops["messageflags"]] = MSGFLAG_UNSENT | MSGFLAG_READ;
0 ignored issues
show
Bug introduced by
The constant MSGFLAG_UNSENT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant MSGFLAG_READ was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1307
1308
			if (isset($message->asbody->type) && $message->asbody->type == SYNC_BODYPREFERENCE_HTML && isset($message->asbody->data)) {
1309
				$props[$emailprops["html"]] = stream_get_contents($message->asbody->data);
1310
			}
1311
1312
			// Android devices send the recipients in to, cc and bcc tags
1313
			if (isset($message->to) || isset($message->cc) || isset($message->bcc)) {
1314
				$recips = [];
1315
				$this->addRecips($message->to, MAPI_TO, $recips);
0 ignored issues
show
Bug introduced by
The constant MAPI_TO was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1316
				$this->addRecips($message->cc, MAPI_CC, $recips);
0 ignored issues
show
Bug introduced by
The constant MAPI_CC was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1317
				$this->addRecips($message->bcc, MAPI_BCC, $recips);
0 ignored issues
show
Bug introduced by
The constant MAPI_BCC was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1318
1319
				mapi_message_modifyrecipients($mapimessage, MODRECIP_MODIFY, $recips);
0 ignored issues
show
Bug introduced by
The constant MODRECIP_MODIFY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1320
			}
1321
			// remove PR_CLIENT_SUBMIT_TIME
1322
			mapi_deleteprops(
1323
				$mapimessage,
1324
				[
1325
					$emailprops["clientsubmittime"],
1326
				]
1327
			);
1328
		}
1329
1330
		// save DRAFTs attachments
1331
		if (!empty($message->asattachments)) {
1332
			$this->editAttachments($mapimessage, $message->asattachments, $response);
1333
		}
1334
1335
		// unset message flags if:
1336
		// flag is not set
1337
		if (empty($message->flag) ||
1338
			// flag status is not set
1339
			!isset($message->flag->flagstatus) ||
1340
			// flag status is 0 or empty
1341
			(isset($message->flag->flagstatus) && ($message->flag->flagstatus == 0 || $message->flag->flagstatus == ""))) {
1342
			// if message flag is empty, some properties need to be deleted
1343
			// and some set to 0 or false
1344
1345
			$props[$flagprops["todoitemsflags"]] = 0;
1346
			$props[$flagprops["status"]] = 0;
1347
			$props[$flagprops["completion"]] = 0.0;
1348
			$props[$flagprops["flagtype"]] = "";
1349
			$props[$flagprops["ordinaldate"]] = 0x7FFFFFFF; // ordinal date is 12am 1.1.4501, set it to max possible value
1350
			$props[$flagprops["subordinaldate"]] = "";
1351
			$props[$flagprops["replyrequested"]] = false;
1352
			$props[$flagprops["responserequested"]] = false;
1353
			$props[$flagprops["reminderset"]] = false;
1354
			$props[$flagprops["complete"]] = false;
1355
1356
			$delprops[] = $flagprops["todotitle"];
1357
			$delprops[] = $flagprops["duedate"];
1358
			$delprops[] = $flagprops["startdate"];
1359
			$delprops[] = $flagprops["datecompleted"];
1360
			$delprops[] = $flagprops["utcstartdate"];
1361
			$delprops[] = $flagprops["utcduedate"];
1362
			$delprops[] = $flagprops["completetime"];
1363
			$delprops[] = $flagprops["flagstatus"];
1364
			$delprops[] = $flagprops["flagicon"];
1365
		}
1366
		else {
1367
			$this->setPropsInMAPI($mapimessage, $message->flag, $flagmapping);
1368
			$props[$flagprops["todoitemsflags"]] = 1;
1369
			if (isset($message->subject) && strlen($message->subject) > 0) {
1370
				$props[$flagprops["todotitle"]] = $message->subject;
1371
			}
1372
			// ordinal date is utc current time
1373
			if (!isset($message->flag->ordinaldate) || empty($message->flag->ordinaldate)) {
1374
				$props[$flagprops["ordinaldate"]] = time();
1375
			}
1376
			// the default value
1377
			if (!isset($message->flag->subordinaldate) || empty($message->flag->subordinaldate)) {
1378
				$props[$flagprops["subordinaldate"]] = "5555555";
1379
			}
1380
			$props[$flagprops["flagicon"]] = 6; // red flag icon
1381
			$props[$flagprops["replyrequested"]] = true;
1382
			$props[$flagprops["responserequested"]] = true;
1383
1384
			if ($message->flag->flagstatus == SYNC_FLAGSTATUS_COMPLETE) {
1385
				$props[$flagprops["status"]] = olTaskComplete;
0 ignored issues
show
Bug introduced by
The constant olTaskComplete was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1386
				$props[$flagprops["completion"]] = 1.0;
1387
				$props[$flagprops["complete"]] = true;
1388
				$props[$flagprops["replyrequested"]] = false;
1389
				$props[$flagprops["responserequested"]] = false;
1390
				unset($props[$flagprops["flagicon"]]);
1391
				$delprops[] = $flagprops["flagicon"];
1392
			}
1393
		}
1394
1395
		if (!empty($props)) {
1396
			mapi_setprops($mapimessage, $props);
1397
		}
1398
		if (!empty($delprops)) {
1399
			mapi_deleteprops($mapimessage, $delprops);
1400
		}
1401
1402
		return $response;
1403
	}
1404
1405
	/**
1406
	 * Writes a SyncAppointment to MAPI.
1407
	 *
1408
	 * @param mixed $mapimessage
1409
	 * @param mixed $appointment
1410
	 *
1411
	 * @return SyncObject
1412
	 */
1413
	private function setAppointment($mapimessage, $appointment) {
1414
		$response = new SyncAppointmentResponse();
1415
1416
		$isAllday = isset($appointment->alldayevent) && $appointment->alldayevent;
1417
		$isMeeting = isset($appointment->meetingstatus) && $appointment->meetingstatus > 0;
1418
		$isAs16 = Request::GetProtocolVersion() >= 16.0;
1419
1420
		// Get timezone info
1421
		if (isset($appointment->timezone)) {
1422
			$tz = $this->getTZFromSyncBlob(base64_decode($appointment->timezone));
1423
		}
1424
		// AS 16: doesn't sent a timezone - use server TZ
1425
		elseif ($isAs16 && $isAllday) {
1426
			$tz = TimezoneUtil::GetFullTZ();
1427
		}
1428
		else {
1429
			$tz = false;
1430
		}
1431
1432
		$appointmentmapping = MAPIMapping::GetAppointmentMapping();
1433
		$appointmentprops = MAPIMapping::GetAppointmentProperties();
1434
		$appointmentprops = array_merge($this->getPropIdsFromStrings($appointmentmapping), $this->getPropIdsFromStrings($appointmentprops));
1435
1436
		// AS 16: incoming instanceid means we need to create/update an appointment exception
1437
		if ($isAs16 && isset($appointment->instanceid) && $appointment->instanceid) {
1438
			// this property wasn't decoded so use Utils->ParseDate to convert it into a timestamp and get basedate from it
1439
			$instanceid = Utils::ParseDate($appointment->instanceid);
1440
			$basedate = $this->getDayStartOfTimestamp($instanceid);
1441
1442
			// get compatible TZ data
1443
			$props = [$appointmentprops["timezonetag"], $appointmentprops["isrecurring"]];
1444
			$tzprop = $this->getProps($mapimessage, $props);
1445
			$tz = $this->getTZFromMAPIBlob($tzprop[$appointmentprops["timezonetag"]]);
0 ignored issues
show
Unused Code introduced by
The assignment to $tz is dead and can be removed.
Loading history...
1446
1447
			if ($appointmentprops["isrecurring"] == false) {
1448
				SLog::Write(LOGLEVEL_INFO, sprintf("MAPIProvider->setAppointment(): Cannot modify exception instanceId '%s' as target appointment is not recurring. Ignoring.", $appointment->instanceid));
1449
1450
				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 SyncObject.
Loading history...
1451
			}
1452
			// get a recurrence object
1453
			$recurrence = new Recurrence($this->store, $mapimessage);
1454
1455
			// check if the exception is to be deleted
1456
			if (isset($appointment->instanceiddelete) && $appointment->instanceiddelete === true) {
1457
				// Delete exception
1458
				$recurrence->createException([], $basedate, true);
1459
			}
1460
			// create or update the exception
1461
			else {
1462
				$exceptionprops = [];
1463
1464
				if (isset($appointment->starttime)) {
1465
					$exceptionprops[$appointmentprops["starttime"]] = $appointment->starttime;
1466
				}
1467
				if (isset($appointment->endtime)) {
1468
					$exceptionprops[$appointmentprops["endtime"]] = $appointment->endtime;
1469
				}
1470
				if (isset($appointment->subject)) {
1471
					$exceptionprops[$appointmentprops["subject"]] = $appointment->subject;
1472
				}
1473
				if (isset($appointment->location)) {
1474
					$exceptionprops[$appointmentprops["location"]] = $appointment->location;
1475
				}
1476
				if (isset($appointment->busystatus)) {
1477
					$exceptionprops[$appointmentprops["busystatus"]] = $appointment->busystatus;
1478
				}
1479
				if (isset($appointment->reminder)) {
1480
					$exceptionprops[$appointmentprops["reminderset"]] = 1;
1481
					$exceptionprops[$appointmentprops["remindertime"]] = $appointment->reminder;
1482
				}
1483
				if (isset($appointment->alldayevent)) {
1484
					$exceptionprops[$appointmentprops["alldayevent"]] = $mapiexception["alldayevent"] = $appointment->alldayevent;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$mapiexception was never initialized. Although not strictly required by PHP, it is generally a good practice to add $mapiexception = array(); before regardless.
Loading history...
1485
				}
1486
				if (isset($appointment->body)) {
1487
					$exceptionprops[$appointmentprops["body"]] = $appointment->body;
1488
				}
1489
				if (isset($appointment->asbody)) {
1490
					$this->setASbody($appointment->asbody, $exceptionprops, $appointmentprops);
1491
				}
1492
				if (isset($appointment->location2)) {
1493
					$this->setASlocation($appointment->location2, $exceptionprops, $appointmentprops);
1494
				}
1495
1496
				// modify if exists else create exception
1497
				if ($recurrence->isException($basedate)) {
1498
					$recurrence->modifyException($exceptionprops, $basedate);
1499
				}
1500
				else {
1501
					$recurrence->createException($exceptionprops, $basedate);
1502
				}
1503
			}
1504
1505
			// instantiate the MR so we can send a updates to the attendees
1506
			$mr = new Meetingrequest($this->store, $mapimessage, $this->session);
1507
			$mr->updateMeetingRequest($basedate);
1508
			$deleteException = isset($appointment->instanceiddelete) && $appointment->instanceiddelete === true;
1509
			$mr->sendMeetingRequest($deleteException, false, $basedate);
1510
1511
			return $response;
1512
		}
1513
1514
		// Save OldProps to later check which data is being changed
1515
		$oldProps = $this->getProps($mapimessage, $appointmentprops);
1516
1517
		// start and end time may not be set - try to get them from the existing appointment for further calculation.
1518
		if (!isset($appointment->starttime) || !isset($appointment->endtime)) {
1519
			$amapping = MAPIMapping::GetAppointmentMapping();
1520
			$amapping = $this->getPropIdsFromStrings($amapping);
1521
			$existingstartendpropsmap = [$amapping["starttime"], $amapping["endtime"]];
1522
			$existingstartendprops = $this->getProps($mapimessage, $existingstartendpropsmap);
1523
1524
			if (isset($existingstartendprops[$amapping["starttime"]]) && !isset($appointment->starttime)) {
1525
				$appointment->starttime = $existingstartendprops[$amapping["starttime"]];
1526
				SLog::Write(LOGLEVEL_WBXML, sprintf("MAPIProvider->setAppointment(): Parameter 'starttime' was not set, using value from MAPI %d (%s).", $appointment->starttime, Utils::FormatDate($appointment->starttime)));
1527
			}
1528
			if (isset($existingstartendprops[$amapping["endtime"]]) && !isset($appointment->endtime)) {
1529
				$appointment->endtime = $existingstartendprops[$amapping["endtime"]];
1530
				SLog::Write(LOGLEVEL_WBXML, sprintf("MAPIProvider->setAppointment(): Parameter 'endtime' was not set, using value from MAPI %d (%s).", $appointment->endtime, Utils::FormatDate($appointment->endtime)));
1531
			}
1532
		}
1533
		if (!isset($appointment->starttime) || !isset($appointment->endtime)) {
1534
			throw new StatusException("MAPIProvider->setAppointment(): Error, start and/or end time not set and can not be retrieved from MAPI.", SYNC_STATUS_SYNCCANNOTBECOMPLETED);
1535
		}
1536
1537
		// calculate duration because without it some webaccess views are broken. duration is in min
1538
		$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

1538
		$localstart = $this->getLocaltimeByTZ($appointment->starttime, /** @scrutinizer ignore-type */ $tz);
Loading history...
1539
		$localend = $this->getLocaltimeByTZ($appointment->endtime, $tz);
1540
		$duration = ($localend - $localstart) / 60;
1541
1542
		// nokia sends an yearly event with 0 mins duration but as all day event,
1543
		// so make it end next day
1544
		if ($appointment->starttime == $appointment->endtime && $isAllday) {
1545
			$duration = 1440;
1546
			$appointment->endtime = $appointment->starttime + 24 * 60 * 60;
1547
			$localend = $localstart + 24 * 60 * 60;
1548
		}
1549
1550
		// use clientUID if set
1551
		if ($appointment->clientuid && !$appointment->uid) {
1552
			$appointment->uid = $appointment->clientuid;
1553
			// Facepalm: iOS sends weird ids (without dashes and a trailing null character)
1554
			if (strlen($appointment->uid) == 33) {
1555
				$appointment->uid = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split($appointment->uid, 4));
0 ignored issues
show
Bug introduced by
It seems like str_split($appointment->uid, 4) can also be of type true; however, parameter $values of vsprintf() 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

1555
				$appointment->uid = vsprintf('%s%s-%s-%s-%s-%s%s%s', /** @scrutinizer ignore-type */ str_split($appointment->uid, 4));
Loading history...
1556
			}
1557
		}
1558
		// is the transmitted UID OL compatible?
1559
		if ($appointment->uid && substr($appointment->uid, 0, 16) != "040000008200E000") {
1560
			// if not, encapsulate the transmitted uid
1561
			$appointment->uid = getGoidFromUid($appointment->uid);
0 ignored issues
show
Bug introduced by
The function getGoidFromUid 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

1561
			$appointment->uid = /** @scrutinizer ignore-call */ getGoidFromUid($appointment->uid);
Loading history...
1562
		}
1563
		// if there was a clientuid transport the new UID to the response
1564
		if ($appointment->clientuid) {
1565
			$response->uid = bin2hex($appointment->uid);
1566
			$response->hasResponse = true;
1567
		}
1568
1569
		mapi_setprops($mapimessage, [PR_MESSAGE_CLASS => "IPM.Appointment"]);
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1570
1571
		$this->setPropsInMAPI($mapimessage, $appointment, $appointmentmapping);
1572
1573
		// appointment specific properties to be set
1574
		$props = [];
1575
1576
		// sensitivity is not enough to mark an appointment as private, so we use another mapi tag
1577
		$private = (isset($appointment->sensitivity) && $appointment->sensitivity >= SENSITIVITY_PRIVATE) ? true : false;
0 ignored issues
show
Bug introduced by
The constant SENSITIVITY_PRIVATE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1578
1579
		// Set commonstart/commonend to start/end and remindertime to start, duration, private and cleanGlobalObjectId
1580
		$props[$appointmentprops["commonstart"]] = $appointment->starttime;
1581
		$props[$appointmentprops["commonend"]] = $appointment->endtime;
1582
		$props[$appointmentprops["reminderstart"]] = $appointment->starttime;
1583
		// Set reminder boolean to 'true' if reminder is set
1584
		$props[$appointmentprops["reminderset"]] = isset($appointment->reminder) ? true : false;
1585
		$props[$appointmentprops["duration"]] = $duration;
1586
		$props[$appointmentprops["private"]] = $private;
1587
		$props[$appointmentprops["uid"]] = $appointment->uid;
1588
		// Set named prop 8510, unknown property, but enables deleting a single occurrence of a recurring
1589
		// type in OLK2003.
1590
		$props[$appointmentprops["sideeffects"]] = 369;
1591
1592
		if (isset($appointment->reminder) && $appointment->reminder >= 0) {
1593
			// Set 'flagdueby' to correct value (start - reminderminutes)
1594
			$props[$appointmentprops["flagdueby"]] = $appointment->starttime - $appointment->reminder * 60;
1595
			$props[$appointmentprops["remindertime"]] = $appointment->reminder;
1596
		}
1597
		// unset the reminder
1598
		else {
1599
			$props[$appointmentprops["reminderset"]] = false;
1600
		}
1601
1602
		if (isset($appointment->asbody)) {
1603
			$this->setASbody($appointment->asbody, $props, $appointmentprops);
1604
		}
1605
1606
		if (isset($appointment->location2)) {
1607
			$this->setASlocation($appointment->location2, $props, $appointmentprops);
1608
		}
1609
		if ($tz !== false) {
1610
			if (!($isAs16 && $isAllday)) {
1611
				$props[$appointmentprops["timezonetag"]] = $this->getMAPIBlobFromTZ($tz);
1612
			}
1613
		}
1614
1615
		if (isset($appointment->recurrence)) {
1616
			// Set PR_ICON_INDEX to 1025 to show correct icon in category view
1617
			$props[$appointmentprops["icon"]] = 1025;
1618
1619
			// if there aren't any exceptions, use the 'old style' set recurrence
1620
			$noexceptions = true;
1621
1622
			$recurrence = new Recurrence($this->store, $mapimessage);
1623
			$recur = [];
1624
			$this->setRecurrence($appointment, $recur);
1625
1626
			// set the recurrence type to that of the MAPI
1627
			$props[$appointmentprops["recurrencetype"]] = $recur["recurrencetype"];
1628
1629
			$starttime = $this->gmtime($localstart);
1630
			$endtime = $this->gmtime($localend);
0 ignored issues
show
Unused Code introduced by
The assignment to $endtime is dead and can be removed.
Loading history...
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

1630
			$endtime = $this->gmtime(/** @scrutinizer ignore-type */ $localend);
Loading history...
1631
1632
			// set recurrence start here because it's calculated differently for tasks and appointments
1633
			$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

1633
			$recur["start"] = $this->getDayStartOfTimestamp($this->getGMTTimeByTZ($localstart, /** @scrutinizer ignore-type */ $tz));
Loading history...
1634
1635
			$recur["startocc"] = $starttime["tm_hour"] * 60 + $starttime["tm_min"];
1636
			$recur["endocc"] = $recur["startocc"] + $duration; // Note that this may be > 24*60 if multi-day
1637
1638
			// only tasks can regenerate
1639
			$recur["regen"] = false;
1640
1641
			// Process exceptions. The PDA will send all exceptions for this recurring item.
1642
			if (isset($appointment->exceptions)) {
1643
				foreach ($appointment->exceptions as $exception) {
1644
					// we always need the base date
1645
					if (!isset($exception->exceptionstarttime)) {
1646
						continue;
1647
					}
1648
1649
					$basedate = $this->getDayStartOfTimestamp($exception->exceptionstarttime);
1650
					if (isset($exception->deleted) && $exception->deleted) {
1651
						$noexceptions = false;
1652
						// Delete exception
1653
						$recurrence->createException([], $basedate, true);
1654
					}
1655
					else {
1656
						// Change exception
1657
						$mapiexception = ["basedate" => $basedate];
1658
						// other exception properties which are not handled in recurrence
1659
						$exceptionprops = [];
1660
1661
						if (isset($exception->starttime)) {
1662
							$mapiexception["start"] = $this->getLocaltimeByTZ($exception->starttime, $tz);
1663
							$exceptionprops[$appointmentprops["starttime"]] = $exception->starttime;
1664
						}
1665
						if (isset($exception->endtime)) {
1666
							$mapiexception["end"] = $this->getLocaltimeByTZ($exception->endtime, $tz);
1667
							$exceptionprops[$appointmentprops["endtime"]] = $exception->endtime;
1668
						}
1669
						if (isset($exception->subject)) {
1670
							$exceptionprops[$appointmentprops["subject"]] = $mapiexception["subject"] = $exception->subject;
1671
						}
1672
						if (isset($exception->location)) {
1673
							$exceptionprops[$appointmentprops["location"]] = $mapiexception["location"] = $exception->location;
1674
						}
1675
						if (isset($exception->busystatus)) {
1676
							$exceptionprops[$appointmentprops["busystatus"]] = $mapiexception["busystatus"] = $exception->busystatus;
1677
						}
1678
						if (isset($exception->reminder)) {
1679
							$exceptionprops[$appointmentprops["reminderset"]] = $mapiexception["reminder_set"] = 1;
1680
							$exceptionprops[$appointmentprops["remindertime"]] = $mapiexception["remind_before"] = $exception->reminder;
1681
						}
1682
						if (isset($exception->alldayevent)) {
1683
							$exceptionprops[$appointmentprops["alldayevent"]] = $mapiexception["alldayevent"] = $exception->alldayevent;
1684
						}
1685
1686
						if (!isset($recur["changed_occurrences"])) {
1687
							$recur["changed_occurrences"] = [];
1688
						}
1689
1690
						if (isset($exception->body)) {
1691
							$exceptionprops[$appointmentprops["body"]] = $exception->body;
1692
						}
1693
1694
						if (isset($exception->asbody)) {
1695
							$this->setASbody($exception->asbody, $exceptionprops, $appointmentprops);
1696
							$mapiexception["body"] = $exceptionprops[$appointmentprops["body"]] =
1697
								(isset($exceptionprops[$appointmentprops["body"]])) ? $exceptionprops[$appointmentprops["body"]] :
1698
								((isset($exceptionprops[$appointmentprops["html"]])) ? $exceptionprops[$appointmentprops["html"]] : "");
1699
						}
1700
1701
						array_push($recur["changed_occurrences"], $mapiexception);
1702
1703
						if (!empty($exceptionprops)) {
1704
							$noexceptions = false;
1705
							if ($recurrence->isException($basedate)) {
1706
								$recurrence->modifyException($exceptionprops, $basedate);
1707
							}
1708
							else {
1709
								$recurrence->createException($exceptionprops, $basedate);
1710
							}
1711
						}
1712
					}
1713
				}
1714
			}
1715
1716
			// setRecurrence deletes the attachments from an appointment
1717
			if ($noexceptions) {
1718
				$recurrence->setRecurrence($tz, $recur);
1719
			}
1720
		}
1721
		else {
1722
			$props[$appointmentprops["isrecurring"]] = false;
1723
			// remove recurringstate
1724
			mapi_deleteprops($mapimessage, [$appointmentprops["recurringstate"]]);
1725
		}
1726
1727
		// always set the PR_SENT_REPRESENTING_* props so that the attendee status update also works with the webaccess
1728
		$p = [$appointmentprops["representingentryid"], $appointmentprops["representingname"], $appointmentprops["sentrepresentingaddt"],
1729
			$appointmentprops["sentrepresentingemail"], $appointmentprops["sentrepresentinsrchk"], $appointmentprops["responsestatus"], ];
1730
		$representingprops = $this->getProps($mapimessage, $p);
1731
1732
		$storeProps = $this->GetStoreProps();
1733
		$abEntryProps = $this->getAbPropsFromEntryID($storeProps[PR_MAILBOX_OWNER_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_MAILBOX_OWNER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1734
		if (!isset($representingprops[$appointmentprops["representingentryid"]])) {
1735
			$displayname = $sentrepresentingemail = Request::GetUser();
1736
			$sentrepresentingaddt = 'SMPT';
1737
			if ($abEntryProps !== false) {
1738
				$displayname = $abEntryProps[PR_DISPLAY_NAME] ?? $displayname;
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1739
				$sentrepresentingemail = $abEntryProps[PR_EMAIL_ADDRESS] ?? $abEntryProps[PR_SMTP_ADDRESS] ?? $sentrepresentingemail;
0 ignored issues
show
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SMTP_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1740
				$sentrepresentingaddt = $abEntryProps[PR_ADDRTYPE] ?? $sentrepresentingaddt;
0 ignored issues
show
Bug introduced by
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1741
			}
1742
			$props[$appointmentprops["representingentryid"]] = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
1743
			$props[$appointmentprops["representingname"]] = $displayname;
1744
			$props[$appointmentprops["sentrepresentingemail"]] = $sentrepresentingemail;
1745
			$props[$appointmentprops["sentrepresentingaddt"]] = $sentrepresentingaddt;
1746
			$props[$appointmentprops["sentrepresentinsrchk"]] = $props[$appointmentprops["sentrepresentingaddt"]] . ":" . $props[$appointmentprops["sentrepresentingemail"]];
1747
1748
			if (isset($appointment->attendees) && is_array($appointment->attendees) && !empty($appointment->attendees)) {
1749
				$props[$appointmentprops["icon"]] = 1026;
1750
				// the user is the organizer
1751
				// set these properties to show tracking tab in webapp
1752
				$props[$appointmentprops["responsestatus"]] = olResponseOrganized;
0 ignored issues
show
Bug introduced by
The constant olResponseOrganized was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1753
				$props[$appointmentprops["meetingstatus"]] = olMeeting;
0 ignored issues
show
Bug introduced by
The constant olMeeting was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1754
			}
1755
		}
1756
		// we also have to set the responsestatus and not only meetingstatus, so we use another mapi tag
1757
		if (!isset($props[$appointmentprops["responsestatus"]])) {
1758
			if (isset($appointment->responsetype)) {
1759
				$props[$appointmentprops["responsestatus"]] = $appointment->responsetype;
1760
			}
1761
			// only set responsestatus to none if it is not set on the server
1762
			elseif (!isset($representingprops[$appointmentprops["responsestatus"]])) {
1763
				$props[$appointmentprops["responsestatus"]] = olResponseNone;
0 ignored issues
show
Bug introduced by
The constant olResponseNone was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1764
			}
1765
		}
1766
1767
		// when updating a normal appointment to a MR we need to send MR emails
1768
		$forceMRUpdateSend = false;
1769
1770
		// Do attendees
1771
		// For AS-16 get a list of the current attendees (pre update)
1772
		if ($isAs16 && $isMeeting) {
1773
			$old_recipienttable = mapi_message_getrecipienttable($mapimessage);
1774
			$old_receipstable = mapi_table_queryallrows(
1775
				$old_recipienttable,
1776
				[
1777
					PR_ENTRYID,
1778
					PR_DISPLAY_NAME,
1779
					PR_EMAIL_ADDRESS,
1780
					PR_RECIPIENT_ENTRYID,
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1781
					PR_RECIPIENT_TYPE,
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1782
					PR_SEND_INTERNET_ENCODING,
0 ignored issues
show
Bug introduced by
The constant PR_SEND_INTERNET_ENCODING was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1783
					PR_SEND_RICH_INFO,
0 ignored issues
show
Bug introduced by
The constant PR_SEND_RICH_INFO was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1784
					PR_RECIPIENT_DISPLAY_NAME,
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1785
					PR_ADDRTYPE,
1786
					PR_DISPLAY_TYPE,
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1787
					PR_DISPLAY_TYPE_EX,
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_TYPE_EX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1788
					PR_RECIPIENT_TRACKSTATUS,
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TRACKSTATUS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1789
					PR_RECIPIENT_TRACKSTATUS_TIME,
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TRACKSTATUS_TIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1790
					PR_RECIPIENT_FLAGS,
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1791
					PR_ROWID,
0 ignored issues
show
Bug introduced by
The constant PR_ROWID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1792
					PR_OBJECT_TYPE,
0 ignored issues
show
Bug introduced by
The constant PR_OBJECT_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1793
					PR_SEARCH_KEY,
0 ignored issues
show
Bug introduced by
The constant PR_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1794
				]
1795
			);
1796
			$old_receips = [];
1797
			foreach ($old_receipstable as $oldrec) {
1798
				if (isset($oldrec[PR_EMAIL_ADDRESS])) {
1799
					$old_receips[$oldrec[PR_EMAIL_ADDRESS]] = $oldrec;
1800
				}
1801
			}
1802
		}
1803
1804
		if (isset($appointment->attendees) && is_array($appointment->attendees)) {
1805
			$recips = [];
1806
1807
			// Outlook XP requires organizer in the attendee list as well
1808
			// Only add organizer if it's a meeting
1809
			if ($isMeeting) {
1810
				$org = [];
1811
				$org[PR_ENTRYID] = isset($representingprops[$appointmentprops["representingentryid"]]) ? $representingprops[$appointmentprops["representingentryid"]] : $props[$appointmentprops["representingentryid"]];
1812
				$org[PR_DISPLAY_NAME] = isset($representingprops[$appointmentprops["representingname"]]) ? $representingprops[$appointmentprops["representingname"]] : $props[$appointmentprops["representingname"]];
1813
				$org[PR_ADDRTYPE] = isset($representingprops[$appointmentprops["sentrepresentingaddt"]]) ? $representingprops[$appointmentprops["sentrepresentingaddt"]] : $props[$appointmentprops["sentrepresentingaddt"]];
1814
				$org[PR_SMTP_ADDRESS] = $org[PR_EMAIL_ADDRESS] = isset($representingprops[$appointmentprops["sentrepresentingemail"]]) ? $representingprops[$appointmentprops["sentrepresentingemail"]] : $props[$appointmentprops["sentrepresentingemail"]];
1815
				$org[PR_SEARCH_KEY] = isset($representingprops[$appointmentprops["sentrepresentinsrchk"]]) ? $representingprops[$appointmentprops["sentrepresentinsrchk"]] : $props[$appointmentprops["sentrepresentinsrchk"]];
1816
				$org[PR_RECIPIENT_FLAGS] = recipOrganizer | recipSendable;
0 ignored issues
show
Bug introduced by
The constant recipOrganizer was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant recipSendable was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1817
				$org[PR_RECIPIENT_TYPE] = MAPI_ORIG;
0 ignored issues
show
Bug introduced by
The constant MAPI_ORIG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1818
				$org[PR_RECIPIENT_TRACKSTATUS] = olResponseOrganized;
1819
				if ($abEntryProps !== false && isset($abEntryProps[PR_SMTP_ADDRESS])) {
1820
					$org[PR_SMTP_ADDRESS] = $abEntryProps[PR_SMTP_ADDRESS];
1821
				}
1822
1823
				array_push($recips, $org);
1824
				// remove organizer from old_receips
1825
				if (isset($old_receips[$org[PR_EMAIL_ADDRESS]])) {
1826
					unset($old_receips[$org[PR_EMAIL_ADDRESS]]);
1827
				}
1828
			}
1829
1830
			// Open address book for user resolve
1831
			$addrbook = $this->getAddressbook();
1832
			foreach ($appointment->attendees as $attendee) {
1833
				$recip = [];
1834
				$recip[PR_EMAIL_ADDRESS] = $recip[PR_SMTP_ADDRESS] = $attendee->email;
1835
1836
				// lookup information in GAB if possible so we have up-to-date name for given address
1837
				$userinfo = [[PR_DISPLAY_NAME => $recip[PR_EMAIL_ADDRESS]]];
1838
				$userinfo = mapi_ab_resolvename($addrbook, $userinfo, EMS_AB_ADDRESS_LOOKUP);
0 ignored issues
show
Bug introduced by
The constant EMS_AB_ADDRESS_LOOKUP was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1839
				if (mapi_last_hresult() == NOERROR) {
0 ignored issues
show
Bug introduced by
The constant NOERROR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1840
					$recip[PR_DISPLAY_NAME] = $userinfo[0][PR_DISPLAY_NAME];
1841
					$recip[PR_EMAIL_ADDRESS] = $userinfo[0][PR_EMAIL_ADDRESS];
1842
					$recip[PR_SEARCH_KEY] = $userinfo[0][PR_SEARCH_KEY];
1843
					$recip[PR_ADDRTYPE] = $userinfo[0][PR_ADDRTYPE];
1844
					$recip[PR_ENTRYID] = $userinfo[0][PR_ENTRYID];
1845
					$recip[PR_RECIPIENT_TYPE] = isset($attendee->attendeetype) ? $attendee->attendeetype : MAPI_TO;
0 ignored issues
show
Bug introduced by
The constant MAPI_TO was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1846
					$recip[PR_RECIPIENT_FLAGS] = recipSendable;
1847
					$recip[PR_RECIPIENT_TRACKSTATUS] = isset($attendee->attendeestatus) ? $attendee->attendeestatus : olResponseNone;
1848
				}
1849
				else {
1850
					$recip[PR_DISPLAY_NAME] = $attendee->name;
1851
					$recip[PR_SEARCH_KEY] = "SMTP:" . $recip[PR_EMAIL_ADDRESS] . "\0";
1852
					$recip[PR_ADDRTYPE] = "SMTP";
1853
					$recip[PR_RECIPIENT_TYPE] = isset($attendee->attendeetype) ? $attendee->attendeetype : MAPI_TO;
1854
					$recip[PR_ENTRYID] = mapi_createoneoff($recip[PR_DISPLAY_NAME], $recip[PR_ADDRTYPE], $recip[PR_EMAIL_ADDRESS]);
1855
				}
1856
1857
				// remove still existing attendees from the list of pre-update attendees - remaining pre-update are considered deleted attendees
1858
				if (isset($old_receips[$recip[PR_EMAIL_ADDRESS]])) {
1859
					unset($old_receips[$recip[PR_EMAIL_ADDRESS]]);
1860
				}
1861
				// if there is a new attendee a MR update must be send -> Appointment to MR update
1862
				else {
1863
					$forceMRUpdateSend = true;
1864
				}
1865
				// the organizer is already in the recipient list, no need to add him again
1866
				if (isset($org[PR_EMAIL_ADDRESS]) && strcasecmp($org[PR_EMAIL_ADDRESS], $recip[PR_EMAIL_ADDRESS]) == 0) {
1867
					continue;
1868
				}
1869
				array_push($recips, $recip);
1870
			}
1871
1872
			mapi_message_modifyrecipients($mapimessage, ($appointment->clientuid) ? MODRECIP_ADD : MODRECIP_MODIFY, $recips);
0 ignored issues
show
Bug introduced by
The constant MODRECIP_ADD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant MODRECIP_MODIFY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1873
		}
1874
		mapi_setprops($mapimessage, $props);
1875
1876
		// Since AS 16 we have to take care of MeetingRequest updates
1877
		if ($isAs16 && $isMeeting) {
1878
			$mr = new Meetingrequest($this->store, $mapimessage, $this->session);
1879
			// Only send updates if this is a new MR or we are the organizer
1880
			if ($appointment->clientuid || $mr->isLocalOrganiser() || $forceMRUpdateSend) {
1881
				// initialize MR and/or update internal counters
1882
				$mr->updateMeetingRequest();
1883
				// when updating, check for significant changes and if needed will clear the existing recipient responses
1884
				if (!isset($appointment->clientuid) && !$forceMRUpdateSend) {
1885
					$mr->checkSignificantChanges($oldProps, false, false);
1886
				}
1887
				$mr->sendMeetingRequest(false, false, false, false, array_values($old_receips));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $old_receips does not seem to be defined for all execution paths leading up to this point.
Loading history...
1888
			}
1889
		}
1890
1891
		// update attachments send by the mobile
1892
		if (!empty($appointment->asattachments)) {
1893
			$this->editAttachments($mapimessage, $appointment->asattachments, $response);
1894
		}
1895
1896
		// Existing allday events may have tzdef* properties set,
1897
		// so it's necessary to set them to UTC in order for other clients
1898
		// to display such events properly.
1899
		if ($isAllday && (
1900
			isset($oldProps[$appointmentprops['tzdefstart']]) ||
1901
			isset($oldProps[$appointmentprops['tzdefend']])
1902
		)) {
1903
			$utc = TimezoneUtil::GetBinaryTZ('Etc/Utc');
1904
			if ($utc !== false) {
1905
				mapi_setprops($mapimessage, [
1906
					$appointmentprops['tzdefstart'] => $utc,
1907
					$appointmentprops['tzdefend'] => $utc,
1908
				]);
1909
			}
1910
		}
1911
1912
		return $response;
1913
	}
1914
1915
	/**
1916
	 * Writes a SyncContact to MAPI.
1917
	 *
1918
	 * @param mixed       $mapimessage
1919
	 * @param SyncContact $contact
1920
	 *
1921
	 * @return bool
1922
	 */
1923
	private function setContact($mapimessage, $contact) {
1924
		$response = new SyncContactResponse();
1925
		mapi_setprops($mapimessage, [PR_MESSAGE_CLASS => "IPM.Contact"]);
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1926
1927
		// normalize email addresses
1928
		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...
1929
			unset($contact->email1address);
1930
		}
1931
1932
		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...
1933
			unset($contact->email2address);
1934
		}
1935
1936
		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...
1937
			unset($contact->email3address);
1938
		}
1939
1940
		$contactmapping = MAPIMapping::GetContactMapping();
1941
		$contactprops = MAPIMapping::GetContactProperties();
1942
		$this->setPropsInMAPI($mapimessage, $contact, $contactmapping);
1943
1944
		// /set display name from contact's properties
1945
		$cname = $this->composeDisplayName($contact);
1946
1947
		// get contact specific mapi properties and merge them with the AS properties
1948
		$contactprops = array_merge($this->getPropIdsFromStrings($contactmapping), $this->getPropIdsFromStrings($contactprops));
1949
1950
		// contact specific properties to be set
1951
		$props = [];
1952
1953
		// need to be set in order to show contacts properly in outlook and wa
1954
		$nremails = [];
1955
		$abprovidertype = 0;
1956
1957
		if (isset($contact->email1address)) {
1958
			$this->setEmailAddress($contact->email1address, $cname, 1, $props, $contactprops, $nremails, $abprovidertype);
1959
		}
1960
		if (isset($contact->email2address)) {
1961
			$this->setEmailAddress($contact->email2address, $cname, 2, $props, $contactprops, $nremails, $abprovidertype);
1962
		}
1963
		if (isset($contact->email3address)) {
1964
			$this->setEmailAddress($contact->email3address, $cname, 3, $props, $contactprops, $nremails, $abprovidertype);
1965
		}
1966
1967
		$props[$contactprops["addressbooklong"]] = $abprovidertype;
1968
		$props[$contactprops["displayname"]] = $props[$contactprops["subject"]] = $cname;
1969
1970
		// pda multiple e-mail addresses bug fix for the contact
1971
		if (!empty($nremails)) {
1972
			$props[$contactprops["addressbookmv"]] = $nremails;
1973
		}
1974
1975
		// set addresses
1976
		$this->setAddress("home", $contact->homecity, $contact->homecountry, $contact->homepostalcode, $contact->homestate, $contact->homestreet, $props, $contactprops);
1977
		$this->setAddress("business", $contact->businesscity, $contact->businesscountry, $contact->businesspostalcode, $contact->businessstate, $contact->businessstreet, $props, $contactprops);
1978
		$this->setAddress("other", $contact->othercity, $contact->othercountry, $contact->otherpostalcode, $contact->otherstate, $contact->otherstreet, $props, $contactprops);
1979
1980
		// set the mailing address and its type
1981
		if (isset($props[$contactprops["businessaddress"]])) {
1982
			$props[$contactprops["mailingaddress"]] = 2;
1983
			$this->setMailingAddress($contact->businesscity, $contact->businesscountry, $contact->businesspostalcode, $contact->businessstate, $contact->businessstreet, $props[$contactprops["businessaddress"]], $props, $contactprops);
1984
		}
1985
		elseif (isset($props[$contactprops["homeaddress"]])) {
1986
			$props[$contactprops["mailingaddress"]] = 1;
1987
			$this->setMailingAddress($contact->homecity, $contact->homecountry, $contact->homepostalcode, $contact->homestate, $contact->homestreet, $props[$contactprops["homeaddress"]], $props, $contactprops);
1988
		}
1989
		elseif (isset($props[$contactprops["otheraddress"]])) {
1990
			$props[$contactprops["mailingaddress"]] = 3;
1991
			$this->setMailingAddress($contact->othercity, $contact->othercountry, $contact->otherpostalcode, $contact->otherstate, $contact->otherstreet, $props[$contactprops["otheraddress"]], $props, $contactprops);
1992
		}
1993
1994
		if (isset($contact->picture)) {
1995
			$picbinary = base64_decode($contact->picture);
1996
			$picsize = strlen($picbinary);
1997
			$props[$contactprops["haspic"]] = false;
1998
1999
			// TODO contact picture handling
2000
			// check if contact has already got a picture. delete it first in that case
2001
			// delete it also if it was removed on a mobile
2002
			$picprops = mapi_getprops($mapimessage, [$contactprops["haspic"]]);
2003
			if (isset($picprops[$contactprops["haspic"]]) && $picprops[$contactprops["haspic"]]) {
2004
				SLog::Write(LOGLEVEL_DEBUG, "Contact already has a picture. Delete it");
2005
2006
				$attachtable = mapi_message_getattachmenttable($mapimessage);
2007
				mapi_table_restrict($attachtable, MAPIUtils::GetContactPicRestriction());
2008
				$rows = mapi_table_queryallrows($attachtable, [PR_ATTACH_NUM]);
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_NUM was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2009
				if (isset($rows) && is_array($rows)) {
2010
					foreach ($rows as $row) {
2011
						mapi_message_deleteattach($mapimessage, $row[PR_ATTACH_NUM]);
2012
					}
2013
				}
2014
			}
2015
2016
			// only set picture if there's data in the request
2017
			if ($picbinary !== false && $picsize > 0) {
2018
				$props[$contactprops["haspic"]] = true;
2019
				$pic = mapi_message_createattach($mapimessage);
2020
				// Set properties of the attachment
2021
				$picprops = [
2022
					PR_ATTACH_LONG_FILENAME => "ContactPicture.jpg",
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_LONG_FILENAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2023
					PR_DISPLAY_NAME => "ContactPicture.jpg",
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2024
					0x7FFF000B => true,
2025
					PR_ATTACHMENT_HIDDEN => false,
0 ignored issues
show
Bug introduced by
The constant PR_ATTACHMENT_HIDDEN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2026
					PR_ATTACHMENT_FLAGS => 1,
0 ignored issues
show
Bug introduced by
The constant PR_ATTACHMENT_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2027
					PR_ATTACH_METHOD => ATTACH_BY_VALUE,
0 ignored issues
show
Bug introduced by
The constant ATTACH_BY_VALUE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_METHOD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2028
					PR_ATTACH_EXTENSION => ".jpg",
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_EXTENSION was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2029
					PR_ATTACH_NUM => 1,
2030
					PR_ATTACH_SIZE => $picsize,
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_SIZE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2031
					PR_ATTACH_DATA_BIN => $picbinary,
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_DATA_BIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2032
				];
2033
2034
				mapi_setprops($pic, $picprops);
2035
				mapi_savechanges($pic);
2036
			}
2037
		}
2038
2039
		if (isset($contact->asbody)) {
2040
			$this->setASbody($contact->asbody, $props, $contactprops);
2041
		}
2042
2043
		// set fileas
2044
		if (defined('FILEAS_ORDER')) {
2045
			$lastname = (isset($contact->lastname)) ? $contact->lastname : "";
2046
			$firstname = (isset($contact->firstname)) ? $contact->firstname : "";
2047
			$middlename = (isset($contact->middlename)) ? $contact->middlename : "";
2048
			$company = (isset($contact->companyname)) ? $contact->companyname : "";
2049
			$props[$contactprops["fileas"]] = Utils::BuildFileAs($lastname, $firstname, $middlename, $company);
2050
		}
2051
		else {
2052
			SLog::Write(LOGLEVEL_DEBUG, "FILEAS_ORDER not defined");
2053
		}
2054
2055
		mapi_setprops($mapimessage, $props);
2056
2057
		return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response returns the type SyncContactResponse which is incompatible with the documented return type boolean.
Loading history...
2058
	}
2059
2060
	/**
2061
	 * Writes a SyncTask to MAPI.
2062
	 *
2063
	 * @param mixed    $mapimessage
2064
	 * @param SyncTask $task
2065
	 *
2066
	 * @return bool
2067
	 */
2068
	private function setTask($mapimessage, $task) {
2069
		$response = new SyncTaskResponse();
2070
		mapi_setprops($mapimessage, [PR_MESSAGE_CLASS => "IPM.Task"]);
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2071
2072
		$taskmapping = MAPIMapping::GetTaskMapping();
2073
		$taskprops = MAPIMapping::GetTaskProperties();
2074
		$this->setPropsInMAPI($mapimessage, $task, $taskmapping);
2075
		$taskprops = array_merge($this->getPropIdsFromStrings($taskmapping), $this->getPropIdsFromStrings($taskprops));
2076
2077
		// task specific properties to be set
2078
		$props = [];
2079
2080
		if (isset($task->asbody)) {
2081
			$this->setASbody($task->asbody, $props, $taskprops);
2082
		}
2083
2084
		if (isset($task->complete)) {
2085
			if ($task->complete) {
2086
				// Set completion to 100%
2087
				// Set status to 'complete'
2088
				$props[$taskprops["completion"]] = 1.0;
2089
				$props[$taskprops["status"]] = 2;
2090
				$props[$taskprops["reminderset"]] = false;
2091
			}
2092
			else {
2093
				// Set completion to 0%
2094
				// Set status to 'not started'
2095
				$props[$taskprops["completion"]] = 0.0;
2096
				$props[$taskprops["status"]] = 0;
2097
			}
2098
		}
2099
		if (isset($task->recurrence) && class_exists('TaskRecurrence')) {
2100
			$deadoccur = false;
2101
			if ((isset($task->recurrence->occurrences) && $task->recurrence->occurrences == 1) ||
2102
				(isset($task->recurrence->deadoccur) && $task->recurrence->deadoccur == 1)) { // ios5 sends deadoccur inside the recurrence
2103
				$deadoccur = true;
2104
			}
2105
2106
			// Set PR_ICON_INDEX to 1281 to show correct icon in category view
2107
			$props[$taskprops["icon"]] = 1281;
2108
			// dead occur - false if new occurrences should be generated from the task
2109
			// true - if it is the last occurrence of the task
2110
			$props[$taskprops["deadoccur"]] = $deadoccur;
2111
			$props[$taskprops["isrecurringtag"]] = true;
2112
2113
			$recurrence = new TaskRecurrence($this->store, $mapimessage);
2114
			$recur = [];
2115
			$this->setRecurrence($task, $recur);
2116
2117
			// task specific recurrence properties which we need to set here
2118
			// "start" and "end" are in GMT when passing to class.recurrence
2119
			// set recurrence start here because it's calculated differently for tasks and appointments
2120
			$recur["start"] = $task->recurrence->start;
2121
			$recur["regen"] = (isset($task->recurrence->regenerate) && $task->recurrence->regenerate) ? 1 : 0;
2122
			// OL regenerates recurring task itself, but setting deleteOccurrence is required so that PHP-MAPI doesn't regenerate
2123
			// completed occurrence of a task.
2124
			if ($recur["regen"] == 0) {
2125
				$recur["deleteOccurrence"] = 0;
2126
			}
2127
			// Also add dates to $recur
2128
			$recur["duedate"] = $task->duedate;
2129
			$recur["complete"] = (isset($task->complete) && $task->complete) ? 1 : 0;
2130
			if (isset($task->datecompleted)) {
2131
				$recur["datecompleted"] = $task->datecompleted;
2132
			}
2133
			$recurrence->setRecurrence($recur);
2134
		}
2135
2136
		$props[$taskprops["private"]] = (isset($task->sensitivity) && $task->sensitivity >= SENSITIVITY_PRIVATE) ? true : false;
0 ignored issues
show
Bug introduced by
The constant SENSITIVITY_PRIVATE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2137
2138
		// Open address book for user resolve to set the owner
2139
		$addrbook = $this->getAddressbook();
0 ignored issues
show
Unused Code introduced by
The assignment to $addrbook is dead and can be removed.
Loading history...
2140
2141
		// check if there is already an owner for the task, set current user if not
2142
		$p = [$taskprops["owner"]];
2143
		$owner = $this->getProps($mapimessage, $p);
2144
		if (!isset($owner[$taskprops["owner"]])) {
2145
			$userinfo = nsp_getuserinfo(Request::GetUser());
2146
			if (mapi_last_hresult() == NOERROR && isset($userinfo["fullname"])) {
0 ignored issues
show
Bug introduced by
The constant NOERROR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2147
				$props[$taskprops["owner"]] = $userinfo["fullname"];
2148
			}
2149
		}
2150
		mapi_setprops($mapimessage, $props);
2151
2152
		return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response returns the type SyncTaskResponse which is incompatible with the documented return type boolean.
Loading history...
2153
	}
2154
2155
	/**
2156
	 * Writes a SyncNote to MAPI.
2157
	 *
2158
	 * @param mixed    $mapimessage
2159
	 * @param SyncNote $note
2160
	 *
2161
	 * @return bool
2162
	 */
2163
	private function setNote($mapimessage, $note) {
2164
		$response = new SyncNoteResponse();
2165
		// Touchdown does not send categories if all are unset or there is none.
2166
		// Setting it to an empty array will unset the property in gromox as well
2167
		if (!isset($note->categories)) {
2168
			$note->categories = [];
2169
		}
2170
2171
		// update icon index to correspond to the color
2172
		if (isset($note->Color) && $note->Color > -1 && $note->Color < 5) {
2173
			$note->Iconindex = 768 + $note->Color;
0 ignored issues
show
Bug introduced by
The property Iconindex does not seem to exist on SyncNote.
Loading history...
2174
		}
2175
2176
		$this->setPropsInMAPI($mapimessage, $note, MAPIMapping::GetNoteMapping());
2177
2178
		$noteprops = MAPIMapping::GetNoteProperties();
2179
		$noteprops = $this->getPropIdsFromStrings($noteprops);
2180
2181
		// note specific properties to be set
2182
		$props = [];
2183
		$props[$noteprops["messageclass"]] = "IPM.StickyNote";
2184
		// set body otherwise the note will be "broken" when editing it in outlook
2185
		if (isset($note->asbody)) {
2186
			$this->setASbody($note->asbody, $props, $noteprops);
2187
		}
2188
2189
		$props[$noteprops["internetcpid"]] = INTERNET_CPID_UTF8;
2190
		mapi_setprops($mapimessage, $props);
2191
2192
		return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response returns the type SyncNoteResponse which is incompatible with the documented return type boolean.
Loading history...
2193
	}
2194
2195
	/*----------------------------------------------------------------------------------------------------------
2196
	 * HELPER
2197
	 */
2198
2199
	/**
2200
	 * Returns the timestamp offset.
2201
	 *
2202
	 * @param string $ts
2203
	 *
2204
	 * @return long
2205
	 */
2206
	private function GetTZOffset($ts) {
2207
		$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

2207
		$Offset = date("O", /** @scrutinizer ignore-type */ $ts);
Loading history...
2208
2209
		$Parity = $Offset < 0 ? -1 : 1;
2210
		$Offset *= $Parity;
2211
		$Offset = ($Offset - ($Offset % 100)) / 100 * 60 + $Offset % 100;
2212
2213
		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...
2214
	}
2215
2216
	/**
2217
	 * UTC time of the timestamp.
2218
	 *
2219
	 * @param long $time
2220
	 *
2221
	 * @return array
2222
	 */
2223
	private function gmtime($time) {
2224
		$TZOffset = $this->GetTZOffset($time);
2225
2226
		$t_time = $time - $TZOffset * 60; # Counter adjust for localtime()
2227
2228
		return localtime($t_time, 1);
2229
	}
2230
2231
	/**
2232
	 * Sets the properties in a MAPI object according to an Sync object and a property mapping.
2233
	 *
2234
	 * @param mixed      $mapimessage
2235
	 * @param SyncObject $message
2236
	 * @param array      $mapping
2237
	 */
2238
	private function setPropsInMAPI($mapimessage, $message, $mapping) {
2239
		$mapiprops = $this->getPropIdsFromStrings($mapping);
2240
		$unsetVars = $message->getUnsetVars();
2241
		$propsToDelete = [];
2242
		$propsToSet = [];
2243
2244
		foreach ($mapiprops as $asprop => $mapiprop) {
2245
			if (isset($message->{$asprop})) {
2246
				$value = $message->{$asprop};
2247
2248
				// Make sure the php values are the correct type
2249
				switch (mapi_prop_type($mapiprop)) {
2250
					case PT_BINARY:
0 ignored issues
show
Bug introduced by
The constant PT_BINARY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2251
					case PT_STRING8:
0 ignored issues
show
Bug introduced by
The constant PT_STRING8 was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2252
						settype($value, "string");
2253
						break;
2254
2255
					case PT_BOOLEAN:
0 ignored issues
show
Bug introduced by
The constant PT_BOOLEAN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2256
						settype($value, "boolean");
2257
						break;
2258
2259
					case PT_SYSTIME:
0 ignored issues
show
Bug introduced by
The constant PT_SYSTIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2260
					case PT_LONG:
0 ignored issues
show
Bug introduced by
The constant PT_LONG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2261
						settype($value, "integer");
2262
						break;
2263
				}
2264
2265
				// if an "empty array" is to be saved, it the mvprop should be deleted - fixes Mantis #468
2266
				if (is_array($value) && empty($value)) {
2267
					$propsToDelete[] = $mapiprop;
2268
					SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->setPropsInMAPI(): Property '%s' to be deleted as it is an empty array", $asprop));
2269
				}
2270
				else {
2271
					// all properties will be set at once
2272
					$propsToSet[$mapiprop] = $value;
2273
				}
2274
			}
2275
			elseif (in_array($asprop, $unsetVars)) {
2276
				$propsToDelete[] = $mapiprop;
2277
			}
2278
		}
2279
2280
		mapi_setprops($mapimessage, $propsToSet);
2281
		if (mapi_last_hresult()) {
2282
			SLog::Write(LOGLEVEL_WARN, sprintf("Failed to set properties, trying to set them separately. Error code was:%x", mapi_last_hresult()));
2283
			$this->setPropsIndividually($mapimessage, $propsToSet, $mapiprops);
2284
		}
2285
2286
		mapi_deleteprops($mapimessage, $propsToDelete);
2287
2288
		// clean up
2289
		unset($unsetVars, $propsToDelete);
2290
	}
2291
2292
	/**
2293
	 * Sets the properties one by one in a MAPI object.
2294
	 *
2295
	 * @param mixed &$mapimessage
2296
	 * @param array &$propsToSet
2297
	 * @param array &$mapiprops
2298
	 */
2299
	private function setPropsIndividually(&$mapimessage, &$propsToSet, &$mapiprops) {
2300
		foreach ($propsToSet as $prop => $value) {
2301
			mapi_setprops($mapimessage, [$prop => $value]);
2302
			if (mapi_last_hresult()) {
2303
				SLog::Write(LOGLEVEL_ERROR, sprintf("Failed setting property [%s] with value [%s], error code was:%x", array_search($prop, $mapiprops), $value, mapi_last_hresult()));
2304
			}
2305
		}
2306
	}
2307
2308
	/**
2309
	 * Gets the properties from a MAPI object and sets them in the Sync object according to mapping.
2310
	 *
2311
	 * @param SyncObject &$message
2312
	 * @param mixed      $mapimessage
2313
	 * @param array      $mapping
2314
	 */
2315
	private function getPropsFromMAPI(&$message, $mapimessage, $mapping) {
2316
		$messageprops = $this->getProps($mapimessage, $mapping);
2317
		foreach ($mapping as $asprop => $mapiprop) {
2318
			// Get long strings via openproperty
2319
			if (isset($messageprops[mapi_prop_tag(PT_ERROR, mapi_prop_id($mapiprop))])) {
0 ignored issues
show
Bug introduced by
The constant PT_ERROR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2320
				if ($messageprops[mapi_prop_tag(PT_ERROR, mapi_prop_id($mapiprop))] == MAPI_E_NOT_ENOUGH_MEMORY_32BIT ||
2321
					$messageprops[mapi_prop_tag(PT_ERROR, mapi_prop_id($mapiprop))] == MAPI_E_NOT_ENOUGH_MEMORY_64BIT) {
2322
					$messageprops[$mapiprop] = MAPIUtils::readPropStream($mapimessage, $mapiprop);
2323
				}
2324
			}
2325
2326
			if (isset($messageprops[$mapiprop])) {
2327
				if (mapi_prop_type($mapiprop) == PT_BOOLEAN) {
0 ignored issues
show
Bug introduced by
The constant PT_BOOLEAN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2328
					// Force to actual '0' or '1'
2329
					if ($messageprops[$mapiprop]) {
2330
						$message->{$asprop} = 1;
2331
					}
2332
					else {
2333
						$message->{$asprop} = 0;
2334
					}
2335
				}
2336
				else {
2337
					// Special handling for PR_MESSAGE_FLAGS
2338
					if ($mapiprop == PR_MESSAGE_FLAGS) {
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2339
						$message->{$asprop} = $messageprops[$mapiprop] & 1;
2340
					} // only look at 'read' flag
2341
					else {
2342
						$message->{$asprop} = $messageprops[$mapiprop];
2343
					}
2344
				}
2345
			}
2346
		}
2347
	}
2348
2349
	/**
2350
	 * Wraps getPropIdsFromStrings() calls.
2351
	 *
2352
	 * @param mixed &$mapiprops
2353
	 */
2354
	private function getPropIdsFromStrings(&$mapiprops) {
2355
		return getPropIdsFromStrings($this->store, $mapiprops);
0 ignored issues
show
Bug introduced by
The function getPropIdsFromStrings 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

2355
		return /** @scrutinizer ignore-call */ getPropIdsFromStrings($this->store, $mapiprops);
Loading history...
2356
	}
2357
2358
	/**
2359
	 * Wraps mapi_getprops() calls.
2360
	 *
2361
	 * @param mixed $mapimessage
2362
	 * @param mixed $mapiproperties
2363
	 */
2364
	protected function getProps($mapimessage, &$mapiproperties) {
2365
		$mapiproperties = $this->getPropIdsFromStrings($mapiproperties);
2366
2367
		return mapi_getprops($mapimessage, $mapiproperties);
2368
	}
2369
2370
	/**
2371
	 * Unpack timezone info from MAPI.
2372
	 *
2373
	 * @param string $data
2374
	 *
2375
	 * @return array
2376
	 */
2377
	private function getTZFromMAPIBlob($data) {
2378
		return unpack("lbias/lstdbias/ldstbias/" .
2379
						   "vconst1/vdstendyear/vdstendmonth/vdstendday/vdstendweek/vdstendhour/vdstendminute/vdstendsecond/vdstendmillis/" .
2380
						   "vconst2/vdststartyear/vdststartmonth/vdststartday/vdststartweek/vdststarthour/vdststartminute/vdststartsecond/vdststartmillis", $data);
2381
	}
2382
2383
	/**
2384
	 * Unpack timezone info from Sync.
2385
	 *
2386
	 * @param string $data
2387
	 *
2388
	 * @return array
2389
	 */
2390
	private function getTZFromSyncBlob($data) {
2391
		$tz = unpack("lbias/a64tzname/vdstendyear/vdstendmonth/vdstendday/vdstendweek/vdstendhour/vdstendminute/vdstendsecond/vdstendmillis/" .
2392
						"lstdbias/a64tznamedst/vdststartyear/vdststartmonth/vdststartday/vdststartweek/vdststarthour/vdststartminute/vdststartsecond/vdststartmillis/" .
2393
						"ldstbias", $data);
2394
2395
		// Make the structure compatible with class.recurrence.php
2396
		$tz["timezone"] = $tz["bias"];
2397
		$tz["timezonedst"] = $tz["dstbias"];
2398
2399
		return $tz;
2400
	}
2401
2402
	/**
2403
	 * Pack timezone info for MAPI.
2404
	 *
2405
	 * @param array $tz
2406
	 *
2407
	 * @return string
2408
	 */
2409
	private function getMAPIBlobFromTZ($tz) {
2410
		return pack(
2411
			"lllvvvvvvvvvvvvvvvvvv",
2412
			$tz["bias"],
2413
			$tz["stdbias"],
2414
			$tz["dstbias"],
2415
			0,
2416
			0,
2417
			$tz["dstendmonth"],
2418
			$tz["dstendday"],
2419
			$tz["dstendweek"],
2420
			$tz["dstendhour"],
2421
			$tz["dstendminute"],
2422
			$tz["dstendsecond"],
2423
			$tz["dstendmillis"],
2424
			0,
2425
			0,
2426
			$tz["dststartmonth"],
2427
			$tz["dststartday"],
2428
			$tz["dststartweek"],
2429
			$tz["dststarthour"],
2430
			$tz["dststartminute"],
2431
			$tz["dststartsecond"],
2432
			$tz["dststartmillis"]
2433
		);
2434
	}
2435
2436
	/**
2437
	 * Checks the date to see if it is in DST, and returns correct GMT date accordingly.
2438
	 *
2439
	 * @param long  $localtime
2440
	 * @param array $tz
2441
	 *
2442
	 * @return long
2443
	 */
2444
	private function getGMTTimeByTZ($localtime, $tz) {
2445
		if (!isset($tz) || !is_array($tz)) {
2446
			return $localtime;
2447
		}
2448
2449
		if ($this->isDST($localtime, $tz)) {
2450
			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...
2451
		}
2452
2453
		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...
2454
	}
2455
2456
	/**
2457
	 * Returns the local time for the given GMT time, taking account of the given timezone.
2458
	 *
2459
	 * @param long  $gmttime
2460
	 * @param array $tz
2461
	 *
2462
	 * @return long
2463
	 */
2464
	private function getLocaltimeByTZ($gmttime, $tz) {
2465
		if (!isset($tz) || !is_array($tz)) {
2466
			return $gmttime;
2467
		}
2468
2469
		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

2469
		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...
2470
			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...
2471
		}
2472
2473
		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...
2474
	}
2475
2476
	/**
2477
	 * Returns TRUE if it is the summer and therefore DST is in effect.
2478
	 *
2479
	 * @param long  $localtime
2480
	 * @param array $tz
2481
	 *
2482
	 * @return bool
2483
	 */
2484
	private function isDST($localtime, $tz) {
2485
		if (!isset($tz) || !is_array($tz) ||
2486
			!isset($tz["dstbias"]) || $tz["dstbias"] == 0 ||
2487
			!isset($tz["dststartmonth"]) || $tz["dststartmonth"] == 0 ||
2488
			!isset($tz["dstendmonth"]) || $tz["dstendmonth"] == 0) {
2489
			return false;
2490
		}
2491
2492
		$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

2492
		$year = gmdate("Y", /** @scrutinizer ignore-type */ $localtime);
Loading history...
2493
		$start = $this->getTimestampOfWeek($year, $tz["dststartmonth"], $tz["dststartday"], $tz["dststartweek"], $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

2493
		$start = $this->getTimestampOfWeek(/** @scrutinizer ignore-type */ $year, $tz["dststartmonth"], $tz["dststartday"], $tz["dststartweek"], $tz["dststarthour"], $tz["dststartminute"], $tz["dststartsecond"]);
Loading history...
2494
		$end = $this->getTimestampOfWeek($year, $tz["dstendmonth"], $tz["dstendday"], $tz["dstendweek"], $tz["dstendhour"], $tz["dstendminute"], $tz["dstendsecond"]);
2495
2496
		if ($start < $end) {
2497
			// northern hemisphere (july = dst)
2498
			if ($localtime >= $start && $localtime < $end) {
2499
				$dst = true;
2500
			}
2501
			else {
2502
				$dst = false;
2503
			}
2504
		}
2505
		else {
2506
			// southern hemisphere (january = dst)
2507
			if ($localtime >= $end && $localtime < $start) {
2508
				$dst = false;
2509
			}
2510
			else {
2511
				$dst = true;
2512
			}
2513
		}
2514
2515
		return $dst;
2516
	}
2517
2518
	/**
2519
	 * Returns the local timestamp for the $week'th $wday of $month in $year at $hour:$minute:$second.
2520
	 *
2521
	 * @param int $year
2522
	 * @param int $month
2523
	 * @param int $week
2524
	 * @param int $wday
2525
	 * @param int $hour
2526
	 * @param int $minute
2527
	 * @param int $second
2528
	 *
2529
	 * @return long
2530
	 */
2531
	private function getTimestampOfWeek($year, $month, $week, $wday, $hour, $minute, $second) {
2532
		if ($month == 0) {
2533
			return;
2534
		}
2535
2536
		$date = gmmktime($hour, $minute, $second, $month, 1, $year);
2537
2538
		// Find first day in month which matches day of the week
2539
		while (1) {
2540
			$wdaynow = gmdate("w", $date);
2541
			if ($wdaynow == $wday) {
2542
				break;
2543
			}
2544
			$date += 24 * 60 * 60;
2545
		}
2546
2547
		// Forward $week weeks (may 'overflow' into the next month)
2548
		$date = $date + $week * (24 * 60 * 60 * 7);
2549
2550
		// Reverse 'overflow'. Eg week '10' will always be the last week of the month in which the
2551
		// specified weekday exists
2552
		while (1) {
2553
			$monthnow = gmdate("n", $date); // gmdate returns 1-12
2554
			if ($monthnow > $month) {
2555
				$date -= (24 * 7 * 60 * 60);
2556
			}
2557
			else {
2558
				break;
2559
			}
2560
		}
2561
2562
		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...
2563
	}
2564
2565
	/**
2566
	 * Normalize the given timestamp to the start of the day.
2567
	 *
2568
	 * @param long $timestamp
2569
	 *
2570
	 * @return long
2571
	 */
2572
	private function getDayStartOfTimestamp($timestamp) {
2573
		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...
2574
	}
2575
2576
	/**
2577
	 * Returns an SMTP address from an entry id.
2578
	 *
2579
	 * @param string $entryid
2580
	 *
2581
	 * @return string
2582
	 */
2583
	private function getSMTPAddressFromEntryID($entryid) {
2584
		$addrbook = $this->getAddressbook();
2585
2586
		$mailuser = mapi_ab_openentry($addrbook, $entryid);
2587
		if (!$mailuser) {
2588
			return "";
2589
		}
2590
2591
		$props = mapi_getprops($mailuser, [PR_ADDRTYPE, PR_SMTP_ADDRESS, PR_EMAIL_ADDRESS]);
0 ignored issues
show
Bug introduced by
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SMTP_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2592
2593
		$addrtype = isset($props[PR_ADDRTYPE]) ? $props[PR_ADDRTYPE] : "";
2594
2595
		if (isset($props[PR_SMTP_ADDRESS])) {
2596
			return $props[PR_SMTP_ADDRESS];
2597
		}
2598
2599
		if ($addrtype == "SMTP" && isset($props[PR_EMAIL_ADDRESS])) {
2600
			return $props[PR_EMAIL_ADDRESS];
2601
		}
2602
2603
		return "";
2604
	}
2605
2606
	/**
2607
	 * Returns AB data from an entryid.
2608
	 *
2609
	 * @param string $entryid
2610
	 *
2611
	 * @return mixed
2612
	 */
2613
	private function getAbPropsFromEntryID($entryid) {
2614
		$addrbook = $this->getAddressbook();
2615
		$mailuser = mapi_ab_openentry($addrbook, $entryid);
2616
		if ($mailuser) {
2617
			return mapi_getprops($mailuser, [PR_DISPLAY_NAME, PR_ADDRTYPE, PR_SMTP_ADDRESS, PR_EMAIL_ADDRESS]);
0 ignored issues
show
Bug introduced by
The constant PR_SMTP_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2618
		}
2619
2620
		SLog::Write(LOGLEVEL_ERROR, sprintf("MAPIProvider->getAbPropsFromEntryID(): Unable to get mailuser (0x%X)", mapi_last_hresult()));
2621
2622
		return false;
2623
	}
2624
2625
	/**
2626
	 * Builds a displayname from several separated values.
2627
	 *
2628
	 * @param SyncContact $contact
2629
	 *
2630
	 * @return string
2631
	 */
2632
	private function composeDisplayName(&$contact) {
2633
		// Set display name and subject to a combined value of firstname and lastname
2634
		$cname = (isset($contact->prefix)) ? $contact->prefix . " " : "";
2635
		$cname .= $contact->firstname;
2636
		$cname .= (isset($contact->middlename)) ? " " . $contact->middlename : "";
2637
		$cname .= " " . $contact->lastname;
2638
		$cname .= (isset($contact->suffix)) ? " " . $contact->suffix : "";
2639
2640
		return trim($cname);
2641
	}
2642
2643
	/**
2644
	 * Sets all dependent properties for an email address.
2645
	 *
2646
	 * @param string $emailAddress
2647
	 * @param string $displayName
2648
	 * @param int    $cnt
2649
	 * @param array  &$props
2650
	 * @param array  &$properties
2651
	 * @param array  &$nremails
2652
	 * @param int    &$abprovidertype
2653
	 */
2654
	private function setEmailAddress($emailAddress, $displayName, $cnt, &$props, &$properties, &$nremails, &$abprovidertype) {
2655
		if (isset($emailAddress)) {
2656
			$name = (isset($displayName)) ? $displayName : $emailAddress;
2657
2658
			$props[$properties["emailaddress{$cnt}"]] = $emailAddress;
2659
			$props[$properties["emailaddressdemail{$cnt}"]] = $emailAddress;
2660
			$props[$properties["emailaddressdname{$cnt}"]] = $name;
2661
			$props[$properties["emailaddresstype{$cnt}"]] = "SMTP";
2662
			$props[$properties["emailaddressentryid{$cnt}"]] = mapi_createoneoff($name, "SMTP", $emailAddress);
2663
			$nremails[] = $cnt - 1;
2664
			$abprovidertype |= 2 ^ ($cnt - 1);
2665
		}
2666
	}
2667
2668
	/**
2669
	 * Sets the properties for an address string.
2670
	 *
2671
	 * @param string $type        which address is being set
2672
	 * @param string $city
2673
	 * @param string $country
2674
	 * @param string $postalcode
2675
	 * @param string $state
2676
	 * @param string $street
2677
	 * @param array  &$props
2678
	 * @param array  &$properties
2679
	 */
2680
	private function setAddress($type, &$city, &$country, &$postalcode, &$state, &$street, &$props, &$properties) {
2681
		if (isset($city)) {
2682
			$props[$properties[$type . "city"]] = $city;
2683
		}
2684
2685
		if (isset($country)) {
2686
			$props[$properties[$type . "country"]] = $country;
2687
		}
2688
2689
		if (isset($postalcode)) {
2690
			$props[$properties[$type . "postalcode"]] = $postalcode;
2691
		}
2692
2693
		if (isset($state)) {
2694
			$props[$properties[$type . "state"]] = $state;
2695
		}
2696
2697
		if (isset($street)) {
2698
			$props[$properties[$type . "street"]] = $street;
2699
		}
2700
2701
		// set composed address
2702
		$address = Utils::BuildAddressString($street, $postalcode, $city, $state, $country);
2703
		if ($address) {
2704
			$props[$properties[$type . "address"]] = $address;
2705
		}
2706
	}
2707
2708
	/**
2709
	 * Sets the properties for a mailing address.
2710
	 *
2711
	 * @param string $city
2712
	 * @param string $country
2713
	 * @param string $postalcode
2714
	 * @param string $state
2715
	 * @param string $street
2716
	 * @param string $address
2717
	 * @param array  &$props
2718
	 * @param array  &$properties
2719
	 */
2720
	private function setMailingAddress($city, $country, $postalcode, $state, $street, $address, &$props, &$properties) {
2721
		if (isset($city)) {
2722
			$props[$properties["city"]] = $city;
2723
		}
2724
		if (isset($country)) {
2725
			$props[$properties["country"]] = $country;
2726
		}
2727
		if (isset($postalcode)) {
2728
			$props[$properties["postalcode"]] = $postalcode;
2729
		}
2730
		if (isset($state)) {
2731
			$props[$properties["state"]] = $state;
2732
		}
2733
		if (isset($street)) {
2734
			$props[$properties["street"]] = $street;
2735
		}
2736
		if (isset($address)) {
2737
			$props[$properties["postaladdress"]] = $address;
2738
		}
2739
	}
2740
2741
	/**
2742
	 * Sets data in a recurrence array.
2743
	 *
2744
	 * @param SyncObject $message
2745
	 * @param array      &$recur
2746
	 */
2747
	private function setRecurrence($message, &$recur) {
2748
		if (isset($message->complete)) {
2749
			$recur["complete"] = $message->complete;
2750
		}
2751
2752
		if (!isset($message->recurrence->interval)) {
2753
			$message->recurrence->interval = 1;
2754
		}
2755
2756
		// set the default value of numoccur
2757
		$recur["numoccur"] = 0;
2758
		// a place holder for recurrencetype property
2759
		$recur["recurrencetype"] = 0;
2760
2761
		switch ($message->recurrence->type) {
2762
			case 0:
2763
				$recur["type"] = 10;
2764
				if (isset($message->recurrence->dayofweek)) {
2765
					$recur["subtype"] = 1;
2766
				}
2767
				else {
2768
					$recur["subtype"] = 0;
2769
				}
2770
2771
				$recur["everyn"] = $message->recurrence->interval * (60 * 24);
2772
				$recur["recurrencetype"] = 1;
2773
				break;
2774
2775
			case 1:
2776
				$recur["type"] = 11;
2777
				$recur["subtype"] = 1;
2778
				$recur["everyn"] = $message->recurrence->interval;
2779
				$recur["recurrencetype"] = 2;
2780
				break;
2781
2782
			case 2:
2783
				$recur["type"] = 12;
2784
				$recur["subtype"] = 2;
2785
				$recur["everyn"] = $message->recurrence->interval;
2786
				$recur["recurrencetype"] = 3;
2787
				break;
2788
2789
			case 3:
2790
				$recur["type"] = 12;
2791
				$recur["subtype"] = 3;
2792
				$recur["everyn"] = $message->recurrence->interval;
2793
				$recur["recurrencetype"] = 3;
2794
				break;
2795
2796
			case 4:
2797
				$recur["type"] = 13;
2798
				$recur["subtype"] = 1;
2799
				$recur["everyn"] = $message->recurrence->interval * 12;
2800
				$recur["recurrencetype"] = 4;
2801
				break;
2802
2803
			case 5:
2804
				$recur["type"] = 13;
2805
				$recur["subtype"] = 2;
2806
				$recur["everyn"] = $message->recurrence->interval * 12;
2807
				$recur["recurrencetype"] = 4;
2808
				break;
2809
2810
			case 6:
2811
				$recur["type"] = 13;
2812
				$recur["subtype"] = 3;
2813
				$recur["everyn"] = $message->recurrence->interval * 12;
2814
				$recur["recurrencetype"] = 4;
2815
				break;
2816
		}
2817
2818
		// "start" and "end" are in GMT when passing to class.recurrence
2819
		$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

2819
		$recur["end"] = $this->getDayStartOfTimestamp(/** @scrutinizer ignore-type */ 0x7FFFFFFF); // Maximum GMT value for end by default
Loading history...
2820
2821
		if (isset($message->recurrence->until)) {
2822
			$recur["term"] = 0x21;
2823
			$recur["end"] = $message->recurrence->until;
2824
		}
2825
		elseif (isset($message->recurrence->occurrences)) {
2826
			$recur["term"] = 0x22;
2827
			$recur["numoccur"] = $message->recurrence->occurrences;
2828
		}
2829
		else {
2830
			$recur["term"] = 0x23;
2831
		}
2832
2833
		if (isset($message->recurrence->dayofweek)) {
2834
			$recur["weekdays"] = $message->recurrence->dayofweek;
2835
		}
2836
		if (isset($message->recurrence->weekofmonth)) {
2837
			$recur["nday"] = $message->recurrence->weekofmonth;
2838
		}
2839
		if (isset($message->recurrence->monthofyear)) {
2840
			// MAPI stores months as the amount of minutes until the beginning of the month in a
2841
			// non-leapyear. Why this is, is totally unclear.
2842
			$monthminutes = [0, 44640, 84960, 129600, 172800, 217440, 260640, 305280, 348480, 393120, 437760, 480960];
2843
			$recur["month"] = $monthminutes[$message->recurrence->monthofyear - 1];
2844
		}
2845
		if (isset($message->recurrence->dayofmonth)) {
2846
			$recur["monthday"] = $message->recurrence->dayofmonth;
2847
		}
2848
	}
2849
2850
	/**
2851
	 * Extracts the email address (mailbox@host) from an email address because
2852
	 * some devices send email address as "Firstname Lastname" <[email protected]>.
2853
	 *
2854
	 *  @see http://developer.berlios.de/mantis/view.php?id=486
2855
	 *
2856
	 * @param string $email
2857
	 *
2858
	 * @return string or false on error
2859
	 */
2860
	private function extractEmailAddress($email) {
2861
		if (!isset($this->zRFC822)) {
2862
			$this->zRFC822 = new Mail_RFC822();
2863
		}
2864
		$parsedAddress = $this->zRFC822->parseAddressList($email);
2865
		if (!isset($parsedAddress[0]->mailbox) || !isset($parsedAddress[0]->host)) {
2866
			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...
2867
		}
2868
2869
		return $parsedAddress[0]->mailbox . '@' . $parsedAddress[0]->host;
2870
	}
2871
2872
	/**
2873
	 * Returns the message body for a required format.
2874
	 *
2875
	 * @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...
2876
	 * @param int         $bpReturnType
2877
	 * @param SyncObject  $message
2878
	 *
2879
	 * @return bool
2880
	 */
2881
	private function setMessageBodyForType($mapimessage, $bpReturnType, &$message) {
2882
		$truncateHtmlSafe = false;
2883
		// default value is PR_BODY
2884
		$property = PR_BODY;
0 ignored issues
show
Bug introduced by
The constant PR_BODY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2885
2886
		switch ($bpReturnType) {
2887
			case SYNC_BODYPREFERENCE_HTML:
2888
				$property = PR_HTML;
0 ignored issues
show
Bug introduced by
The constant PR_HTML was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2889
				$truncateHtmlSafe = true;
2890
				break;
2891
2892
			case SYNC_BODYPREFERENCE_MIME:
2893
				$stat = $this->imtoinet($mapimessage, $message);
2894
				if (isset($message->asbody)) {
2895
					$message->asbody->type = $bpReturnType;
2896
				}
2897
2898
				return $stat;
2899
		}
2900
2901
		$stream = mapi_openproperty($mapimessage, $property, IID_IStream, 0, 0);
0 ignored issues
show
Bug introduced by
The constant IID_IStream was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2902
		if ($stream) {
2903
			$stat = mapi_stream_stat($stream);
2904
			$streamsize = $stat['cb'];
2905
		}
2906
		else {
2907
			$streamsize = 0;
2908
		}
2909
2910
		// set the properties according to supported AS version
2911
		if (Request::GetProtocolVersion() >= 12.0) {
2912
			$message->asbody = new SyncBaseBody();
2913
			$message->asbody->type = $bpReturnType;
2914
			if (isset($message->internetcpid) && $bpReturnType == SYNC_BODYPREFERENCE_HTML) {
2915
				// if PR_HTML is UTF-8 we can stream it directly, else we have to convert to UTF-8 & wrap it
2916
				if ($message->internetcpid == INTERNET_CPID_UTF8) {
2917
					$message->asbody->data = MAPIStreamWrapper::Open($stream, $truncateHtmlSafe);
2918
				}
2919
				else {
2920
					$body = $this->mapiReadStream($stream, $streamsize);
2921
					$message->asbody->data = StringStreamWrapper::Open(Utils::ConvertCodepageStringToUtf8($message->internetcpid, $body), $truncateHtmlSafe);
2922
					$message->internetcpid = INTERNET_CPID_UTF8;
2923
				}
2924
			}
2925
			else {
2926
				$message->asbody->data = MAPIStreamWrapper::Open($stream);
2927
			}
2928
			$message->asbody->estimatedDataSize = $streamsize;
2929
		}
2930
		else {
2931
			$body = $this->mapiReadStream($stream, $streamsize);
2932
			$message->body = str_replace("\n", "\r\n", str_replace("\r", "", $body));
2933
			$message->bodysize = $streamsize;
2934
			$message->bodytruncated = 0;
2935
		}
2936
2937
		return true;
2938
	}
2939
2940
	/**
2941
	 * Reads from a mapi stream, if it's set. If not, returns an empty string.
2942
	 *
2943
	 * @param resource $stream
2944
	 * @param int      $size
2945
	 *
2946
	 * @return string
2947
	 */
2948
	private function mapiReadStream($stream, $size) {
2949
		if (!$stream || $size == 0) {
0 ignored issues
show
introduced by
$stream is of type resource, thus it always evaluated to false.
Loading history...
2950
			return "";
2951
		}
2952
2953
		return mapi_stream_read($stream, $size);
2954
	}
2955
2956
	/**
2957
	 * Build a filereference key by the clientid.
2958
	 *
2959
	 * @param MAPIMessage $mapimessage
2960
	 * @param mixed       $clientid
2961
	 * @param mixed       $entryid
2962
	 * @param mixed       $parentSourcekey
2963
	 * @param mixed       $exceptionBasedate
2964
	 *
2965
	 * @return string/bool
0 ignored issues
show
Documentation Bug introduced by
The doc comment string/bool at position 0 could not be parsed: Unknown type name 'string/bool' at position 0 in string/bool.
Loading history...
2966
	 */
2967
	private function getFileReferenceForClientId($mapimessage, $clientid, $entryid = 0, $parentSourcekey = 0, $exceptionBasedate = 0) {
2968
		if (!$entryid || !$parentSourcekey) {
2969
			$props = mapi_getprops($mapimessage, [PR_ENTRYID, PR_PARENT_SOURCE_KEY]);
0 ignored issues
show
Bug introduced by
The constant PR_PARENT_SOURCE_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2970
			if (!$entryid && isset($props[PR_ENTRYID])) {
2971
				$entryid = bin2hex($props[PR_ENTRYID]);
2972
			}
2973
			if (!$parentSourcekey && isset($props[PR_PARENT_SOURCE_KEY])) {
2974
				$parentSourcekey = bin2hex($props[PR_PARENT_SOURCE_KEY]);
2975
			}
2976
		}
2977
2978
		$attachtable = mapi_message_getattachmenttable($mapimessage);
2979
		$rows = mapi_table_queryallrows($attachtable, [PR_EC_WA_ATTACHMENT_ID, PR_ATTACH_NUM]);
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_NUM was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EC_WA_ATTACHMENT_ID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2980
		foreach ($rows as $row) {
2981
			if ($row[PR_EC_WA_ATTACHMENT_ID] == $clientid) {
2982
				return sprintf("%s:%s:%s:%s", $entryid, $row[PR_ATTACH_NUM], $parentSourcekey, $exceptionBasedate);
2983
			}
2984
		}
2985
2986
		return false;
2987
	}
2988
2989
	/**
2990
	 * A wrapper for mapi_inetmapi_imtoinet function.
2991
	 *
2992
	 * @param MAPIMessage $mapimessage
2993
	 * @param SyncObject  $message
2994
	 *
2995
	 * @return bool
2996
	 */
2997
	private function imtoinet($mapimessage, &$message) {
2998
		$addrbook = $this->getAddressbook();
2999
		$stream = mapi_inetmapi_imtoinet($this->session, $addrbook, $mapimessage, ['use_tnef' => -1, 'ignore_missing_attachments' => 1]);
3000
		// is_resource($stream) returns false in PHP8
3001
		if ($stream !== null && mapi_last_hresult() === ecSuccess) {
0 ignored issues
show
Bug introduced by
The constant ecSuccess was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3002
			$mstreamstat = mapi_stream_stat($stream);
3003
			$streamsize = $mstreamstat["cb"];
3004
			if (isset($streamsize)) {
3005
				if (Request::GetProtocolVersion() >= 12.0) {
3006
					if (!isset($message->asbody)) {
3007
						$message->asbody = new SyncBaseBody();
3008
					}
3009
					$message->asbody->data = MAPIStreamWrapper::Open($stream);
3010
					$message->asbody->estimatedDataSize = $streamsize;
3011
					$message->asbody->truncated = 0;
3012
				}
3013
				else {
3014
					$message->mimedata = MAPIStreamWrapper::Open($stream);
3015
					$message->mimesize = $streamsize;
3016
					$message->mimetruncated = 0;
3017
				}
3018
				unset($message->body, $message->bodytruncated);
3019
3020
				return true;
3021
			}
3022
		}
3023
		SLog::Write(LOGLEVEL_ERROR, sprintf("MAPIProvider->imtoinet(): got no stream or content from mapi_inetmapi_imtoinet(): 0x%08X", mapi_last_hresult()));
3024
3025
		return false;
3026
	}
3027
3028
	/**
3029
	 * Sets the message body.
3030
	 *
3031
	 * @param MAPIMessage       $mapimessage
3032
	 * @param ContentParameters $contentparameters
3033
	 * @param SyncObject        $message
3034
	 */
3035
	private function setMessageBody($mapimessage, $contentparameters, &$message) {
3036
		// get the available body preference types
3037
		$bpTypes = $contentparameters->GetBodyPreference();
3038
		if ($bpTypes !== false) {
3039
			SLog::Write(LOGLEVEL_DEBUG, sprintf("BodyPreference types: %s", implode(', ', $bpTypes)));
3040
			// do not send mime data if the client requests it
3041
			if (($contentparameters->GetMimeSupport() == SYNC_MIMESUPPORT_NEVER) && ($key = array_search(SYNC_BODYPREFERENCE_MIME, $bpTypes) !== false)) {
3042
				unset($bpTypes[$key]);
3043
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Remove mime body preference type because the device required no mime support. BodyPreference types: %s", implode(', ', $bpTypes)));
3044
			}
3045
			// get the best fitting preference type
3046
			$bpReturnType = Utils::GetBodyPreferenceBestMatch($bpTypes);
3047
			SLog::Write(LOGLEVEL_DEBUG, sprintf("GetBodyPreferenceBestMatch: %d", $bpReturnType));
3048
			$bpo = $contentparameters->BodyPreference($bpReturnType);
3049
			SLog::Write(LOGLEVEL_DEBUG, sprintf("bpo: truncation size:'%d', allornone:'%d', preview:'%d'", $bpo->GetTruncationSize(), $bpo->GetAllOrNone(), $bpo->GetPreview()));
3050
3051
			// Android Blackberry expects a full mime message for signed emails
3052
			// @TODO change this when refactoring
3053
			$props = mapi_getprops($mapimessage, [PR_MESSAGE_CLASS]);
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3054
			if (isset($props[PR_MESSAGE_CLASS]) &&
3055
					stripos($props[PR_MESSAGE_CLASS], 'IPM.Note.SMIME.MultipartSigned') !== false &&
3056
					($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...
3057
				SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->setMessageBody(): enforcing SYNC_BODYPREFERENCE_MIME type for a signed message"));
3058
				$bpReturnType = SYNC_BODYPREFERENCE_MIME;
3059
			}
3060
3061
			$this->setMessageBodyForType($mapimessage, $bpReturnType, $message);
3062
			// only set the truncation size data if device set it in request
3063
			if ($bpo->GetTruncationSize() != false &&
3064
					$bpReturnType != SYNC_BODYPREFERENCE_MIME &&
3065
					$message->asbody->estimatedDataSize > $bpo->GetTruncationSize()
3066
			) {
3067
				// Truncated plaintext requests are used on iOS for the preview in the email list. All images and links should be removed.
3068
				if ($bpReturnType == SYNC_BODYPREFERENCE_PLAIN) {
3069
					SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->setMessageBody(): truncated plain-text body requested, stripping all links and images");
3070
					// 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.
3071
					$plainbody = stream_get_contents($message->asbody->data, $bpo->GetTruncationSize() * 5);
3072
					$message->asbody->data = StringStreamWrapper::Open(preg_replace('/<http(s){0,1}:\/\/.*?>/i', '', $plainbody));
3073
				}
3074
3075
				// truncate data stream
3076
				ftruncate($message->asbody->data, $bpo->GetTruncationSize());
3077
				$message->asbody->truncated = 1;
3078
			}
3079
			// set the preview or windows phones won't show the preview of an email
3080
			if (Request::GetProtocolVersion() >= 14.0 && $bpo->GetPreview()) {
3081
				$message->asbody->preview = Utils::Utf8_truncate(MAPIUtils::readPropStream($mapimessage, PR_BODY), $bpo->GetPreview());
0 ignored issues
show
Bug introduced by
The constant PR_BODY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3082
			}
3083
		}
3084
		else {
3085
			// Override 'body' for truncation
3086
			$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

3086
			$truncsize = Utils::GetTruncSize($contentparameters->/** @scrutinizer ignore-call */ GetTruncation());
Loading history...
3087
			$this->setMessageBodyForType($mapimessage, SYNC_BODYPREFERENCE_PLAIN, $message);
3088
3089
			if ($message->bodysize > $truncsize) {
3090
				$message->body = Utils::Utf8_truncate($message->body, $truncsize);
3091
				$message->bodytruncated = 1;
3092
			}
3093
3094
			if (!isset($message->body) || strlen($message->body) == 0) {
3095
				$message->body = " ";
3096
			}
3097
3098
			if ($contentparameters->GetMimeSupport() == SYNC_MIMESUPPORT_ALWAYS) {
3099
				// set the html body for iphone in AS 2.5 version
3100
				$this->imtoinet($mapimessage, $message);
3101
			}
3102
		}
3103
	}
3104
3105
	/**
3106
	 * Sets properties for an email message.
3107
	 *
3108
	 * @param mixed    $mapimessage
3109
	 * @param SyncMail $message
3110
	 */
3111
	private function setFlag($mapimessage, &$message) {
3112
		// do nothing if protocol version is lower than 12.0 as flags haven't been defined before
3113
		if (Request::GetProtocolVersion() < 12.0) {
3114
			return;
3115
		}
3116
3117
		$message->flag = new SyncMailFlags();
3118
3119
		$this->getPropsFromMAPI($message->flag, $mapimessage, MAPIMapping::GetMailFlagsMapping());
3120
	}
3121
3122
	/**
3123
	 * Sets information from SyncBaseBody type for a MAPI message.
3124
	 *
3125
	 * @param SyncBaseBody $asbody
3126
	 * @param array        $props
3127
	 * @param array        $appointmentprops
3128
	 */
3129
	private function setASbody($asbody, &$props, $appointmentprops) {
3130
		// TODO: fix checking for the length
3131
		if (isset($asbody->type, $asbody->data)   /* && strlen($asbody->data) > 0 */) {
3132
			switch ($asbody->type) {
3133
				case SYNC_BODYPREFERENCE_PLAIN:
3134
				default:
3135
					// set plain body if the type is not in valid range
3136
					$props[$appointmentprops["body"]] = stream_get_contents($asbody->data);
3137
					break;
3138
3139
				case SYNC_BODYPREFERENCE_HTML:
3140
					$props[$appointmentprops["html"]] = stream_get_contents($asbody->data);
3141
					break;
3142
3143
				case SYNC_BODYPREFERENCE_MIME:
3144
					break;
3145
			}
3146
		}
3147
		else {
3148
			SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->setASbody either type or data are not set. Setting to empty body");
3149
			$props[$appointmentprops["body"]] = "";
3150
		}
3151
	}
3152
3153
	/**
3154
	 * Sets attachments from an email message to a SyncObject.
3155
	 *
3156
	 * @param mixed      $mapimessage
3157
	 * @param SyncObject $message
3158
	 * @param string     $entryid
3159
	 * @param string     $parentSourcekey
3160
	 * @param mixed      $exceptionBasedate
3161
	 */
3162
	private function setAttachment($mapimessage, &$message, $entryid, $parentSourcekey, $exceptionBasedate = 0) {
3163
		// Add attachments
3164
		$attachtable = mapi_message_getattachmenttable($mapimessage);
3165
		$rows = mapi_table_queryallrows($attachtable, [PR_ATTACH_NUM]);
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_NUM was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3166
3167
		foreach ($rows as $row) {
3168
			if (isset($row[PR_ATTACH_NUM])) {
3169
				if (Request::GetProtocolVersion() >= 12.0) {
3170
					$attach = new SyncBaseAttachment();
3171
				}
3172
				else {
3173
					$attach = new SyncAttachment();
3174
				}
3175
3176
				$mapiattach = mapi_message_openattach($mapimessage, $row[PR_ATTACH_NUM]);
3177
				$attachprops = mapi_getprops($mapiattach, [PR_ATTACH_LONG_FILENAME, PR_ATTACH_FILENAME, PR_ATTACHMENT_HIDDEN, PR_ATTACH_CONTENT_ID, PR_ATTACH_CONTENT_ID_A, PR_ATTACH_MIME_TAG, PR_ATTACH_METHOD, PR_DISPLAY_NAME, PR_ATTACH_SIZE, PR_ATTACH_FLAGS]);
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_METHOD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACHMENT_HIDDEN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_MIME_TAG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_FILENAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_CONTENT_ID_A was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_LONG_FILENAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_SIZE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_CONTENT_ID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3178
				if (isset($attachprops[PR_ATTACH_MIME_TAG]) && strpos(strtolower($attachprops[PR_ATTACH_MIME_TAG]), 'signed') !== false) {
3179
					continue;
3180
				}
3181
3182
				// the displayname is handled equally for all AS versions
3183
				$attach->displayname = (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"));
3184
				// fix attachment name in case of inline images
3185
				if (($attach->displayname == "inline.txt" && isset($attachprops[PR_ATTACH_MIME_TAG])) ||
3186
						(substr_compare($attach->displayname, "attachment", 0, 10, true) === 0 && substr_compare($attach->displayname, ".dat", -4, 4, true) === 0)) {
3187
					$mimetype = $attachprops[PR_ATTACH_MIME_TAG] ?? 'application/octet-stream';
3188
					$mime = explode("/", $mimetype);
3189
3190
					if (count($mime) == 2 && $mime[0] == "image") {
3191
						$attach->displayname = "inline." . $mime[1];
3192
					}
3193
				}
3194
3195
				// set AS version specific parameters
3196
				if (Request::GetProtocolVersion() >= 12.0) {
3197
					$attach->filereference = sprintf("%s:%s:%s:%s", $entryid, $row[PR_ATTACH_NUM], $parentSourcekey, $exceptionBasedate);
0 ignored issues
show
Bug introduced by
The property filereference does not seem to exist on SyncAttachment.
Loading history...
3198
					$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...
Bug introduced by
The constant ATTACH_BY_VALUE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3199
3200
					// if displayname does not have the eml extension for embedde messages, android and WP devices won't open it
3201
					if ($attach->method == ATTACH_EMBEDDED_MSG) {
0 ignored issues
show
Bug introduced by
The constant ATTACH_EMBEDDED_MSG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3202
						if (strtolower(substr($attach->displayname, -4)) != '.eml') {
3203
							$attach->displayname .= '.eml';
3204
						}
3205
					}
3206
					// android devices require attachment size in order to display an attachment properly
3207
					if (!isset($attachprops[PR_ATTACH_SIZE])) {
3208
						$stream = mapi_openproperty($mapiattach, PR_ATTACH_DATA_BIN, IID_IStream, 0, 0);
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_DATA_BIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant IID_IStream was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3209
						// It's not possible to open some (embedded only?) messages, so we need to open the attachment object itself to get the data
3210
						if (mapi_last_hresult()) {
3211
							$embMessage = mapi_attach_openobj($mapiattach);
3212
							$addrbook = $this->getAddressbook();
3213
							$stream = mapi_inetmapi_imtoinet($this->session, $addrbook, $embMessage, ['use_tnef' => -1]);
3214
						}
3215
						$stat = mapi_stream_stat($stream);
3216
						$attach->estimatedDataSize = $stat['cb'];
0 ignored issues
show
Bug introduced by
The property estimatedDataSize does not seem to exist on SyncAttachment.
Loading history...
3217
					}
3218
					else {
3219
						$attach->estimatedDataSize = $attachprops[PR_ATTACH_SIZE];
3220
					}
3221
3222
					if (isset($attachprops[PR_ATTACH_CONTENT_ID]) && $attachprops[PR_ATTACH_CONTENT_ID]) {
3223
						$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...
3224
					}
3225
3226
					if (!isset($attach->contentid) && isset($attachprops[PR_ATTACH_CONTENT_ID_A]) && $attachprops[PR_ATTACH_CONTENT_ID_A]) {
3227
						$attach->contentid = $attachprops[PR_ATTACH_CONTENT_ID_A];
3228
					}
3229
3230
					if (isset($attachprops[PR_ATTACHMENT_HIDDEN]) && $attachprops[PR_ATTACHMENT_HIDDEN]) {
3231
						$attach->isinline = 1;
0 ignored issues
show
Bug introduced by
The property isinline does not seem to exist on SyncAttachment.
Loading history...
3232
					}
3233
3234
					if (isset($attach->contentid, $attachprops[PR_ATTACH_FLAGS]) && $attachprops[PR_ATTACH_FLAGS] & 4) {
3235
						$attach->isinline = 1;
3236
					}
3237
3238
					if (!isset($message->asattachments)) {
3239
						$message->asattachments = [];
3240
					}
3241
3242
					array_push($message->asattachments, $attach);
3243
				}
3244
				else {
3245
					$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...
3246
					$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...
3247
					if (!isset($message->attachments)) {
3248
						$message->attachments = [];
3249
					}
3250
3251
					array_push($message->attachments, $attach);
3252
				}
3253
			}
3254
		}
3255
	}
3256
3257
	/**
3258
	 * Update attachments of a mapimessage based on asattachments received.
3259
	 *
3260
	 * @param MAPIMessage $mapimessage
3261
	 * @param array       $asattachments
3262
	 * @param SyncObject  $response
3263
	 */
3264
	public function editAttachments($mapimessage, $asattachments, &$response) {
3265
		foreach ($asattachments as $att) {
3266
			// new attachment to be saved
3267
			if ($att instanceof SyncBaseAttachmentAdd) {
3268
				if (!isset($att->content)) {
3269
					SLog::Write(LOGLEVEL_WARN, sprintf("MAPIProvider->editAttachments(): Ignoring attachment %s to be added as it has no content: %s", $att->clientid, $att->displayname));
0 ignored issues
show
Bug introduced by
The property clientid does not seem to exist on SyncBaseAttachmentAdd.
Loading history...
3270
3271
					continue;
3272
				}
3273
3274
				SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->editAttachments(): Saving attachment %s with name: %s", $att->clientid, $att->displayname));
3275
				// only create if the attachment does not already exist
3276
				if ($this->getFileReferenceForClientId($mapimessage, $att->clientid, 0, 0) === false) {
3277
					// TODO: check: contentlocation
3278
					$props = [
3279
						PR_ATTACH_LONG_FILENAME => $att->displayname,
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_LONG_FILENAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3280
						PR_DISPLAY_NAME => $att->displayname,
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3281
						PR_ATTACH_METHOD => $att->method, // is this correct ??
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_METHOD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3282
						PR_ATTACH_DATA_BIN => "",
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_DATA_BIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3283
						PR_ATTACHMENT_HIDDEN => false,
0 ignored issues
show
Bug introduced by
The constant PR_ATTACHMENT_HIDDEN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3284
						PR_ATTACH_EXTENSION => pathinfo($att->displayname, PATHINFO_EXTENSION),
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_EXTENSION was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3285
						PR_EC_WA_ATTACHMENT_ID => $att->clientid,
0 ignored issues
show
Bug introduced by
The constant PR_EC_WA_ATTACHMENT_ID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3286
					];
3287
					if (!empty($att->contenttype)) {
0 ignored issues
show
Bug introduced by
The property contenttype does not exist on SyncBaseAttachmentAdd. Did you mean content?
Loading history...
3288
						$props[PR_ATTACH_MIME_TAG] = $att->contenttype;
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_MIME_TAG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3289
					}
3290
					if (!empty($att->contentid)) {
3291
						$props[PR_ATTACH_CONTENT_ID] = $att->contentid;
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_CONTENT_ID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3292
					}
3293
3294
					$attachment = mapi_message_createattach($mapimessage);
3295
					mapi_setprops($attachment, $props);
3296
3297
					// Stream the file to the PR_ATTACH_DATA_BIN property
3298
					$stream = mapi_openproperty($attachment, PR_ATTACH_DATA_BIN, IID_IStream, 0, MAPI_CREATE | MAPI_MODIFY);
0 ignored issues
show
Bug introduced by
The constant IID_IStream was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant MAPI_CREATE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant MAPI_MODIFY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3299
					mapi_stream_write($stream, stream_get_contents($att->content));
3300
3301
					// Commit the stream and save changes
3302
					mapi_stream_commit($stream);
3303
					mapi_savechanges($attachment);
3304
				}
3305
				if (!isset($response->asattachments)) {
3306
					$response->asattachments = [];
3307
				}
3308
				// respond linking the clientid with the newly created filereference
3309
				$attResp = new SyncBaseAttachment();
3310
				$attResp->clientid = $att->clientid;
0 ignored issues
show
Bug introduced by
The property clientid does not seem to exist on SyncBaseAttachment.
Loading history...
3311
				$attResp->filereference = $this->getFileReferenceForClientId($mapimessage, $att->clientid, 0, 0);
3312
				$response->asattachments[] = $attResp;
3313
				$response->hasResponse = true;
0 ignored issues
show
Bug introduced by
The property hasResponse does not seem to exist on SyncObject.
Loading history...
3314
			}
3315
			// attachment to be removed
3316
			elseif ($att instanceof SyncBaseAttachmentDelete) {
3317
				list($id, $attachnum, $parentEntryid, $exceptionBasedate) = explode(":", $att->filereference);
3318
				SLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->editAttachments(): Deleting attachment with num: %s", $attachnum));
3319
				mapi_message_deleteattach($mapimessage, (int) $attachnum);
3320
			}
3321
		}
3322
	}
3323
3324
	/**
3325
	 * Sets information from SyncLocation type for a MAPI message.
3326
	 *
3327
	 * @param SyncBaseBody $aslocation
3328
	 * @param array        $props
3329
	 * @param array        $appointmentprops
3330
	 */
3331
	private function setASlocation($aslocation, &$props, $appointmentprops) {
3332
		$fullAddress = "";
3333
		if ($aslocation->street || $aslocation->city || $aslocation->state || $aslocation->country || $aslocation->postalcode) {
0 ignored issues
show
Bug introduced by
The property country does not seem to exist on SyncBaseBody.
Loading history...
Bug introduced by
The property postalcode does not seem to exist on SyncBaseBody.
Loading history...
Bug introduced by
The property state does not seem to exist on SyncBaseBody.
Loading history...
Bug introduced by
The property street does not seem to exist on SyncBaseBody.
Loading history...
Bug introduced by
The property city does not seem to exist on SyncBaseBody.
Loading history...
3334
			$fullAddress = $aslocation->street . ", " . $aslocation->city . "-" . $aslocation->state . "," . $aslocation->country . "," . $aslocation->postalcode;
3335
		}
3336
3337
		// Determine which data to use as DisplayName. This is also set to the traditional location property for backwards compatibility (this is currently displayed in OL).
3338
		$useStreet = false;
3339
		if ($aslocation->displayname) {
0 ignored issues
show
Bug introduced by
The property displayname does not seem to exist on SyncBaseBody.
Loading history...
3340
			$props[$appointmentprops["location"]] = $aslocation->displayname;
3341
		}
3342
		elseif ($aslocation->street) {
3343
			$useStreet = true;
3344
			$props[$appointmentprops["location"]] = $fullAddress;
3345
		}
3346
		elseif ($aslocation->city) {
3347
			$props[$appointmentprops["location"]] = $aslocation->city;
3348
		}
3349
		$loc = [];
3350
		$loc["DisplayName"] = ($useStreet) ? $aslocation->street : $props[$appointmentprops["location"]];
3351
		$loc["LocationAnnotation"] = ($aslocation->annotation) ? $aslocation->annotation : "";
0 ignored issues
show
Bug introduced by
The property annotation does not seem to exist on SyncBaseBody.
Loading history...
3352
		$loc["LocationSource"] = "None";
3353
		$loc["Unresolved"] = ($aslocation->locationuri) ? false : true;
0 ignored issues
show
Bug introduced by
The property locationuri does not seem to exist on SyncBaseBody.
Loading history...
3354
		$loc["LocationUri"] = $aslocation->locationuri ?? "";
3355
		$loc["Latitude"] = ($aslocation->latitude) ? floatval($aslocation->latitude) : null;
0 ignored issues
show
Bug introduced by
The property latitude does not seem to exist on SyncBaseBody.
Loading history...
3356
		$loc["Longitude"] = ($aslocation->longitude) ? floatval($aslocation->longitude) : null;
0 ignored issues
show
Bug introduced by
The property longitude does not seem to exist on SyncBaseBody.
Loading history...
3357
		$loc["Altitude"] = ($aslocation->altitude) ? floatval($aslocation->altitude) : null;
0 ignored issues
show
Bug introduced by
The property altitude does not seem to exist on SyncBaseBody.
Loading history...
3358
		$loc["Accuracy"] = ($aslocation->accuracy) ? floatval($aslocation->accuracy) : null;
0 ignored issues
show
Bug introduced by
The property accuracy does not seem to exist on SyncBaseBody.
Loading history...
3359
		$loc["AltitudeAccuracy"] = ($aslocation->altitudeaccuracy) ? floatval($aslocation->altitudeaccuracy) : null;
0 ignored issues
show
Bug introduced by
The property altitudeaccuracy does not seem to exist on SyncBaseBody.
Loading history...
3360
		$loc["LocationStreet"] = $aslocation->street ?? "";
3361
		$loc["LocationCity"] = $aslocation->city ?? "";
3362
		$loc["LocationState"] = $aslocation->state ?? "";
3363
		$loc["LocationCountry"] = $aslocation->country ?? "";
3364
		$loc["LocationPostalCode"] = $aslocation->postalcode ?? "";
3365
		$loc["LocationFullAddress"] = $fullAddress;
3366
3367
		$props[$appointmentprops["locations"]] = json_encode([$loc], JSON_UNESCAPED_UNICODE);
3368
	}
3369
3370
	/**
3371
	 * Gets information from a MAPI message and applies it to a SyncLocation object.
3372
	 *
3373
	 * @param MAPIMessage $mapimessage
3374
	 * @param SyncObject  $aslocation
3375
	 * @param array       $appointmentprops
3376
	 */
3377
	private function getASlocation($mapimessage, &$aslocation, $appointmentprops) {
3378
		$props = mapi_getprops($mapimessage, [$appointmentprops["locations"], $appointmentprops["location"]]);
3379
		// set the old location as displayname - this is also the "correct" approach if there is more than one location in the "locations" property json
3380
		if (isset($props[$appointmentprops["location"]])) {
3381
			$aslocation->displayname = $props[$appointmentprops["location"]];
3382
		}
3383
3384
		if (isset($props[$appointmentprops["locations"]])) {
3385
			$loc = json_decode($props[$appointmentprops["locations"]], true);
3386
			if (is_array($loc) && count($loc) == 1) {
3387
				$l = $loc[0];
3388
				if (!empty($l['DisplayName'])) {
3389
					$aslocation->displayname = $l['DisplayName'];
3390
				}
3391
				if (!empty($l['LocationAnnotation'])) {
3392
					$aslocation->annotation = $l['LocationAnnotation'];
3393
				}
3394
				if (!empty($l['LocationStreet'])) {
3395
					$aslocation->street = $l['LocationStreet'];
3396
				}
3397
				if (!empty($l['LocationCity'])) {
3398
					$aslocation->city = $l['LocationCity'];
3399
				}
3400
				if (!empty($l['LocationState'])) {
3401
					$aslocation->state = $l['LocationState'];
3402
				}
3403
				if (!empty($l['LocationCountry'])) {
3404
					$aslocation->country = $l['LocationCountry'];
3405
				}
3406
				if (!empty($l['LocationPostalCode'])) {
3407
					$aslocation->postalcode = $l['LocationPostalCode'];
3408
				}
3409
				if (isset($l['Latitude']) && is_numeric($l['Latitude'])) {
3410
					$aslocation->latitude = floatval($l['Latitude']);
3411
				}
3412
				if (isset($l['Longitude']) && is_numeric($l['Longitude'])) {
3413
					$aslocation->longitude = floatval($l['Longitude']);
3414
				}
3415
				if (isset($l['Accuracy']) && is_numeric($l['Accuracy'])) {
3416
					$aslocation->accuracy = floatval($l['Accuracy']);
3417
				}
3418
				if (isset($l['Altitude']) && is_numeric($l['Altitude'])) {
3419
					$aslocation->altitude = floatval($l['Altitude']);
3420
				}
3421
				if (isset($l['AltitudeAccuracy']) && is_numeric($l['AltitudeAccuracy'])) {
3422
					$aslocation->altitudeaccuracy = floatval($l['AltitudeAccuracy']);
3423
				}
3424
				if (!empty($l['LocationUri'])) {
3425
					$aslocation->locationuri = $l['LocationUri'];
3426
				}
3427
			}
3428
		}
3429
	}
3430
3431
	/**
3432
	 * Get MAPI addressbook object.
3433
	 *
3434
	 * @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...
3435
	 */
3436
	private function getAddressbook() {
3437
		if (isset($this->addressbook) && $this->addressbook) {
3438
			return $this->addressbook;
3439
		}
3440
		$this->addressbook = mapi_openaddressbook($this->session);
3441
		$result = mapi_last_hresult();
3442
		if ($result && $this->addressbook === false) {
3443
			SLog::Write(LOGLEVEL_ERROR, sprintf("MAPIProvider->getAddressbook error opening addressbook 0x%X", $result));
3444
3445
			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...
3446
		}
3447
3448
		return $this->addressbook;
3449
	}
3450
3451
	/**
3452
	 * Gets the required store properties.
3453
	 *
3454
	 * @return array
3455
	 */
3456
	public function GetStoreProps() {
3457
		if (!isset($this->storeProps) || empty($this->storeProps)) {
3458
			SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->GetStoreProps(): Getting store properties.");
3459
			$this->storeProps = mapi_getprops($this->store, [PR_IPM_SUBTREE_ENTRYID, PR_IPM_OUTBOX_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_ENTRYID, PR_IPM_PUBLIC_FOLDERS_ENTRYID, PR_IPM_FAVORITES_ENTRYID, PR_MAILBOX_OWNER_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_FAVORITES_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_SUBTREE_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_PUBLIC_FOLDERS_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_MAILBOX_OWNER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_OUTBOX_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_SENTMAIL_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_WASTEBASKET_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3460
			// make sure all properties are set
3461
			if (!isset($this->storeProps[PR_IPM_WASTEBASKET_ENTRYID])) {
3462
				$this->storeProps[PR_IPM_WASTEBASKET_ENTRYID] = false;
3463
			}
3464
			if (!isset($this->storeProps[PR_IPM_SENTMAIL_ENTRYID])) {
3465
				$this->storeProps[PR_IPM_SENTMAIL_ENTRYID] = false;
3466
			}
3467
			if (!isset($this->storeProps[PR_IPM_OUTBOX_ENTRYID])) {
3468
				$this->storeProps[PR_IPM_OUTBOX_ENTRYID] = false;
3469
			}
3470
			if (!isset($this->storeProps[PR_IPM_PUBLIC_FOLDERS_ENTRYID])) {
3471
				$this->storeProps[PR_IPM_PUBLIC_FOLDERS_ENTRYID] = false;
3472
			}
3473
		}
3474
3475
		return $this->storeProps;
3476
	}
3477
3478
	/**
3479
	 * Gets the required inbox properties.
3480
	 *
3481
	 * @return array
3482
	 */
3483
	public function GetInboxProps() {
3484
		if (!isset($this->inboxProps) || empty($this->inboxProps)) {
3485
			SLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->GetInboxProps(): Getting inbox properties.");
3486
			$this->inboxProps = [];
3487
			$inbox = mapi_msgstore_getreceivefolder($this->store);
3488
			if ($inbox) {
3489
				$this->inboxProps = mapi_getprops($inbox, [PR_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_TASK_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_JOURNAL_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_APPOINTMENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_JOURNAL_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_DRAFTS_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_TASK_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_NOTE_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_CONTACT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3490
				// make sure all properties are set
3491
				if (!isset($this->inboxProps[PR_ENTRYID])) {
3492
					$this->inboxProps[PR_ENTRYID] = false;
3493
				}
3494
				if (!isset($this->inboxProps[PR_IPM_DRAFTS_ENTRYID])) {
3495
					$this->inboxProps[PR_IPM_DRAFTS_ENTRYID] = false;
3496
				}
3497
				if (!isset($this->inboxProps[PR_IPM_TASK_ENTRYID])) {
3498
					$this->inboxProps[PR_IPM_TASK_ENTRYID] = false;
3499
				}
3500
				if (!isset($this->inboxProps[PR_IPM_APPOINTMENT_ENTRYID])) {
3501
					$this->inboxProps[PR_IPM_APPOINTMENT_ENTRYID] = false;
3502
				}
3503
				if (!isset($this->inboxProps[PR_IPM_CONTACT_ENTRYID])) {
3504
					$this->inboxProps[PR_IPM_CONTACT_ENTRYID] = false;
3505
				}
3506
				if (!isset($this->inboxProps[PR_IPM_NOTE_ENTRYID])) {
3507
					$this->inboxProps[PR_IPM_NOTE_ENTRYID] = false;
3508
				}
3509
				if (!isset($this->inboxProps[PR_IPM_JOURNAL_ENTRYID])) {
3510
					$this->inboxProps[PR_IPM_JOURNAL_ENTRYID] = false;
3511
				}
3512
			}
3513
		}
3514
3515
		return $this->inboxProps;
3516
	}
3517
3518
	/**
3519
	 * Gets the required store root properties.
3520
	 *
3521
	 * @return array
3522
	 */
3523
	private function getRootProps() {
3524
		if (!isset($this->rootProps)) {
3525
			$root = mapi_msgstore_openentry($this->store, null);
3526
			$this->rootProps = mapi_getprops($root, [PR_ADDITIONAL_REN_ENTRYIDS_EX]);
0 ignored issues
show
Bug introduced by
The constant PR_ADDITIONAL_REN_ENTRYIDS_EX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3527
		}
3528
3529
		return $this->rootProps;
3530
	}
3531
3532
	/**
3533
	 * Returns an array with entryids of some special folders.
3534
	 *
3535
	 * @return array
3536
	 */
3537
	private function getSpecialFoldersData() {
3538
		// The persist data of an entry in PR_ADDITIONAL_REN_ENTRYIDS_EX consists of:
3539
		//      PersistId - e.g. RSF_PID_SUGGESTED_CONTACTS (2 bytes)
3540
		//      DataElementsSize - size of DataElements field (2 bytes)
3541
		//      DataElements - array of PersistElement structures (variable size)
3542
		//          PersistElement Structure consists of
3543
		//              ElementID - e.g. RSF_ELID_ENTRYID (2 bytes)
3544
		//              ElementDataSize - size of ElementData (2 bytes)
3545
		//              ElementData - The data for the special folder identified by the PersistID (variable size)
3546
		if (empty($this->specialFoldersData)) {
3547
			$this->specialFoldersData = [];
3548
			$rootProps = $this->getRootProps();
3549
			if (isset($rootProps[PR_ADDITIONAL_REN_ENTRYIDS_EX])) {
0 ignored issues
show
Bug introduced by
The constant PR_ADDITIONAL_REN_ENTRYIDS_EX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3550
				$persistData = $rootProps[PR_ADDITIONAL_REN_ENTRYIDS_EX];
3551
				while (strlen($persistData) > 0) {
3552
					// PERSIST_SENTINEL marks the end of the persist data
3553
					if (strlen($persistData) == 4 && intval($persistData) == PERSIST_SENTINEL) {
0 ignored issues
show
Bug introduced by
The constant PERSIST_SENTINEL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3554
						break;
3555
					}
3556
					$unpackedData = unpack("vdataSize/velementID/velDataSize", substr($persistData, 2, 6));
3557
					if (isset($unpackedData['dataSize'], $unpackedData['elementID']) && $unpackedData['elementID'] == RSF_ELID_ENTRYID && isset($unpackedData['elDataSize'])) {
0 ignored issues
show
Bug introduced by
The constant RSF_ELID_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3558
						$this->specialFoldersData[] = substr($persistData, 8, $unpackedData['elDataSize']);
3559
						// Add PersistId and DataElementsSize lengths to the data size as they're not part of it
3560
						$persistData = substr($persistData, $unpackedData['dataSize'] + 4);
3561
					}
3562
					else {
3563
						SLog::Write(LOGLEVEL_INFO, "MAPIProvider->getSpecialFoldersData(): persistent data is not valid");
3564
						break;
3565
					}
3566
				}
3567
			}
3568
		}
3569
3570
		return $this->specialFoldersData;
3571
	}
3572
3573
	/**
3574
	 * Extracts email address from PR_SEARCH_KEY property if possible.
3575
	 *
3576
	 * @param string $searchKey
3577
	 *
3578
	 * @return string
3579
	 */
3580
	private function getEmailAddressFromSearchKey($searchKey) {
3581
		if (strpos($searchKey, ':') !== false && strpos($searchKey, '@') !== false) {
3582
			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");
3583
3584
			return trim(strtolower(explode(':', $searchKey)[1]));
3585
		}
3586
3587
		return "";
3588
	}
3589
3590
	/**
3591
	 * Returns categories for a message.
3592
	 *
3593
	 * @param binary $parentsourcekey
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...
3594
	 * @param binary $sourcekey
3595
	 *
3596
	 * @return array or false on failure
3597
	 */
3598
	public function GetMessageCategories($parentsourcekey, $sourcekey) {
3599
		$entryid = mapi_msgstore_entryidfromsourcekey($this->store, $parentsourcekey, $sourcekey);
3600
		if (!$entryid) {
3601
			SLog::Write(LOGLEVEL_INFO, sprintf("MAPIProvider->GetMessageCategories(): Couldn't retrieve message, sourcekey: '%s', parentsourcekey: '%s'", bin2hex($sourcekey), bin2hex($parentsourcekey)));
3602
3603
			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...
3604
		}
3605
		$mapimessage = mapi_msgstore_openentry($this->store, $entryid);
3606
		$emailMapping = MAPIMapping::GetEmailMapping();
3607
		$emailMapping = ["categories" => $emailMapping["categories"]];
3608
		$messageCategories = $this->getProps($mapimessage, $emailMapping);
3609
		if (isset($messageCategories[$emailMapping["categories"]]) && is_array($messageCategories[$emailMapping["categories"]])) {
3610
			return $messageCategories[$emailMapping["categories"]];
3611
		}
3612
3613
		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...
3614
	}
3615
3616
	/**
3617
	 * Adds recipients to the recips array.
3618
	 *
3619
	 * @param string $recip
3620
	 * @param int    $type
3621
	 * @param array  $recips
3622
	 */
3623
	private function addRecips($recip, $type, &$recips) {
3624
		if (!empty($recip) && is_array($recip)) {
0 ignored issues
show
introduced by
The condition is_array($recip) is always false.
Loading history...
3625
			$emails = $recip;
3626
			// Recipients should be comma separated, but android devices separate
3627
			// them with semicolon, hence the additional processing
3628
			if (count($recip) === 1 && strpos($recip[0], ';') !== false) {
3629
				$emails = explode(';', $recip[0]);
3630
			}
3631
3632
			foreach ($emails as $email) {
3633
				$extEmail = $this->extractEmailAddress($email);
3634
				if ($extEmail !== false) {
3635
					$r = $this->createMapiRecipient($extEmail, $type);
3636
					$recips[] = $r;
3637
				}
3638
			}
3639
		}
3640
	}
3641
3642
	/**
3643
	 * Creates a MAPI recipient to use with mapi_message_modifyrecipients().
3644
	 *
3645
	 * @param string $email
3646
	 * @param int    $type
3647
	 *
3648
	 * @return array
3649
	 */
3650
	private function createMapiRecipient($email, $type) {
3651
		// Open address book for user resolve
3652
		$addrbook = $this->getAddressbook();
3653
		$recip = [];
3654
		$recip[PR_EMAIL_ADDRESS] = $email;
0 ignored issues
show
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3655
		$recip[PR_SMTP_ADDRESS] = $email;
0 ignored issues
show
Bug introduced by
The constant PR_SMTP_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3656
3657
		// lookup information in GAB if possible so we have up-to-date name for given address
3658
		$userinfo = [[PR_DISPLAY_NAME => $recip[PR_EMAIL_ADDRESS]]];
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3659
		$userinfo = mapi_ab_resolvename($addrbook, $userinfo, EMS_AB_ADDRESS_LOOKUP);
0 ignored issues
show
Bug introduced by
The constant EMS_AB_ADDRESS_LOOKUP was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3660
		if (mapi_last_hresult() == NOERROR) {
0 ignored issues
show
Bug introduced by
The constant NOERROR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3661
			$recip[PR_DISPLAY_NAME] = $userinfo[0][PR_DISPLAY_NAME];
3662
			$recip[PR_EMAIL_ADDRESS] = $userinfo[0][PR_EMAIL_ADDRESS];
3663
			$recip[PR_SEARCH_KEY] = $userinfo[0][PR_SEARCH_KEY];
0 ignored issues
show
Bug introduced by
The constant PR_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3664
			$recip[PR_ADDRTYPE] = $userinfo[0][PR_ADDRTYPE];
0 ignored issues
show
Bug introduced by
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3665
			$recip[PR_ENTRYID] = $userinfo[0][PR_ENTRYID];
3666
			$recip[PR_RECIPIENT_TYPE] = $type;
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3667
		}
3668
		else {
3669
			$recip[PR_DISPLAY_NAME] = $email;
3670
			$recip[PR_SEARCH_KEY] = "SMTP:" . $recip[PR_EMAIL_ADDRESS] . "\0";
3671
			$recip[PR_ADDRTYPE] = "SMTP";
3672
			$recip[PR_RECIPIENT_TYPE] = $type;
3673
			$recip[PR_ENTRYID] = mapi_createoneoff($recip[PR_DISPLAY_NAME], $recip[PR_ADDRTYPE], $recip[PR_EMAIL_ADDRESS]);
3674
		}
3675
3676
		return $recip;
3677
	}
3678
}
3679