MAPIProvider::setEmail()   F
last analyzed

Complexity

Conditions 27
Paths 2448

Size

Total Lines 126
Code Lines 80

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 27
eloc 80
c 4
b 0
f 0
nc 2448
nop 2
dl 0
loc 126
rs 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

534
				$exception->exceptionstarttime = $exception->instanceid = $this->getGMTTimeByTZ(/** @scrutinizer ignore-type */ $this->getDayStartOfTimestamp($change["basedate"]) + $recurrence->recur["startocc"] * 60, $tz);
Loading history...
535
536
				// open body because getting only property might not work because of memory limit
537
				$exceptionatt = $recurrence->getExceptionAttachment($change["basedate"]);
538
				if ($exceptionatt) {
539
					$exceptionobj = mapi_attach_openobj($exceptionatt, 0);
540
					$this->setMessageBodyForType($exceptionobj, SYNC_BODYPREFERENCE_PLAIN, $exception);
541
					if (Request::GetProtocolVersion() >= 16.0) {
542
						// add attachment
543
						$data = mapi_message_getprops($mapimessage, [PR_ENTRYID, PR_PARENT_SOURCE_KEY]);
0 ignored issues
show
Bug introduced by
The function mapi_message_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

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

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

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

632
				($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...
633
				$bpReturnType != SYNC_BODYPREFERENCE_MIME) {
634
			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

634
			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

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

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

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

1544
		$localstart = $this->getLocaltimeByTZ($appointment->starttime, /** @scrutinizer ignore-type */ $tz);
Loading history...
1545
		$localend = $this->getLocaltimeByTZ($appointment->endtime, $tz);
1546
		$duration = ($localend - $localstart) / 60;
1547
1548
		// nokia sends an yearly event with 0 mins duration but as all day event,
1549
		// so make it end next day
1550
		if ($appointment->starttime == $appointment->endtime && $isAllday) {
1551
			$duration = 1440;
1552
			$appointment->endtime = $appointment->starttime + 24 * 60 * 60;
1553
			$localend = $localstart + 24 * 60 * 60;
1554
		}
1555
1556
		// use clientUID if set
1557
		if ($appointment->clientuid && !$appointment->uid) {
1558
			$appointment->uid = $appointment->clientuid;
1559
			// Facepalm: iOS sends weird ids (without dashes and a trailing null character)
1560
			if (strlen($appointment->uid) == 33) {
1561
				$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

1561
				$appointment->uid = vsprintf('%s%s-%s-%s-%s-%s%s%s', /** @scrutinizer ignore-type */ str_split($appointment->uid, 4));
Loading history...
1562
			}
1563
		}
1564
		// is the transmitted UID OL compatible?
1565
		if ($appointment->uid && substr($appointment->uid, 0, 16) != "040000008200E000") {
1566
			// if not, encapsulate the transmitted uid
1567
			$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

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

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

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

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

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

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

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

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

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

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