MAPIProvider::setEmailAddress()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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

135
			$this->getRecurrence($mapimessage, $messageprops, $message, $message->recurrence, /** @scrutinizer ignore-type */ false, $taskproperties);
Loading history...
136
		}
137
138
		// when set the task to complete using the WebAccess, the dateComplete property is not set correctly
139
		if ($message->complete == 1 && !isset($message->datecompleted)) {
140
			$message->datecompleted = time();
141
		}
142
143
		// if no reminder is set, announce that to the mobile
144
		if (!isset($message->reminderset)) {
145
			$message->reminderset = 0;
146
		}
147
148
		return $message;
149
	}
150
151
	/**
152
	 * Reads an appointment object from MAPI.
153
	 *
154
	 * @param mixed             $mapimessage
155
	 * @param ContentParameters $contentparameters
156
	 *
157
	 * @return SyncAppointment
158
	 */
159
	private function getAppointment($mapimessage, $contentparameters) {
160
		$message = new SyncAppointment();
161
162
		// Standard one-to-one mappings first
163
		$this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetAppointmentMapping());
164
165
		// Appointment specific props
166
		$appointmentprops = MAPIMapping::GetAppointmentProperties();
167
		$messageprops = $this->getProps($mapimessage, $appointmentprops);
168
169
		// set the body according to contentparameters and supported AS version
170
		$this->setMessageBody($mapimessage, $contentparameters, $message);
171
172
		// Set reminder time if reminderset is true
173
		if (isset($messageprops[$appointmentprops["reminderset"]]) && $messageprops[$appointmentprops["reminderset"]] == true) {
174
			if (!isset($messageprops[$appointmentprops["remindertime"]]) || $messageprops[$appointmentprops["remindertime"]] == 0x5AE980E1) {
175
				$message->reminder = 15;
176
			}
177
			else {
178
				$message->reminder = $messageprops[$appointmentprops["remindertime"]];
179
			}
180
		}
181
182
		if (!isset($message->uid)) {
183
			$message->uid = bin2hex((string) $messageprops[$appointmentprops["sourcekey"]]);
184
		}
