MAPIProvider::setMessageBody()   C
last analyzed

Complexity

Conditions 17
Paths 32

Size

Total Lines 66
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 38
nop 3
dl 0
loc 66
rs 5.2166
c 0
b 0
f 0
nc 32

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

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

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

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

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

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

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

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

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

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

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