185
		else {
186
			// if no embedded vCal-Uid is found use hexed GOID
187
			$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

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

516
				$exception->exceptionstarttime = $exception->instanceid = $this->getGMTTimeByTZ(/** @scrutinizer ignore-type */ $this->getDayStartOfTimestamp($change["basedate"]) + $recurrence->recur["startocc"] * 60, $tz);
Loading history...
517
518
				// open body because getting only property might not work because of memory limit
519
				$exceptionatt = $recurrence->getExceptionAttachment($change["basedate"]);
520
				if ($exceptionatt) {
521
					$exceptionobj = mapi_attach_openobj($exceptionatt, 0);
522
					$this->setMessageBodyForType($exceptionobj, SYNC_BODYPREFERENCE_PLAIN, $exception);
523
					if (Request::GetProtocolVersion() >= 16.0) {
524
						// add attachment
525
						$data = mapi_message_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...
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

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

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

613
		if (($contentparameters->/** @scrutinizer ignore-call */ GetMimeSupport() == SYNC_MIMESUPPORT_NEVER) ||
Loading history...
614
				($key = array_search(SYNC_BODYPREFERENCE_MIME, $contentparameters->GetBodyPreference()) === false) ||
0 ignored issues
show
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

614
				($key = array_search(SYNC_BODYPREFERENCE_MIME, /** @scrutinizer ignore-type */ $contentparameters->GetBodyPreference()) === false) ||
Loading history...
Unused Code introduced by
The assignment to $key is dead and can be removed.
Loading history...
615
				$bpReturnType != SYNC_BODYPREFERENCE_MIME) {
616
			MAPIUtils::ParseSmime($this->session, $this->store, $this->getAddressbook(), $mapimessage);
0 ignored issues
show
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

616
			MAPIUtils::ParseSmime(/** @scrutinizer ignore-type */ $this->session, $this->store, $this->getAddressbook(), $mapimessage);
Loading history...
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

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

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

1515
		$localstart = $this->getLocaltimeByTZ($appointment->starttime, /** @scrutinizer ignore-type */ $tz);
Loading history...
1516
		$localend = $this->getLocaltimeByTZ($appointment->endtime, $tz);
1517
		$duration = ($localend - $localstart) / 60;
1518
1519
		// nokia sends an yearly event with 0 mins duration but as all day event,
1520
		// so make it end next day
1521
		if ($appointment->starttime == $appointment->endtime && $isAllday) {
1522
			$duration = 1440;
1523
			$appointment->endtime = $appointment->starttime + 24 * 60 * 60;
1524
			$localend = $localstart + 24 * 60 * 60;
1525
		}
1526
1527
		// use clientUID if set
1528
		if ($appointment->clientuid && !$appointment->uid) {
1529
			$appointment->uid = $appointment->clientuid;
1530
			// Facepalm: iOS sends weird ids (without dashes and a trailing null character)
1531
			if (strlen($appointment->uid) == 33) {
1532
				$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

1532
				$appointment->uid = vsprintf('%s%s-%s-%s-%s-%s%s%s', /** @scrutinizer ignore-type */ str_split($appointment->uid, 4));
Loading history...
1533
			}
1534
		}
1535
		// is the transmitted UID OL compatible?
1536
		if ($appointment->uid && !str_starts_with($appointment->uid, "040000008200E000")) {
1537
			// if not, encapsulate the transmitted uid
1538
			$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

1538
			$appointment->uid = /** @scrutinizer ignore-call */ getGoidFromUid($appointment->uid);
Loading history...
1539
		}
1540
		// if there was a clientuid transport the new UID to the response
1541
		if ($appointment->clientuid) {
1542
			$response->uid = bin2hex($appointment->uid);
1543
			$response->hasResponse = true;
1544
		}
1545
1546
		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...
1547
1548
		$this->setPropsInMAPI($mapimessage, $appointment, $appointmentmapping);
1549
1550
		// appointment specific properties to be set
1551
		$props = [];
1552
1553
		// sensitivity is not enough to mark an appointment as private, so we use another mapi tag
1554
		$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...
1555
1556
		// Set commonstart/commonend to start/end and remindertime to start, duration, private and cleanGlobalObjectId
1557
		$props[$appointmentprops["commonstart"]] = $appointment->starttime;
1558
		$props[$appointmentprops["commonend"]] = $appointment->endtime;
1559
		$props[$appointmentprops["reminderstart"]] = $appointment->starttime;
1560
		// Set reminder boolean to 'true' if reminder is set
1561
		$props[$appointmentprops["reminderset"]] = isset($appointment->reminder) ? true : false;
1562
		$props[$appointmentprops["duration"]] = $duration;
1563
		$props[$appointmentprops["private"]] = $private;
1564
		$props[$appointmentprops["uid"]] = $appointment->uid;
1565
		// Set named prop 8510, unknown property, but enables deleting a single occurrence of a recurring
1566
		// type in OLK2003.
1567
		$props[$appointmentprops["sideeffects"]] = 369;
1568
1569
		if (isset($appointment->reminder) && $appointment->reminder >= 0) {
1570
			// Set 'flagdueby' to correct value (start - reminderminutes)
1571
			$props[$appointmentprops["flagdueby"]] = $appointment->starttime - $appointment->reminder * 60;
1572
			$props[$appointmentprops["remindertime"]] = $appointment->reminder;
1573
		}
1574
		// unset the reminder
1575
		else {
1576
			$props[$appointmentprops["reminderset"]] = false;
1577
		}
1578
1579
		if (isset($appointment->asbody)) {
1580
			$this->setASbody($appointment->asbody, $props, $appointmentprops);
1581
		}
1582
1583
		if (isset($appointment->location2)) {
1584
			$this->setASlocation($appointment->location2, $props, $appointmentprops);
1585
		}
1586
		if ($tz !== false) {
1587
			if (!($isAs16 && $isAllday)) {
1588
				$props[$appointmentprops["timezonetag"]] = $this->getMAPIBlobFromTZ($tz);
1589
			}
1590
		}
1591
1592
		if (isset($appointment->recurrence)) {
1593
			// Set PR_ICON_INDEX to 1025 to show correct icon in category view
1594
			$props[$appointmentprops["icon"]] = 1025;
1595
1596
			// if there aren't any exceptions, use the 'old style' set recurrence
1597
			$noexceptions = true;
1598
1599
			$recurrence = new Recurrence($this->store, $mapimessage);
1600
			$recur = [];
1601
			$this->setRecurrence($appointment, $recur);
1602
1603
			// set the recurrence type to that of the MAPI
1604
			$props[$appointmentprops["recurrencetype"]] = $recur["recurrencetype"];
1605
1606
			$starttime = $this->gmtime($localstart);
1607
			$endtime = $this->gmtime($localend);
0 ignored issues
show
Bug introduced by
It seems like $localend can also be of type integer; however, parameter $time of MAPIProvider::gmtime() does only seem to accept long, maybe add an additional type check? ( Ignorable by Annotation )

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

1607
			$endtime = $this->gmtime(/** @scrutinizer ignore-type */ $localend);
Loading history...
Unused Code introduced by
The assignment to $endtime is dead and can be removed.
Loading history...
1608
1609
			// set recurrence start here because it's calculated differently for tasks and appointments
1610
			$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

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

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

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

2444
		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...
2445
			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...
2446
		}
2447
2448
		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...
2449
	}
2450
2451
	/**
2452
	 * Returns TRUE if it is the summer and therefore DST is in effect.
2453
	 *
2454
	 * @param long  $localtime
2455
	 * @param array $tz
2456
	 *
2457
	 * @return bool
2458
	 */
2459
	private function isDST($localtime, $tz) {
2460
		if (!isset($tz) || !is_array($tz) ||
2461
			!isset($tz["dstbias"]) || $tz["dstbias"] == 0 ||
2462
			!isset($tz["dststartmonth"]) || $tz["dststartmonth"] == 0 ||
2463
			!isset($tz["dstendmonth"]) || $tz["dstendmonth"] == 0) {
2464
			return false;
2465
		}
2466
2467
		$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

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

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

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

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

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