Test Failed
Push — master ( 5ef0bf...1ac602 )
by
unknown
08:18
created

Meetingrequest::epochToMapiFileTime()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nop 1
dl 0
loc 4
rs 10
nc 1
1
<?php
2
/*
3
 * SPDX-License-Identifier: AGPL-3.0-only
4
 * SPDX-FileCopyrightText: Copyright 2005-2016 Zarafa Deutschland GmbH
5
 * SPDX-FileCopyrightText: Copyright 2020-2022 grommunio GmbH
6
 */
7
8
class Meetingrequest {
9
	/*
10
	 * NOTE
11
	 *
12
	 * This class is designed to modify and update meeting request properties
13
	 * and to search for linked appointments in the calendar. It does not
14
	 * - set standard properties like subject or location
15
	 * - commit property changes through savechanges() (except in accept() and decline())
16
	 *
17
	 * To set all the other properties, just handle the item as any other appointment
18
	 * item. You aren't even required to set those properties before or after using
19
	 * this class. If you update properties before REsending a meeting request (ie with
20
	 * a time change) you MUST first call updateMeetingRequest() so the internal counters
21
	 * can be updated. You can then submit the message any way you like.
22
	 *
23
	 */
24
25
	/*
26
	 * How to use
27
	 * ----------
28
	 *
29
	 * Sending a meeting request:
30
	 * - Create appointment item as normal, but as 'tentative'
31
	 *   (this is the state of the item when the receiving user has received but
32
	 *    not accepted the item)
33
	 * - Set recipients as normally in e-mails
34
	 * - Create Meetingrequest class instance
35
	 * - Call checkCalendarWriteAccess(), to check for write permissions on calendar folder
36
	 * - Call setMeetingRequest(), this turns on all the meeting request properties in the
37
	 *   calendar item
38
	 * - Call sendMeetingRequest(), this sends a copy of the item with some extra properties
39
	 *
40
	 * Updating a meeting request:
41
	 * - Create Meetingrequest class instance
42
	 * - Call checkCalendarWriteAccess(), to check for write permissions on calendar folder
43
	 * - Call updateMeetingRequest(), this updates the counters
44
	 * - Call checkSignificantChanges(), this will check for significant changes and if needed will clear the
45
	 *   existing recipient responses
46
	 * - Call sendMeetingRequest()
47
	 *
48
	 * Clicking on a an e-mail:
49
	 * - Create Meetingrequest class instance
50
	 * - Check isMeetingRequest(), if true:
51
	 *   - Check isLocalOrganiser(), if true then ignore the message
52
	 *   - Check isInCalendar(), if not call doAccept(true, false, false). This adds the item in your
53
	 *     calendar as tentative without sending a response
54
	 *   - Show Accept, Tentative, Decline buttons
55
	 *   - When the user presses Accept, Tentative or Decline, call doAccept(false, true, true),
56
	 *     doAccept(true, true, true) or doDecline(true) respectively to really accept or decline and
57
	 *     send the response. This will remove the request from your inbox.
58
	 * - Check isMeetingRequestResponse, if true:
59
	 *   - Check isLocalOrganiser(), if not true then ignore the message
60
	 *   - Call processMeetingRequestResponse()
61
	 *     This will update the trackstatus of all recipients, and set the item to 'busy'
62
	 *     when all the recipients have accepted.
63
	 * - Check isMeetingCancellation(), if true:
64
	 *   - Check isLocalOrganiser(), if true then ignore the message
65
	 *   - Check isInCalendar(), if not, then ignore
66
	 *     Call processMeetingCancellation()
67
	 *   - Show 'Remove From Calendar' button to user
68
	 *   - When userpresses button, call doRemoveFromCalendar(), which removes the item from your
69
	 *     calendar and deletes the message
70
	 *
71
	 * Cancelling a meeting request:
72
	 *   - Call doCancelInvitation, which will send cancellation mails to attendees and will remove
73
	 *     meeting object from calendar
74
	 */
75
76
	// All properties for a recipient that are interesting
77
	public $recipprops = [
78
		PR_ENTRYID,
1 ignored issue
show
Bug introduced by
The constant PR_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
79
		PR_DISPLAY_NAME,
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...
80
		PR_EMAIL_ADDRESS,
0 ignored issues
show
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
81
		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...
82
		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...
83
		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...
84
		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...
85
		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...
86
		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...
87
		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...
88
		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...
89
		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...
90
		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...
91
		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...
92
		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...
93
		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...
94
		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...
95
	];
96
97
	/**
98
	 * Indication whether the setting of resources in a Meeting Request is success (false) or if it
99
	 * has failed (integer).
100
	 */
101
	public $errorSetResource;
102
103
	public $proptags;
104
	private $store;
105
	public $message;
106
	private $session;
107
	private $meetingTimeInfo;
108
	private $enableDirectBooking;
109
	private $includesResources;
110
	private $nonAcceptingResources;
111
	private $recipientDisplayname;
112
113
	/**
114
	 * Constructor.
115
	 *
116
	 * Takes a store and a message. The message is an appointment item
117
	 * that should be converted into a meeting request or an incoming
118
	 * e-mail message that is a meeting request.
119
	 *
120
	 * The $session variable is optional, but required if the following features
121
	 * are to be used:
122
	 *
123
	 * - Sending meeting requests for meetings that are not in your own store
124
	 * - Sending meeting requests to resources, resource availability checking and resource freebusy updates
125
	 *
126
	 * @param mixed $store
127
	 * @param mixed $message
128
	 * @param mixed $session
129
	 * @param mixed $enableDirectBooking
130
	 */
131
	public function __construct($store, $message, $session = false, $enableDirectBooking = true) {
132
		$this->store = $store;
133
		$this->message = $message;
134
		$this->session = $session;
135
		// This variable string saves time information for the MR.
136
		$this->meetingTimeInfo = false;
137
		$this->enableDirectBooking = $enableDirectBooking;
138
139
		$properties['goid'] = 'PT_BINARY:PSETID_Meeting:0x3';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$properties was never initialized. Although not strictly required by PHP, it is generally a good practice to add $properties = array(); before regardless.
Loading history...
140
		$properties['goid2'] = 'PT_BINARY:PSETID_Meeting:0x23';
141
		$properties['type'] = 'PT_STRING8:PSETID_Meeting:0x24';
142
		$properties['meetingrecurring'] = 'PT_BOOLEAN:PSETID_Meeting:0x5';
143
		$properties['unknown2'] = 'PT_BOOLEAN:PSETID_Meeting:0xa';
144
		$properties['attendee_critical_change'] = 'PT_SYSTIME:PSETID_Meeting:0x1';
145
		$properties['owner_critical_change'] = 'PT_SYSTIME:PSETID_Meeting:0x1a';
146
		$properties['meetingstatus'] = 'PT_LONG:PSETID_Appointment:0x8217';
147
		$properties['responsestatus'] = 'PT_LONG:PSETID_Appointment:0x8218';
148
		$properties['unknown6'] = 'PT_LONG:PSETID_Meeting:0x4';
149
		$properties['replytime'] = 'PT_SYSTIME:PSETID_Appointment:0x8220';
150
		$properties['usetnef'] = 'PT_BOOLEAN:PSETID_Common:0x8582';
151
		$properties['recurrence_data'] = 'PT_BINARY:PSETID_Appointment:0x8216';
152
		$properties['reminderminutes'] = 'PT_LONG:PSETID_Common:0x8501';
153
		$properties['reminderset'] = 'PT_BOOLEAN:PSETID_Common:0x8503';
154
		$properties['sendasical'] = 'PT_BOOLEAN:PSETID_Appointment:0x8200';
155
		$properties['updatecounter'] = 'PT_LONG:PSETID_Appointment:0x8201';					// AppointmentSequenceNumber
156
		$properties['unknown7'] = 'PT_LONG:PSETID_Appointment:0x8202';
157
		$properties['last_updatecounter'] = 'PT_LONG:PSETID_Appointment:0x8203';			// AppointmentLastSequence
158
		$properties['busystatus'] = 'PT_LONG:PSETID_Appointment:0x8205';
159
		$properties['intendedbusystatus'] = 'PT_LONG:PSETID_Appointment:0x8224';
160
		$properties['start'] = 'PT_SYSTIME:PSETID_Appointment:0x820d';
161
		$properties['responselocation'] = 'PT_STRING8:PSETID_Meeting:0x2';
162
		$properties['location'] = 'PT_STRING8:PSETID_Appointment:0x8208';
163
		$properties['requestsent'] = 'PT_BOOLEAN:PSETID_Appointment:0x8229';		// PidLidFInvited, MeetingRequestWasSent
164
		$properties['startdate'] = 'PT_SYSTIME:PSETID_Appointment:0x820d';
165
		$properties['duedate'] = 'PT_SYSTIME:PSETID_Appointment:0x820e';
166
		$properties['flagdueby'] = 'PT_SYSTIME:PSETID_Common:0x8560';
167
		$properties['commonstart'] = 'PT_SYSTIME:PSETID_Common:0x8516';
168
		$properties['commonend'] = 'PT_SYSTIME:PSETID_Common:0x8517';
169
		$properties['recurring'] = 'PT_BOOLEAN:PSETID_Appointment:0x8223';
170
		$properties['clipstart'] = 'PT_SYSTIME:PSETID_Appointment:0x8235';
171
		$properties['clipend'] = 'PT_SYSTIME:PSETID_Appointment:0x8236';
172
		$properties['start_recur_date'] = 'PT_LONG:PSETID_Meeting:0xD';				// StartRecurTime
173
		$properties['start_recur_time'] = 'PT_LONG:PSETID_Meeting:0xE';				// StartRecurTime
174
		$properties['end_recur_date'] = 'PT_LONG:PSETID_Meeting:0xF';				// EndRecurDate
175
		$properties['end_recur_time'] = 'PT_LONG:PSETID_Meeting:0x10';				// EndRecurTime
176
		$properties['is_exception'] = 'PT_BOOLEAN:PSETID_Meeting:0xA';				// LID_IS_EXCEPTION
177
		$properties['apptreplyname'] = 'PT_STRING8:PSETID_Appointment:0x8230';
178
		// Propose new time properties
179
		$properties['proposed_start_whole'] = 'PT_SYSTIME:PSETID_Appointment:0x8250';
180
		$properties['proposed_end_whole'] = 'PT_SYSTIME:PSETID_Appointment:0x8251';
181
		$properties['proposed_duration'] = 'PT_LONG:PSETID_Appointment:0x8256';
182
		$properties['counter_proposal'] = 'PT_BOOLEAN:PSETID_Appointment:0x8257';
183
		$properties['recurring_pattern'] = 'PT_STRING8:PSETID_Appointment:0x8232';
184
		$properties['basedate'] = 'PT_SYSTIME:PSETID_Appointment:0x8228';
185
		$properties['meetingtype'] = 'PT_LONG:PSETID_Meeting:0x26';
186
		$properties['timezone_data'] = 'PT_BINARY:PSETID_Appointment:0x8233';
187
		$properties['timezone'] = 'PT_STRING8:PSETID_Appointment:0x8234';
188
		$properties['categories'] = 'PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords';
189
		$properties['private'] = 'PT_BOOLEAN:PSETID_Common:0x8506';
190
		$properties['alldayevent'] = 'PT_BOOLEAN:PSETID_Appointment:0x8215';
191
		$properties['toattendeesstring'] = 'PT_STRING8:PSETID_Appointment:0x823B';
192
		$properties['ccattendeesstring'] = 'PT_STRING8:PSETID_Appointment:0x823C';
193
194
		$this->proptags = getPropIdsFromStrings($store, $properties);
195
	}
196
197
	/**
198
	 * Sets the direct booking property. This is an alternative to the setting of the direct booking
199
	 * property through the constructor. However, setting it in the constructor is preferred.
200
	 *
201
	 * @param bool $directBookingSetting
202
	 */
203
	public function setDirectBooking($directBookingSetting) {
204
		$this->enableDirectBooking = $directBookingSetting;
205
	}
206
207
	/**
208
	 * Returns TRUE if the message pointed to is an incoming meeting request and should
209
	 * therefore be replied to with doAccept or doDecline().
210
	 *
211
	 * @param string $messageClass message class to use for checking
212
	 *
213
	 * @return bool returns true if this is a meeting request else false
214
	 */
215
	public function isMeetingRequest($messageClass = false) {
216
		if ($messageClass === false) {
217
			$props = mapi_getprops($this->message, [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...
218
			$messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false;
219
		}
220
221
		if ($messageClass !== false && stripos($messageClass, 'ipm.schedule.meeting.request') === 0) {
222
			return true;
223
		}
224
225
		return false;
226
	}
227
228
	/**
229
	 * Returns TRUE if the message pointed to is a returning meeting request response.
230
	 *
231
	 * @param string $messageClass message class to use for checking
232
	 *
233
	 * @return bool returns true if this is a meeting request else false
234
	 */
235
	public function isMeetingRequestResponse($messageClass = false) {
236
		if ($messageClass === false) {
237
			$props = mapi_getprops($this->message, [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...
238
			$messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false;
239
		}
240
241
		if ($messageClass !== false && stripos($messageClass, 'ipm.schedule.meeting.resp') === 0) {
242
			return true;
243
		}
244
245
		return false;
246
	}
247
248
	/**
249
	 * Returns TRUE if the message pointed to is a cancellation request.
250
	 *
251
	 * @param string $messageClass message class to use for checking
252
	 *
253
	 * @return bool returns true if this is a meeting request else false
254
	 */
255
	public function isMeetingCancellation($messageClass = false) {
256
		if ($messageClass === false) {
257
			$props = mapi_getprops($this->message, [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...
258
			$messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false;
259
		}
260
261
		if ($messageClass !== false && stripos($messageClass, 'ipm.schedule.meeting.canceled') === 0) {
262
			return true;
263
		}
264
265
		return false;
266
	}
267
268
	/**
269
	 * Function is used to get the last update counter of meeting request.
270
	 *
271
	 * @return bool|Number false when last_updatecounter not found else return last_updatecounter
272
	 */
273
	public function getLastUpdateCounter() {
274
		$calendarItemProps = mapi_getprops($this->message, [$this->proptags['last_updatecounter']]);
275
		if (isset($calendarItemProps) && !empty($calendarItemProps)) {
276
			return $calendarItemProps[$this->proptags['last_updatecounter']];
277
		}
278
279
		return false;
280
	}
281
282
	/**
283
	 * Process an incoming meeting request response. This updates the appointment
284
	 * in your calendar to show whether the user has accepted or declined.
285
	 */
286
	public function processMeetingRequestResponse() {
287
		if (!$this->isMeetingRequestResponse()) {
288
			return;
289
		}
290
291
		if (!$this->isLocalOrganiser()) {
292
			return;
293
		}
294
295
		// Get information we need from the response message
296
		$messageprops = mapi_getprops($this->message, [
297
			$this->proptags['goid'],
298
			$this->proptags['goid2'],
299
			PR_OWNER_APPT_ID,
0 ignored issues
show
Bug introduced by
The constant PR_OWNER_APPT_ID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
300
			PR_SENT_REPRESENTING_EMAIL_ADDRESS,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
301
			PR_SENT_REPRESENTING_NAME,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
302
			PR_SENT_REPRESENTING_ADDRTYPE,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
303
			PR_SENT_REPRESENTING_ENTRYID,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
304
			PR_SENT_REPRESENTING_SEARCH_KEY,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
305
			PR_MESSAGE_DELIVERY_TIME,
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_DELIVERY_TIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
306
			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...
307
			PR_PROCESSED,
0 ignored issues
show
Bug introduced by
The constant PR_PROCESSED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
308
			PR_RCVD_REPRESENTING_ENTRYID,
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
309
			$this->proptags['proposed_start_whole'],
310
			$this->proptags['proposed_end_whole'],
311
			$this->proptags['proposed_duration'],
312
			$this->proptags['counter_proposal'],
313
			$this->proptags['attendee_critical_change'],
314
		]);
315
316
		$goid2 = $messageprops[$this->proptags['goid2']];
317
318
		if (!isset($goid2) || !isset($messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS])) {
319
			return;
320
		}
321
322
		// Find basedate in GlobalID(0x3), this can be a response for an occurrence
323
		$basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
324
325
		// check if delegate is processing the response
326
		if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) {
327
			$delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [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...
328
			$userStore = $delegatorStore['store'];
329
		}
330
		else {
331
			$userStore = $this->store;
332
		}
333
334
		// check for calendar access
335
		if ($this->checkCalendarWriteAccess($userStore) !== true) {
336
			// Throw an exception that we don't have write permissions on calendar folder,
337
			// allow caller to fill the error message
338
			throw new MAPIException(null, MAPI_E_NO_ACCESS);
339
		}
340
341
		$calendarItem = $this->getCorrespondentCalendarItem(true);
342
343
		// Open the calendar items, and update all the recipients of the calendar item that match
344
		// the email address of the response.
345
		if ($calendarItem !== false) {
0 ignored issues
show
introduced by
The condition $calendarItem !== false is always true.
Loading history...
346
			$this->processResponse($userStore, $calendarItem, $basedate, $messageprops);
0 ignored issues
show
Bug introduced by
It seems like $userStore can also be of type MAPIStore; however, parameter $store of Meetingrequest::processResponse() does only seem to accept resource, 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

346
			$this->processResponse(/** @scrutinizer ignore-type */ $userStore, $calendarItem, $basedate, $messageprops);
Loading history...
347
		}
348
	}
349
350
	/**
351
	 * Process every incoming MeetingRequest response.This updates the appointment
352
	 * in your calendar to show whether the user has accepted or declined.
353
	 *
354
	 * @param resource    $store        contains the userStore in which the meeting is created
355
	 * @param MAPIMessage $calendarItem resource of the calendar item for which this response has arrived
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...
356
	 * @param bool        $basedate     if present the create an exception
357
	 * @param array       $messageprops contains message properties
358
	 */
359
	public function processResponse($store, $calendarItem, $basedate, $messageprops) {
360
		$senderentryid = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
361
		$messageclass = $messageprops[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...
362
		$deliverytime = $messageprops[PR_MESSAGE_DELIVERY_TIME];
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_DELIVERY_TIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
363
364
		// Open the calendar item, find the sender in the recipient table and update all the recipients of the calendar item that match
365
		// the email address of the response.
366
		$calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['recurring'], PR_STORE_ENTRYID, PR_PARENT_ENTRYID, PR_ENTRYID, $this->proptags['updatecounter']]);
1 ignored issue
show
Bug introduced by
The constant PR_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_STORE_ENTRYID 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...
367
368
		// check if meeting response is already processed
369
		if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
0 ignored issues
show
Bug introduced by
The constant PR_PROCESSED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
370
			// meeting is already processed
371
			return;
372
		}
373
		mapi_setprops($this->message, [PR_PROCESSED => true]);
374
		mapi_savechanges($this->message);
375
376
		// if meeting is updated in organizer's calendar then we don't need to process
377
		// old response
378
		if ($this->isMeetingUpdated($basedate)) {
0 ignored issues
show
Bug introduced by
$basedate of type boolean is incompatible with the type double|integer expected by parameter $basedate of Meetingrequest::isMeetingUpdated(). ( Ignorable by Annotation )

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

378
		if ($this->isMeetingUpdated(/** @scrutinizer ignore-type */ $basedate)) {
Loading history...
379
			return;
380
		}
381
382
		// If basedate is found, then create/modify exception msg and do processing
383
		if ($basedate && isset($calendarItemProps[$this->proptags['recurring']]) && $calendarItemProps[$this->proptags['recurring']] === true) {
384
			$recurr = new Recurrence($store, $calendarItem);
0 ignored issues
show
Bug introduced by
$calendarItem of type MAPIMessage is incompatible with the type resource expected by parameter $message of Recurrence::__construct(). ( Ignorable by Annotation )

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

384
			$recurr = new Recurrence($store, /** @scrutinizer ignore-type */ $calendarItem);
Loading history...
385
386
			// Copy properties from meeting request
387
			$exception_props = mapi_getprops($this->message, [
388
				PR_OWNER_APPT_ID,
0 ignored issues
show
Bug introduced by
The constant PR_OWNER_APPT_ID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
389
				$this->proptags['proposed_start_whole'],
390
				$this->proptags['proposed_end_whole'],
391
				$this->proptags['proposed_duration'],
392
				$this->proptags['counter_proposal'],
393
			]);
394
395
			// Create/modify exception
396
			if ($recurr->isException($basedate)) {
397
				$recurr->modifyException($exception_props, $basedate);
398
			}
399
			else {
400
				// When we are creating an exception we need copy recipients from main recurring item
401
				$recipTable = mapi_message_getrecipienttable($calendarItem);
402
				$recips = mapi_table_queryallrows($recipTable, $this->recipprops);
403
404
				// Retrieve actual start/due dates from calendar item.
405
				$exception_props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
406
				$exception_props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
407
408
				$recurr->createException($exception_props, $basedate, false, $recips);
0 ignored issues
show
Bug introduced by
$basedate of type true is incompatible with the type date expected by parameter $base_date of Recurrence::createException(). ( Ignorable by Annotation )

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

408
				$recurr->createException($exception_props, /** @scrutinizer ignore-type */ $basedate, false, $recips);
Loading history...
409
			}
410
411
			mapi_savechanges($calendarItem);
412
413
			$attach = $recurr->getExceptionAttachment($basedate);
414
			if ($attach) {
415
				$recurringItem = $calendarItem;
416
				$calendarItem = mapi_attach_openobj($attach, MAPI_MODIFY);
417
			}
418
			else {
419
				return false;
420
			}
421
		}
422
423
		// Get the recipients of the calendar item
424
		$reciptable = mapi_message_getrecipienttable($calendarItem);
425
		$recipients = mapi_table_queryallrows($reciptable, $this->recipprops);
426
427
		// FIXME we should look at the updatecounter property and compare it
428
		// to the counter in the recipient to see if this update is actually
429
		// newer than the status in the calendar item
430
		$found = false;
431
432
		$totalrecips = 0;
433
		$acceptedrecips = 0;
434
		foreach ($recipients as $recipient) {
435
			++$totalrecips;
436
			if (isset($recipient[PR_ENTRYID]) && $this->compareABEntryIDs($recipient[PR_ENTRYID], $senderentryid)) {
437
				$found = true;
438
439
				/*
440
				 * If value of attendee_critical_change on meeting response mail is less than PR_RECIPIENT_TRACKSTATUS_TIME
441
				 * on the corresponding recipientRow of meeting then we ignore this response mail.
442
				 */
443
				if (isset($recipient[PR_RECIPIENT_TRACKSTATUS_TIME]) && ($messageprops[$this->proptags['attendee_critical_change']] < $recipient[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...
444
					continue;
445
				}
446
447
				// The email address matches, update the row
448
				$recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
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...
449
				if (isset($messageprops[$this->proptags['attendee_critical_change']])) {
450
					$recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $messageprops[$this->proptags['attendee_critical_change']];
451
				}
452
453
				// If this is a counter proposal, set the proposal properties in the recipient row
454
				if (isset($messageprops[$this->proptags['counter_proposal']]) && $messageprops[$this->proptags['counter_proposal']]) {
455
					$recipient[PR_RECIPIENT_PROPOSEDSTARTTIME] = $messageprops[$this->proptags['proposed_start_whole']];
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_PROPOSEDSTARTTIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
456
					$recipient[PR_RECIPIENT_PROPOSEDENDTIME] = $messageprops[$this->proptags['proposed_end_whole']];
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_PROPOSEDENDTIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
457
					$recipient[PR_RECIPIENT_PROPOSED] = $messageprops[$this->proptags['counter_proposal']];
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_PROPOSED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
458
				}
459
460
				// Update the recipient information
461
				mapi_message_modifyrecipients($calendarItem, MODRECIP_REMOVE, [$recipient]);
462
				mapi_message_modifyrecipients($calendarItem, MODRECIP_ADD, [$recipient]);
463
			}
464
			if (isset($recipient[PR_RECIPIENT_TRACKSTATUS]) && $recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted) {
465
				++$acceptedrecips;
466
			}
467
		}
468
469
		// If the recipient was not found in the original calendar item,
470
		// then add the recpient as a new optional recipient
471
		if (!$found) {
472
			$recipient = [];
473
			$recipient[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
474
			$recipient[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_EMAIL_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...
475
			$recipient[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_NAME 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...
476
			$recipient[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_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...
Bug introduced by
The constant PR_SENT_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
477
			$recipient[PR_RECIPIENT_TYPE] = MAPI_CC;
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...
478
			$recipient[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_SEARCH_KEY 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...
479
			$recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
480
			$recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $deliverytime;
481
482
			// If this is a counter proposal, set the proposal properties in the recipient row
483
			if (isset($messageprops[$this->proptags['counter_proposal']])) {
484
				$recipient[PR_RECIPIENT_PROPOSEDSTARTTIME] = $messageprops[$this->proptags['proposed_start_whole']];
485
				$recipient[PR_RECIPIENT_PROPOSEDENDTIME] = $messageprops[$this->proptags['proposed_end_whole']];
486
				$recipient[PR_RECIPIENT_PROPOSED] = $messageprops[$this->proptags['counter_proposal']];
487
			}
488
489
			mapi_message_modifyrecipients($calendarItem, MODRECIP_ADD, [$recipient]);
490
			++$totalrecips;
491
			if ($recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted) {
492
				++$acceptedrecips;
493
			}
494
		}
495
496
		// TODO: Update counter proposal number property on message
497
		/*
498
		If it is the first time this attendee has proposed a new date/time, increment the value of the PidLidAppointmentProposalNumber property on the organizer's meeting object, by 0x00000001. If this property did not previously exist on the organizer's meeting object, it MUST be set with a value of 0x00000001.
499
		*/
500
		// If this is a counter proposal, set the counter proposal indicator boolean
501
		if (isset($messageprops[$this->proptags['counter_proposal']])) {
502
			$props = [];
503
			if ($messageprops[$this->proptags['counter_proposal']]) {
504
				$props[$this->proptags['counter_proposal']] = true;
505
			}
506
			else {
507
				$props[$this->proptags['counter_proposal']] = false;
508
			}
509
510
			mapi_setprops($calendarItem, $props);
511
		}
512
513
		mapi_savechanges($calendarItem);
514
		if (isset($attach)) {
515
			mapi_savechanges($attach);
516
			mapi_savechanges($recurringItem);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $recurringItem does not seem to be defined for all execution paths leading up to this point.
Loading history...
517
		}
518
	}
519
520
	/**
521
	 * Process an incoming meeting request cancellation. This updates the
522
	 * appointment in your calendar to show that the meeting has been cancelled.
523
	 */
524
	public function processMeetingCancellation() {
525
		if (!$this->isMeetingCancellation()) {
526
			return;
527
		}
528
529
		if ($this->isLocalOrganiser()) {
530
			return;
531
		}
532
533
		if (!$this->isInCalendar()) {
534
			return;
535
		}
536
537
		$listProperties = $this->proptags;
538
		$listProperties['subject'] = PR_SUBJECT;
0 ignored issues
show
Bug introduced by
The constant PR_SUBJECT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
539
		$listProperties['sent_representing_name'] = PR_SENT_REPRESENTING_NAME;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
540
		$listProperties['sent_representing_address_type'] = PR_SENT_REPRESENTING_ADDRTYPE;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
541
		$listProperties['sent_representing_email_address'] = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
542
		$listProperties['sent_representing_entryid'] = PR_SENT_REPRESENTING_ENTRYID;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
543
		$listProperties['sent_representing_search_key'] = PR_SENT_REPRESENTING_SEARCH_KEY;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
544
		$listProperties['rcvd_representing_name'] = PR_RCVD_REPRESENTING_NAME;
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
545
		$listProperties['rcvd_representing_address_type'] = PR_RCVD_REPRESENTING_ADDRTYPE;
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
546
		$listProperties['rcvd_representing_email_address'] = PR_RCVD_REPRESENTING_EMAIL_ADDRESS;
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
547
		$listProperties['rcvd_representing_entryid'] = PR_RCVD_REPRESENTING_ENTRYID;
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
548
		$listProperties['rcvd_representing_search_key'] = PR_RCVD_REPRESENTING_SEARCH_KEY;
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
549
		$messageProps = mapi_getprops($this->message, $listProperties);
550
551
		$goid = $messageProps[$this->proptags['goid']];	// GlobalID (0x3)
552
		if (!isset($goid)) {
553
			return;
554
		}
555
556
		// get delegator store, if delegate is processing this cancellation
557
		if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) {
558
			$delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID], [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...
559
560
			$store = $delegatorStore['store'];
561
			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
0 ignored issues
show
Unused Code introduced by
The assignment to $calFolder is dead and can be removed.
Loading history...
562
		}
563
		else {
564
			$store = $this->store;
565
			$calFolder = $this->openDefaultCalendar();
566
		}
567
568
		// check for calendar access
569
		if ($this->checkCalendarWriteAccess($store) !== true) {
570
			// Throw an exception that we don't have write permissions on calendar folder,
571
			// allow caller to fill the error message
572
			throw new MAPIException(null, MAPI_E_NO_ACCESS);
573
		}
574
575
		$calendarItem = $this->getCorrespondentCalendarItem(true);
576
		$basedate = $this->getBasedateFromGlobalID($goid);
577
578
		if ($calendarItem !== false) {
0 ignored issues
show
introduced by
The condition $calendarItem !== false is always true.
Loading history...
579
			// if basedate is provided and we could not find the item then it could be that we are processing
580
			// an exception so get the exception and process it
581
			if ($basedate) {
582
				$calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['recurring']]);
583
				if ($calendarItemProps[$this->proptags['recurring']] === true) {
584
					$recurr = new Recurrence($store, $calendarItem);
0 ignored issues
show
Bug introduced by
It seems like $store can also be of type MAPIStore; however, parameter $store of Recurrence::__construct() does only seem to accept resource, 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

584
					$recurr = new Recurrence(/** @scrutinizer ignore-type */ $store, $calendarItem);
Loading history...
Bug introduced by
$calendarItem of type entryid is incompatible with the type resource expected by parameter $message of Recurrence::__construct(). ( Ignorable by Annotation )

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

584
					$recurr = new Recurrence($store, /** @scrutinizer ignore-type */ $calendarItem);
Loading history...
585
586
					// Set message class
587
					$messageProps[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...
588
589
					if ($recurr->isException($basedate)) {
590
						$recurr->modifyException($messageProps, $basedate);
591
					}
592
					else {
593
						$recurr->createException($messageProps, $basedate);
0 ignored issues
show
Bug introduced by
$basedate of type true is incompatible with the type date expected by parameter $base_date of Recurrence::createException(). ( Ignorable by Annotation )

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

593
						$recurr->createException($messageProps, /** @scrutinizer ignore-type */ $basedate);
Loading history...
594
					}
595
				}
596
			}
597
			else {
598
				// set the properties of the cancellation object
599
				mapi_setprops($calendarItem, $messageProps);
600
			}
601
602
			mapi_savechanges($calendarItem);
603
		}
604
	}
605
606
	/**
607
	 * Returns true if the corresponding calendar items exists in the celendar folder for this
608
	 * meeting request/response/cancellation.
609
	 */
610
	public function isInCalendar() {
611
		// @TODO check for deleted exceptions
612
		return $this->getCorrespondentCalendarItem(false) !== false;
613
	}
614
615
	/**
616
	 * Accepts the meeting request by moving the item to the calendar
617
	 * and sending a confirmation message back to the sender. If $tentative
618
	 * is TRUE, then the item is accepted tentatively. After accepting, you
619
	 * can't use this class instance any more. The message is closed. If you
620
	 * specify TRUE for 'move', then the item is actually moved (from your
621
	 * inbox probably) to the calendar. If you don't, it is copied into
622
	 * your calendar.
623
	 *
624
	 * @param bool   $tentative            true if user as tentative accepted the meeting
625
	 * @param bool   $sendresponse         true if a response has to be sent to organizer
626
	 * @param bool   $move                 true if the meeting request should be moved to the deleted items after processing
627
	 * @param string $newProposedStartTime contains starttime if user has proposed other time
628
	 * @param string $newProposedEndTime   contains endtime if user has proposed other time
629
	 * @param string $basedate             start of day of occurrence for which user has accepted the recurrent meeting
630
	 * @param bool   $isImported           true to indicate that MR is imported from .ics or .vcs file else it false.
631
	 * @param mixed  $body
632
	 * @param mixed  $userAction
633
	 * @param mixed  $store
634
	 *
635
	 * @return string $entryid entryid of item which created/updated in calendar
636
	 */
637
	public function doAccept($tentative, $sendresponse, $move, $newProposedStartTime = false, $newProposedEndTime = false, $body = false, $userAction = false, $store = false, $basedate = false, $isImported = false) {
638
		if ($this->isLocalOrganiser()) {
639
			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...
640
		}
641
642
		// Remove any previous calendar items with this goid and appt id
643
		$messageprops = mapi_getprops($this->message, [PR_ENTRYID, PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['updatecounter'], PR_PROCESSED, PR_RCVD_REPRESENTING_ENTRYID, PR_SENDER_ENTRYID, PR_SENT_REPRESENTING_ENTRYID, PR_RECEIVED_BY_ENTRYID]);
1 ignored issue
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_PROCESSED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENDER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_RECEIVED_BY_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
644
645
		// If this meeting request is received by a delegate then open delegator's store.
646
		if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) {
647
			$delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [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...
648
649
			$store = $delegatorStore['store'];
650
			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
651
		}
652
		else {
653
			$calFolder = $this->openDefaultCalendar();
654
			$store = $this->store;
655
		}
656
657
		// check for calendar access
658
		if ($this->checkCalendarWriteAccess($store) !== true) {
659
			// Throw an exception that we don't have write permissions on calendar folder,
660
			// allow caller to fill the error message
661
			throw new MAPIException(null, MAPI_E_NO_ACCESS);
662
		}
663
664
		// if meeting is out dated then don't process it
665
		if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS]) && $this->isMeetingOutOfDate()) {
666
			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...
667
		}
668
669
		/*
670
		 *	if this function is called automatically with meeting request object then there will be
671
		 *	two possibilitites
672
		 *	1) meeting request is opened first time, in this case make a tentative appointment in
673
		 *		recipient's calendar
674
		 *	2) after this every subsequent request to open meeting request will not do any processing
675
		 */
676
		if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS]) && $userAction == false) {
677
			if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
678
				// if meeting request is already processed then don't do anything
679
				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...
680
			}
681
682
			// if correspondent calendar item is already processed then don't do anything
683
			$calendarItem = $this->getCorrespondentCalendarItem();
684
			if ($calendarItem) {
0 ignored issues
show
introduced by
$calendarItem is of type entryid, thus it always evaluated to true.
Loading history...
685
				$calendarItemProps = mapi_getprops($calendarItem, [PR_PROCESSED]);
686
				if (isset($calendarItemProps[PR_PROCESSED]) && $calendarItemProps[PR_PROCESSED] == true) {
687
					// mark meeting-request mail as processed as well
688
					mapi_setprops($this->message, [PR_PROCESSED => true]);
689
					mapi_savechanges($this->message);
690
691
					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...
692
				}
693
			}
694
		}
695
696
		// Retrieve basedate from globalID, if it is not received as argument
697
		if (!$basedate) {
698
			$basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
699
		}
700
701
		// set counter proposal properties in calendar item when proposing new time
702
		$proposeNewTimeProps = [];
703
		if ($newProposedStartTime && $newProposedEndTime) {
704
			$proposeNewTimeProps[$this->proptags['proposed_start_whole']] = $newProposedStartTime;
705
			$proposeNewTimeProps[$this->proptags['proposed_end_whole']] = $newProposedEndTime;
706
			$proposeNewTimeProps[$this->proptags['proposed_duration']] = round($newProposedEndTime - $newProposedStartTime) / 60;
707
			$proposeNewTimeProps[$this->proptags['counter_proposal']] = true;
708
		}
709
710
		// While sender is receiver then we have to process the meeting request as per the intended busy status
711
		// instead of tentative, and accept the same as per the intended busystatus.
712
		$senderEntryId = isset($messageprops[PR_SENT_REPRESENTING_ENTRYID]) ? $messageprops[PR_SENT_REPRESENTING_ENTRYID] : $messageprops[PR_SENDER_ENTRYID];
713
		if (isset($messageprops[PR_RECEIVED_BY_ENTRYID]) && compareEntryIds($senderEntryId, $messageprops[PR_RECEIVED_BY_ENTRYID])) {
714
			$entryid = $this->accept(false, $sendresponse, $move, $proposeNewTimeProps, $body, true, $store, $calFolder, $basedate);
715
		}
716
		else {
717
			$entryid = $this->accept($tentative, $sendresponse, $move, $proposeNewTimeProps, $body, $userAction, $store, $calFolder, $basedate);
718
		}
719
720
		// if we have first time processed this meeting then set PR_PROCESSED property
721
		if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS]) && $userAction === false && $isImported === false) {
722
			if (!isset($messageprops[PR_PROCESSED]) || $messageprops[PR_PROCESSED] != true) {
723
				// set processed flag
724
				mapi_setprops($this->message, [PR_PROCESSED => true]);
725
				mapi_savechanges($this->message);
726
			}
727
		}
728
729
		return $entryid;
730
	}
731
732
	public function accept($tentative, $sendresponse, $move, $proposeNewTimeProps = [], $body = false, $userAction = false, $store, $calFolder, $basedate = false) {
733
		$messageprops = mapi_getprops($this->message);
734
		$isDelegate = isset($messageprops[PR_RCVD_REPRESENTING_NAME]);
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
735
736
		if ($sendresponse) {
737
			$this->createResponse($tentative ? olResponseTentative : olResponseAccepted, $proposeNewTimeProps, $body, $store, $basedate, $calFolder);
738
		}
739
740
		/*
741
		 * Further processing depends on what user is receiving. User can receive recurring item, a single occurrence or a normal meeting.
742
		 * 1) If meeting req is of recurrence then we find all the occurrence in calendar because in past user might have received one or few occurrences.
743
		 * 2) If single occurrence then find occurrence itself using globalID and if item is not found then use cleanGlobalID to find main recurring item
744
		 * 3) Normal meeting req are handled normally as they were handled previously.
745
		 *
746
		 * Also user can respond(accept/decline) to item either from previewpane or from calendar by opening the item. If user is responding the meeting from previewpane
747
		 * and that item is not found in calendar then item is move else item is opened and all properties, attachments and recipient are copied from meeting request.
748
		 * If user is responding from calendar then item is opened and properties are set such as meetingstatus, responsestatus, busystatus etc.
749
		 */
750
		if ($this->isMeetingRequest($messageprops[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...
751
			// While processing the item mark it as read.
752
			mapi_message_setreadflag($this->message, SUPPRESS_RECEIPT);
753
754
			// This meeting request item is recurring, so find all occurrences and saves them all as exceptions to this meeting request item.
755
			if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']] == true) {
756
				$calendarItem = false;
757
758
				// Find main recurring item based on GlobalID (0x3)
759
				$items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
760
				if (is_array($items)) {
761
					foreach ($items as $key => $entryid) {
762
						$calendarItem = mapi_msgstore_openentry($store, $entryid);
763
					}
764
				}
765
766
				$processed = false;
767
				if (!$calendarItem) {
768
					// Recurring item not found, so create new meeting in Calendar
769
					$calendarItem = mapi_folder_createmessage($calFolder);
770
				}
771
				else {
772
					// we have found the main recurring item, check if this meeting request is already processed
773
					if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
0 ignored issues
show
Bug introduced by
The constant PR_PROCESSED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
774
						// only set required properties, other properties are already copied when processing this meeting request
775
						// for the first time
776
						$processed = true;
777
					}
778
				}
779
780
				if (!$processed) {
781
					// get all the properties and copy that to calendar item
782
					$props = mapi_getprops($this->message);
783
					// reset the PidLidMeetingType to Unspecified for outlook display the item
784
					$props[$this->proptags['meetingtype']] = mtgEmpty;
785
					/*
786
					 * the client which has sent this meeting request can generate wrong flagdueby
787
					 * time (mainly OL), so regenerate that property so we will always show reminder
788
					 * on right time
789
					 */
790
					if (isset($props[$this->proptags['reminderminutes']])) {
791
						$props[$this->proptags['flagdueby']] = $props[$this->proptags['startdate']] - ($props[$this->proptags['reminderminutes']] * 60);
792
					}
793
				}
794
				else {
795
					// only get required properties so we will not overwrite existing updated properties from calendar
796
					$props = mapi_getprops($this->message, [PR_ENTRYID]);
1 ignored issue
show
Bug introduced by
The constant PR_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
797
				}
798
799
				// While we applying updates of MR then all local categories will be removed,
800
				// So get the local categories of all occurrence before applying update from organiser.
801
				$localCategories = $this->getLocalCategories($calendarItem, $store, $calFolder);
802
803
				$props[PR_MESSAGE_CLASS] = 'IPM.Appointment';
804
				// When meeting requests are generated by third-party solutions, we might be missing the updatecounter property.
805
				if (!isset($props[$this->proptags['updatecounter']])) {
806
					$props[$this->proptags['updatecounter']] = 0;
807
				}
808
				$props[$this->proptags['meetingstatus']] = olMeetingReceived;
809
				// when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
810
				$props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
811
812
				if (isset($props[$this->proptags['intendedbusystatus']])) {
813
					if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
814
						$props[$this->proptags['busystatus']] = fbTentative;
815
					}
816
					else {
817
						$props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
818
					}
819
					// we already have intendedbusystatus value in $props so no need to copy it
820
				}
821
				else {
822
					$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
823
				}
824
825
				if ($userAction) {
826
					$addrInfo = $this->getOwnerAddress($this->store);
827
828
					// if user has responded then set replytime and name
829
					$props[$this->proptags['replytime']] = time();
830
					if (!empty($addrInfo)) {
831
						// @FIXME conditionally set this property only for delegation case
832
						$props[$this->proptags['apptreplyname']] = $addrInfo[0];
833
					}
834
				}
835
836
				mapi_setprops($calendarItem, $props);
837
838
				// we have already processed attachments and recipients, so no need to do it again
839
				if (!$processed) {
840
					// Copy attachments too
841
					$this->replaceAttachments($this->message, $calendarItem);
842
					// Copy recipients too
843
					$this->replaceRecipients($this->message, $calendarItem, $isDelegate);
844
				}
845
846
				// Find all occurrences based on CleanGlobalID (0x23)
847
				// there will be no exceptions left if $processed is true, but even if it doesn't hurt to recheck
848
				$items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
849
				if (is_array($items)) {
850
					// Save all existing occurrence as exceptions
851
					foreach ($items as $entryid) {
852
						// Open occurrence
853
						$occurrenceItem = mapi_msgstore_openentry($store, $entryid);
854
855
						// Save occurrence into main recurring item as exception
856
						if ($occurrenceItem) {
857
							$occurrenceItemProps = mapi_getprops($occurrenceItem, [$this->proptags['goid'], $this->proptags['recurring']]);
858
859
							// Find basedate of occurrence item
860
							$basedate = $this->getBasedateFromGlobalID($occurrenceItemProps[$this->proptags['goid']]);
861
							if ($basedate && $occurrenceItemProps[$this->proptags['recurring']] != true) {
862
								$this->mergeException($calendarItem, $occurrenceItem, $basedate, $store);
0 ignored issues
show
Bug introduced by
$basedate of type true is incompatible with the type string expected by parameter $basedate of Meetingrequest::mergeException(). ( Ignorable by Annotation )

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

862
								$this->mergeException($calendarItem, $occurrenceItem, /** @scrutinizer ignore-type */ $basedate, $store);
Loading history...
863
							}
864
						}
865
					}
866
				}
867
868
				mapi_savechanges($calendarItem);
869
870
				// After applying update of organiser all local categories of occurrence was removed,
871
				// So if local categories exist then apply it on respective occurrence.
872
				if (!empty($localCategories)) {
873
					$this->applyLocalCategories($calendarItem, $store, $localCategories);
874
				}
875
876
				if ($move) {
877
					// open wastebasket of currently logged in user and move the meeting request to it
878
					// for delegates this will be delegate's wastebasket folder
879
					$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
0 ignored issues
show
Bug introduced by
It seems like $this->openDefaultStore() can also be of type false; however, parameter $store of Meetingrequest::openDefaultWastebasket() does only seem to accept MAPIStore, 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

879
					$wastebasket = $this->openDefaultWastebasket(/** @scrutinizer ignore-type */ $this->openDefaultStore());
Loading history...
880
					mapi_folder_copymessages($calFolder, [$props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
881
				}
882
883
				$entryid = $props[PR_ENTRYID];
884
			}
885
			else {
886
				/**
887
				 * This meeting request is not recurring, so can be an exception or normal meeting.
888
				 * If exception then find main recurring item and update exception
889
				 * If main recurring item is not found then put exception into Calendar as normal meeting.
890
				 */
891
				$calendarItem = false;
892
893
				// We found basedate in GlobalID of this meeting request, so this meeting request if for an occurrence.
894
				if ($basedate) {
895
					// Find main recurring item from CleanGlobalID of this meeting request
896
					$items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
897
					if (is_array($items)) {
898
						foreach ($items as $key => $entryid) {
899
							$calendarItem = mapi_msgstore_openentry($store, $entryid);
900
						}
901
					}
902
903
					// Main recurring item is found, so now update exception
904
					if ($calendarItem) {
905
						$this->acceptException($calendarItem, $this->message, $basedate, $move, $tentative, $userAction, $store, $isDelegate);
906
						$calendarItemProps = mapi_getprops($calendarItem, [PR_ENTRYID]);
907
						$entryid = $calendarItemProps[PR_ENTRYID];
908
					}
909
				}
910
911
				if (!$calendarItem) {
912
					$items = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
913
					if (is_array($items)) {
914
						// Get local categories before deleting MR.
915
						$message = mapi_msgstore_openentry($store, $items[0]);
916
						$localCategories = mapi_getprops($message, [$this->proptags['categories']]);
917
						mapi_folder_deletemessages($calFolder, $items);
918
					}
919
920
					if ($move) {
921
						// All we have to do is open the default calendar,
922
						// set the message class correctly to be an appointment item
923
						// and move it to the calendar folder
924
						$sourcefolder = $this->openParentFolder();
925
926
						// create a new calendar message, and copy the message to there,
927
						// since we want to delete (move to wastebasket) the original message
928
						$old_entryid = mapi_getprops($this->message, [PR_ENTRYID]);
929
						$calmsg = mapi_folder_createmessage($calFolder);
930
						mapi_copyto($this->message, [], [], $calmsg); /* includes attachments and recipients */
931
						// reset the PidLidMeetingType to Unspecified for outlook display the item
932
						$tmp_props = [];
933
						$tmp_props[$this->proptags['meetingtype']] = mtgEmpty;
934
						// OL needs this field always being set, or it will not display item
935
						$tmp_props[$this->proptags['recurring']] = false;
936
						mapi_setprops($calmsg, $tmp_props);
937
938
						// After creating new MR, If local categories exist then apply it on new MR.
939
						if (!empty($localCategories)) {
940
							mapi_setprops($calmsg, $localCategories);
941
						}
942
943
						$calItemProps = [];
944
						$calItemProps[PR_MESSAGE_CLASS] = 'IPM.Appointment';
945
946
						/*
947
						 * the client which has sent this meeting request can generate wrong flagdueby
948
						 * time (mainly OL), so regenerate that property so we will always show reminder
949
						 * on right time
950
						 */
951
						if (isset($messageprops[$this->proptags['reminderminutes']])) {
952
							$calItemProps[$this->proptags['flagdueby']] = $messageprops[$this->proptags['startdate']] - ($messageprops[$this->proptags['reminderminutes']] * 60);
953
						}
954
955
						if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
956
							if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
957
								$calItemProps[$this->proptags['busystatus']] = fbTentative;
958
							}
959
							else {
960
								$calItemProps[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
961
							}
962
							$calItemProps[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
963
						}
964
						else {
965
							$calItemProps[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
966
						}
967
968
						// when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
969
						$calItemProps[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
970
						if ($userAction) {
971
							$addrInfo = $this->getOwnerAddress($this->store);
972
973
							// if user has responded then set replytime and name
974
							$calItemProps[$this->proptags['replytime']] = time();
975
							if (!empty($addrInfo)) {
976
								$calItemProps[$this->proptags['apptreplyname']] = $addrInfo[0];
977
							}
978
						}
979
980
						$calItemProps[$this->proptags['recurring_pattern']] = '';
981
						$calItemProps[$this->proptags['alldayevent']] = $calItemProps[$this->proptags['alldayevent']] ?? false;
982
						$calItemProps[$this->proptags['private']] = $calItemProps[$this->proptags['private']] ?? false;
983
						$calItemProps[$this->proptags['meetingstatus']] = $calItemProps[$this->proptags['meetingstatus']] ?? olMeetingReceived;
984
						if (isset($calItemProps[$this->proptags['startdate']])) {
985
							$calItemProps[$this->proptags['commonstart']] = $calItemProps[$this->proptags['startdate']];
986
						}
987
						if (isset($calItemProps[$this->proptags['duedate']])) {
988
							$calItemProps[$this->proptags['commonend']] = $calItemProps[$this->proptags['duedate']];
989
						}
990
991
						mapi_setprops($calmsg, $proposeNewTimeProps + $calItemProps);
992
993
						// get properties which stores owner information in meeting request mails
994
						$props = mapi_getprops($calmsg, [
995
							PR_SENT_REPRESENTING_ENTRYID,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
996
							PR_SENT_REPRESENTING_NAME,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
997
							PR_SENT_REPRESENTING_EMAIL_ADDRESS,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
998
							PR_SENT_REPRESENTING_ADDRTYPE,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
999
							PR_SENT_REPRESENTING_SEARCH_KEY,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1000
						]);
1001
1002
						// add owner to recipient table
1003
						$recips = [];
1004
						$this->addOrganizer($props, $recips);
1005
						mapi_message_modifyrecipients($calmsg, MODRECIP_ADD, $recips);
1006
						mapi_savechanges($calmsg);
1007
1008
						// Move the message to the wastebasket
1009
						$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
1010
						mapi_folder_copymessages($sourcefolder, [$old_entryid[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1011
1012
						$messageprops = mapi_getprops($calmsg, [PR_ENTRYID]);
1013
						$entryid = $messageprops[PR_ENTRYID];
1014
					}
1015
					else {
1016
						// Create a new appointment with duplicate properties and recipient, but as an IPM.Appointment
1017
						$new = mapi_folder_createmessage($calFolder);
1018
						$props = mapi_getprops($this->message);
1019
1020
						$props[$this->proptags['recurring_pattern']] = '';
1021
						$props[$this->proptags['alldayevent']] = $props[$this->proptags['alldayevent']] ?? false;
1022
						$props[$this->proptags['private']] = $props[$this->proptags['private']] ?? false;
1023
						$props[$this->proptags['meetingstatus']] = $props[$this->proptags['meetingstatus']] ?? olMeetingReceived;
1024
						if (isset($props[$this->proptags['startdate']])) {
1025
							$props[$this->proptags['commonstart']] = $props[$this->proptags['startdate']];
1026
						}
1027
						if (isset($props[$this->proptags['duedate']])) {
1028
							$props[$this->proptags['commonend']] = $props[$this->proptags['duedate']];
1029
						}
1030
1031
						$props[PR_MESSAGE_CLASS] = 'IPM.Appointment';
1032
						// reset the PidLidMeetingType to Unspecified for outlook display the item
1033
						$props[$this->proptags['meetingtype']] = mtgEmpty;
1034
						// OL needs this field always being set, or it will not display item
1035
						$props[$this->proptags['recurring']] = false;
1036
1037
						// After creating new MR, If local categories exist then apply it on new MR.
1038
						if (!empty($localCategories)) {
1039
							mapi_setprops($new, $localCategories);
1040
						}
1041
1042
						/*
1043
						 * the client which has sent this meeting request can generate wrong flagdueby
1044
						 * time (mainly OL), so regenerate that property so we will always show reminder
1045
						 * on right time
1046
						 */
1047
						if (isset($props[$this->proptags['reminderminutes']])) {
1048
							$props[$this->proptags['flagdueby']] = $props[$this->proptags['startdate']] - ($props[$this->proptags['reminderminutes']] * 60);
1049
						}
1050
1051
						// When meeting requests are generated by third-party solutions, we might be missing the updatecounter property.
1052
						if (!isset($props[$this->proptags['updatecounter']])) {
1053
							$props[$this->proptags['updatecounter']] = 0;
1054
						}
1055
						// when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
1056
						$props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
1057
1058
						if (isset($props[$this->proptags['intendedbusystatus']])) {
1059
							if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
1060
								$props[$this->proptags['busystatus']] = fbTentative;
1061
							}
1062
							else {
1063
								$props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
1064
							}
1065
							// we already have intendedbusystatus value in $props so no need to copy it
1066
						}
1067
						else {
1068
							$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
1069
						}
1070
1071
						if ($userAction) {
1072
							$addrInfo = $this->getOwnerAddress($this->store);
1073
1074
							// if user has responded then set replytime and name
1075
							$props[$this->proptags['replytime']] = time();
1076
							if (!empty($addrInfo)) {
1077
								$props[$this->proptags['apptreplyname']] = $addrInfo[0];
1078
							}
1079
						}
1080
1081
						mapi_setprops($new, $proposeNewTimeProps + $props);
1082
1083
						$reciptable = mapi_message_getrecipienttable($this->message);
1084
1085
						$recips = [];
1086
						// If delegate, then do not add the delegate in recipients
1087
						if ($isDelegate) {
1088
							$delegate = mapi_getprops($this->message, [PR_RECEIVED_BY_EMAIL_ADDRESS]);
0 ignored issues
show
Bug introduced by
The constant PR_RECEIVED_BY_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1089
							$res = [
1090
								RES_PROPERTY,
1091
								[
1092
									RELOP => RELOP_NE,
1093
									ULPROPTAG => PR_EMAIL_ADDRESS,
0 ignored issues
show
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1094
									VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]],
1095
								],
1096
							];
1097
							$recips = mapi_table_queryallrows($reciptable, $this->recipprops, $res);
1098
						}
1099
						else {
1100
							$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
1101
						}
1102
1103
						$this->addOrganizer($props, $recips);
1104
						mapi_message_modifyrecipients($new, MODRECIP_ADD, $recips);
1105
						mapi_savechanges($new);
1106
1107
						$props = mapi_getprops($new, [PR_ENTRYID]);
1108
						$entryid = $props[PR_ENTRYID];
1109
					}
1110
				}
1111
			}
1112
		}
1113
		else {
1114
			// Here only properties are set on calendaritem, because user is responding from calendar.
1115
			$props = [];
1116
			$props[$this->proptags['responsestatus']] = $tentative ? olResponseTentative : olResponseAccepted;
1117
1118
			if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
1119
				if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
1120
					$props[$this->proptags['busystatus']] = fbTentative;
1121
				}
1122
				else {
1123
					$props[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
1124
				}
1125
				$props[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
1126
			}
1127
			else {
1128
				$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
1129
			}
1130
1131
			$props[$this->proptags['meetingstatus']] = olMeetingReceived;
1132
1133
			$addrInfo = $this->getOwnerAddress($this->store);
1134
1135
			// if user has responded then set replytime and name
1136
			$props[$this->proptags['replytime']] = time();
1137
			if (!empty($addrInfo)) {
1138
				$props[$this->proptags['apptreplyname']] = $addrInfo[0];
1139
			}
1140
1141
			if ($basedate) {
1142
				$recurr = new Recurrence($store, $this->message);
1143
1144
				// Copy recipients list
1145
				$reciptable = mapi_message_getrecipienttable($this->message);
1146
				$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
1147
1148
				if ($recurr->isException($basedate)) {
1149
					$recurr->modifyException($proposeNewTimeProps + $props, $basedate, $recips);
1150
				}
1151
				else {
1152
					$props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
1153
					$props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
1154
1155
					$props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
1156
					$props[PR_SENT_REPRESENTING_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
1157
					$props[PR_SENT_REPRESENTING_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
1158
					$props[PR_SENT_REPRESENTING_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
1159
					$props[PR_SENT_REPRESENTING_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
1160
1161
					$recurr->createException($proposeNewTimeProps + $props, $basedate, false, $recips);
1162
				}
1163
			}
1164
			else {
1165
				mapi_setprops($this->message, $proposeNewTimeProps + $props);
1166
			}
1167
			mapi_savechanges($this->message);
1168
1169
			$entryid = $messageprops[PR_ENTRYID];
1170
		}
1171
1172
		return $entryid;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $entryid does not seem to be defined for all execution paths leading up to this point.
Loading history...
1173
	}
1174
1175
	/**
1176
	 * Declines the meeting request by moving the item to the deleted
1177
	 * items folder and sending a decline message. After declining, you
1178
	 * can't use this class instance any more. The message is closed.
1179
	 * When an occurrence is decline then false is returned because that
1180
	 * occurrence is deleted not the recurring item.
1181
	 *
1182
	 * @param bool   $sendresponse true if a response has to be sent to organizer
1183
	 * @param string $basedate     if specified contains starttime of day of an occurrence
1184
	 * @param mixed  $body
1185
	 *
1186
	 * @return bool true if item is deleted from Calendar else false
1187
	 */
1188
	public function doDecline($sendresponse, $basedate = false, $body = false) {
1189
		if ($this->isLocalOrganiser()) {
1190
			return false;
1191
		}
1192
1193
		$result = false;
1194
		$calendaritem = false;
1195
1196
		// Remove any previous calendar items with this goid and appt id
1197
		$messageprops = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1198
1199
		// If this meeting request is received by a delegate then open delegator's store.
1200
		if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) {
1201
			$delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [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...
1202
1203
			$store = $delegatorStore['store'];
1204
			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
1205
		}
1206
		else {
1207
			$calFolder = $this->openDefaultCalendar();
1208
			$store = $this->store;
1209
		}
1210
1211
		// check for calendar access before deleting the calendar item
1212
		if ($this->checkCalendarWriteAccess($store) !== true) {
1213
			// Throw an exception that we don't have write permissions on calendar folder,
1214
			// allow caller to fill the error message
1215
			throw new MAPIException(null, MAPI_E_NO_ACCESS);
1216
		}
1217
1218
		$goid = $messageprops[$this->proptags['goid']];
1219
1220
		// First, find the items in the calendar by GlobalObjid (0x3)
1221
		$entryids = $this->findCalendarItems($goid, $calFolder);
1222
1223
		if (!$basedate) {
1224
			$basedate = $this->getBasedateFromGlobalID($goid);
1225
		}
1226
1227
		if ($sendresponse) {
1228
			$this->createResponse(olResponseDeclined, [], $body, $store, $basedate, $calFolder);
0 ignored issues
show
Bug introduced by
$basedate of type boolean|string is incompatible with the type integer expected by parameter $basedate of Meetingrequest::createResponse(). ( Ignorable by Annotation )

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

1228
			$this->createResponse(olResponseDeclined, [], $body, $store, /** @scrutinizer ignore-type */ $basedate, $calFolder);
Loading history...
1229
		}
1230
1231
		if ($basedate) {
1232
			// use CleanGlobalObjid (0x23)
1233
			$calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
1234
1235
			if (is_array($calendaritems)) {
1236
				foreach ($calendaritems as $entryid) {
1237
					// Open each calendar item and set the properties of the cancellation object
1238
					$calendaritem = mapi_msgstore_openentry($store, $entryid);
1239
1240
					// Recurring item is found, now delete exception
1241
					if ($calendaritem) {
1242
						$this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store);
0 ignored issues
show
Bug introduced by
It seems like $basedate can also be of type true; however, parameter $basedate of Meetingrequest::doRemoveExceptionFromCalendar() 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

1242
						$this->doRemoveExceptionFromCalendar(/** @scrutinizer ignore-type */ $basedate, $calendaritem, $store);
Loading history...
Bug introduced by
It seems like $store can also be of type MAPIStore; however, parameter $store of Meetingrequest::doRemoveExceptionFromCalendar() does only seem to accept resource, 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

1242
						$this->doRemoveExceptionFromCalendar($basedate, $calendaritem, /** @scrutinizer ignore-type */ $store);
Loading history...
1243
						$result = true;
1244
					}
1245
				}
1246
			}
1247
1248
			if ($this->isMeetingRequest()) {
1249
				$calendaritem = false;
1250
			}
1251
		}
1252
1253
		if (!$calendaritem) {
1254
			$calendar = $this->openDefaultCalendar($store);
1255
1256
			if (!empty($entryids)) {
1257
				mapi_folder_deletemessages($calendar, $entryids);
1258
			}
1259
1260
			// All we have to do to decline, is to move the item to the waste basket
1261
			$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
0 ignored issues
show
Bug introduced by
It seems like $this->openDefaultStore() can also be of type false; however, parameter $store of Meetingrequest::openDefaultWastebasket() does only seem to accept MAPIStore, 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

1261
			$wastebasket = $this->openDefaultWastebasket(/** @scrutinizer ignore-type */ $this->openDefaultStore());
Loading history...
1262
			$sourcefolder = $this->openParentFolder();
1263
1264
			$messageprops = mapi_getprops($this->message, [PR_ENTRYID]);
1 ignored issue
show
Bug introduced by
The constant PR_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1265
1266
			// Release the message
1267
			$this->message = null;
1268
1269
			// Move the message to the waste basket
1270
			mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1271
1272
			$result = true;
1273
		}
1274
1275
		return $result;
1276
	}
1277
1278
	/**
1279
	 * Removes a meeting request from the calendar when the user presses the
1280
	 * 'remove from calendar' button in response to a meeting cancellation.
1281
	 *
1282
	 * @param string $basedate if specified contains starttime of day of an occurrence
1283
	 */
1284
	public function doRemoveFromCalendar($basedate) {
1285
		if ($this->isLocalOrganiser()) {
1286
			return false;
1287
		}
1288
1289
		$messageprops = mapi_getprops($this->message, [PR_ENTRYID, $this->proptags['goid'], PR_RCVD_REPRESENTING_ENTRYID, PR_MESSAGE_CLASS]);
1 ignored issue
show
Bug introduced by
The constant PR_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_RCVD_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1290
1291
		$goid = $messageprops[$this->proptags['goid']];
1292
1293
		if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) {
1294
			$delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [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...
1295
1296
			$store = $delegatorStore['store'];
1297
			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
1298
		}
1299
		else {
1300
			$store = $this->store;
1301
			$calFolder = $this->openDefaultCalendar();
1302
		}
1303
1304
		// check for calendar access before deleting the calendar item
1305
		if ($this->checkCalendarWriteAccess($store) !== true) {
1306
			// Throw an exception that we don't have write permissions on calendar folder,
1307
			// allow caller to fill the error message
1308
			throw new MAPIException(null, MAPI_E_NO_ACCESS);
1309
		}
1310
1311
		$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
0 ignored issues
show
Bug introduced by
It seems like $this->openDefaultStore() can also be of type false; however, parameter $store of Meetingrequest::openDefaultWastebasket() does only seem to accept MAPIStore, 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

1311
		$wastebasket = $this->openDefaultWastebasket(/** @scrutinizer ignore-type */ $this->openDefaultStore());
Loading history...
1312
		// get the source folder of the meeting message
1313
		$sourcefolder = $this->openParentFolder();
1314
1315
		// Check if the message is a meeting request in the inbox or a calendaritem by checking the message class
1316
		if ($this->isMeetingCancellation($messageprops[PR_MESSAGE_CLASS])) {
1317
			// get the basedate to check for exception
1318
			$basedate = $this->getBasedateFromGlobalID($goid);
1319
1320
			$calendarItem = $this->getCorrespondentCalendarItem(true);
1321
1322
			if ($calendarItem !== false) {
0 ignored issues
show
introduced by
The condition $calendarItem !== false is always true.
Loading history...
1323
				// basedate is provided so open exception
1324
				if ($basedate) {
1325
					$exception = $this->getExceptionItem($calendarItem, $basedate);
0 ignored issues
show
Bug introduced by
$basedate of type true is incompatible with the type Unixtime expected by parameter $basedate of Meetingrequest::getExceptionItem(). ( Ignorable by Annotation )

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

1325
					$exception = $this->getExceptionItem($calendarItem, /** @scrutinizer ignore-type */ $basedate);
Loading history...
1326
1327
					if ($exception !== false) {
0 ignored issues
show
introduced by
The condition $exception !== false is always true.
Loading history...
1328
						// exception found, remove it from calendar
1329
						$this->doRemoveExceptionFromCalendar($basedate, $calendarItem, $store);
0 ignored issues
show
Bug introduced by
$basedate of type true is incompatible with the type string expected by parameter $basedate of Meetingrequest::doRemoveExceptionFromCalendar(). ( Ignorable by Annotation )

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

1329
						$this->doRemoveExceptionFromCalendar(/** @scrutinizer ignore-type */ $basedate, $calendarItem, $store);
Loading history...
Bug introduced by
$calendarItem of type entryid is incompatible with the type resource expected by parameter $message of Meetingrequest::doRemoveExceptionFromCalendar(). ( Ignorable by Annotation )

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

1329
						$this->doRemoveExceptionFromCalendar($basedate, /** @scrutinizer ignore-type */ $calendarItem, $store);
Loading history...
Bug introduced by
It seems like $store can also be of type MAPIStore; however, parameter $store of Meetingrequest::doRemoveExceptionFromCalendar() does only seem to accept resource, 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

1329
						$this->doRemoveExceptionFromCalendar($basedate, $calendarItem, /** @scrutinizer ignore-type */ $store);
Loading history...
1330
					}
1331
				}
1332
				else {
1333
					// remove normal / recurring series from calendar
1334
					$entryids = mapi_getprops($calendarItem, [PR_ENTRYID]);
1335
1336
					$entryids = [$entryids[PR_ENTRYID]];
1337
1338
					mapi_folder_copymessages($calFolder, $entryids, $wastebasket, MESSAGE_MOVE);
1339
				}
1340
			}
1341
1342
			// Release the message, because we are going to move it to wastebasket
1343
			$this->message = null;
1344
1345
			// Move the cancellation mail to wastebasket
1346
			mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1347
		}
1348
		else {
1349
			// Here only properties are set on calendaritem, because user is responding from calendar.
1350
			if ($basedate) {
1351
				// remove the occurrence
1352
				$this->doRemoveExceptionFromCalendar($basedate, $this->message, $store);
1353
			}
1354
			else {
1355
				// remove normal/recurring meeting item.
1356
				// Move the message to the waste basket
1357
				mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1358
			}
1359
		}
1360
	}
1361
1362
	/**
1363
	 * Function can be used to cancel any existing meeting and send cancellation mails to attendees.
1364
	 * Should only be called from meeting object from calendar.
1365
	 *
1366
	 * @param string $basedate (optional) basedate of occurrence which should be cancelled
1367
	 * @FIXME cancellation mail is also sent to attendee which has declined the meeting
1368
	 * @FIXME don't send canellation mail when cancelling meeting from past
1369
	 */
1370
	public function doCancelInvitation($basedate = false) {
1371
		if (!$this->isLocalOrganiser()) {
1372
			return;
1373
		}
1374
1375
		// check write access for delegate
1376
		if ($this->checkCalendarWriteAccess($this->store) !== true) {
1377
			// Throw an exception that we don't have write permissions on calendar folder,
1378
			// error message will be filled by module
1379
			throw new MAPIException(null, MAPI_E_NO_ACCESS);
1380
		}
1381
1382
		$messageProps = mapi_getprops($this->message, [PR_ENTRYID, $this->proptags['recurring']]);
1 ignored issue
show
Bug introduced by
The constant PR_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1383
1384
		if (isset($messageProps[$this->proptags['recurring']]) && $messageProps[$this->proptags['recurring']] === true) {
1385
			// cancellation of recurring series or one occurrence
1386
			$recurrence = new Recurrence($this->store, $this->message);
1387
1388
			// if basedate is specified then we are cancelling only one occurrence, so create exception for that occurrence
1389
			if ($basedate) {
1390
				$recurrence->createException([], $basedate, true);
0 ignored issues
show
Bug introduced by
$basedate of type string is incompatible with the type date expected by parameter $base_date of Recurrence::createException(). ( Ignorable by Annotation )

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

1390
				$recurrence->createException([], /** @scrutinizer ignore-type */ $basedate, true);
Loading history...
1391
			}
1392
1393
			// update the meeting request
1394
			$this->updateMeetingRequest();
1395
1396
			// send cancellation mails
1397
			$this->sendMeetingRequest(true, dgettext('zarafa', 'Canceled') . ': ', $basedate);
1398
1399
			// save changes in the message
1400
			mapi_savechanges($this->message);
1401
		}
1402
		else {
1403
			// cancellation of normal meeting request
1404
			// Send the cancellation
1405
			$this->updateMeetingRequest();
1406
			$this->sendMeetingRequest(true, dgettext('zarafa', 'Canceled') . ': ');
1407
1408
			// save changes in the message
1409
			mapi_savechanges($this->message);
1410
		}
1411
1412
		// if basedate is specified then we have already created exception of it so nothing should be done now
1413
		// but when cancelling normal / recurring meeting request we need to remove meeting from calendar
1414
		if ($basedate === false) {
1415
			// get the wastebasket folder, for delegate this will give wastebasket of delegate
1416
			$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
0 ignored issues
show
Bug introduced by
It seems like $this->openDefaultStore() can also be of type false; however, parameter $store of Meetingrequest::openDefaultWastebasket() does only seem to accept MAPIStore, 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

1416
			$wastebasket = $this->openDefaultWastebasket(/** @scrutinizer ignore-type */ $this->openDefaultStore());
Loading history...
1417
1418
			// get the source folder of the meeting message
1419
			$sourcefolder = $this->openParentFolder();
1420
1421
			// Move the message to the deleted items
1422
			mapi_folder_copymessages($sourcefolder, [$messageProps[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1423
		}
1424
	}
1425
1426
	/**
1427
	 * Convert epoch to MAPI FileTime, number of 100-nanosecond units since
1428
	 * the start of January 1, 1601.
1429
	 * https://msdn.microsoft.com/en-us/library/office/cc765906.aspx.
1430
	 *
1431
	 * @param int the current epoch
0 ignored issues
show
Bug introduced by
The type the 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...
1432
	 * @param mixed $epoch
1433
	 *
1434
	 * @return the MAPI FileTime equalevent to the given epoch time
1435
	 */
1436
	public function epochToMapiFileTime($epoch) {
1437
		$nanoseconds_between_epoch = 116444736000000000;
1438
1439
		return ($epoch * 10000000) + $nanoseconds_between_epoch;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $epoch * 10000000...noseconds_between_epoch returns the type integer which is incompatible with the documented return type the.
Loading history...
1440
	}
1441
1442
	/**
1443
	 * Sets the properties in the message so that is can be sent
1444
	 * as a meeting request. The caller has to submit the message. This
1445
	 * is only used for new MeetingRequests. Pass the appointment item as $message
1446
	 * in the constructor to do this.
1447
	 *
1448
	 * @param mixed $basedate
1449
	 */
1450
	public function setMeetingRequest($basedate = false) {
1451
		$props = mapi_getprops($this->message, [$this->proptags['updatecounter']]);
1452
1453
		// Create a new global id for this item
1454
		// https://msdn.microsoft.com/en-us/library/ee160198(v=exchg.80).aspx
1455
		$goid = pack('H*', '040000008200E00074C5B7101A82E00800000000');
1456
		/*
1457
		$year = gmdate('Y');
1458
		$month = gmdate('n');
1459
		$day = gmdate('j');
1460
		$goid .= pack('n', $year);
1461
		$goid .= pack('C', $month);
1462
		$goid .= pack('C', $day);
1463
		*/
1464
		// Creation Time
1465
		$time = $this->epochToMapiFileTime(time());
1466
		$goid .= pack('V', $time & 0xFFFFFFFF);
1467
		$goid .= pack('V', $time >> 32);
1468
		// 8 Zeros
1469
		$goid .= pack('H*', '0000000000000000');
1470
		// Length of the random data
1471
		$goid .= pack('V', 16);
1472
		// Random data.
1473
		for ($i = 0; $i < 16; ++$i) {
1474
			$goid .= chr(rand(0, 255));
1475
		}
1476
1477
		// Create a new appointment id for this item
1478
		$apptid = rand();
1479
1480
		$props[PR_OWNER_APPT_ID] = $apptid;
0 ignored issues
show
Bug introduced by
The constant PR_OWNER_APPT_ID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1481
		$props[PR_ICON_INDEX] = 1026;
0 ignored issues
show
Bug introduced by
The constant PR_ICON_INDEX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1482
		$props[$this->proptags['goid']] = $goid;
1483
		$props[$this->proptags['goid2']] = $goid;
1484
1485
		if (!isset($props[$this->proptags['updatecounter']])) {
1486
			$props[$this->proptags['updatecounter']] = 0;			// OL also starts sequence no with zero.
1487
			$props[$this->proptags['last_updatecounter']] = 0;
1488
		}
1489
1490
		mapi_setprops($this->message, $props);
1491
	}
1492
1493
	/**
1494
	 * Sends a meeting request by copying it to the outbox, converting
1495
	 * the message class, adding some properties that are required only
1496
	 * for sending the message and submitting the message. Set cancel to
1497
	 * true if you wish to completely cancel the meeting request. You can
1498
	 * specify an optional 'prefix' to prefix the sent message, which is normally
1499
	 * 'Canceled: '.
1500
	 *
1501
	 * @param mixed $cancel
1502
	 * @param mixed $prefix
1503
	 * @param mixed $basedate
1504
	 * @param mixed $modifiedRecips
1505
	 * @param mixed $deletedRecips
1506
	 */
1507
	public function sendMeetingRequest($cancel, $prefix = false, $basedate = false, $modifiedRecips = false, $deletedRecips = false) {
1508
		$this->includesResources = false;
1509
		$this->nonAcceptingResources = [];
1510
1511
		// Get the properties of the message
1512
		$messageprops = mapi_getprops($this->message, [$this->proptags['recurring']]);
1513
1514
		/*
1515
		 * Submit message to non-resource recipients
1516
		 */
1517
		// Set BusyStatus to olTentative (1)
1518
		// Set MeetingStatus to olMeetingReceived
1519
		// Set ResponseStatus to olResponseNotResponded
1520
1521
		/*
1522
		 * While sending recurrence meeting exceptions are not sent as attachments
1523
		 * because first all exceptions are sent and then recurrence meeting is sent.
1524
		 */
1525
		if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']] && !$basedate) {
1526
			// Book resource
1527
			$resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
0 ignored issues
show
Bug introduced by
It seems like $prefix can also be of type false; however, parameter $prefix of Meetingrequest::bookResources() 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

1527
			$resourceRecipData = $this->bookResources($this->message, $cancel, /** @scrutinizer ignore-type */ $prefix);
Loading history...
Unused Code introduced by
The assignment to $resourceRecipData is dead and can be removed.
Loading history...
1528
1529
			if (!$this->errorSetResource) {
1530
				$recurr = new Recurrence($this->openDefaultStore(), $this->message);
0 ignored issues
show
Bug introduced by
It seems like $this->openDefaultStore() can also be of type false; however, parameter $store of Recurrence::__construct() does only seem to accept resource, 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

1530
				$recurr = new Recurrence(/** @scrutinizer ignore-type */ $this->openDefaultStore(), $this->message);
Loading history...
1531
1532
				// First send meetingrequest for recurring item
1533
				$this->submitMeetingRequest($this->message, $cancel, $prefix, false, $recurr, false, $modifiedRecips, $deletedRecips);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type integer expected by parameter $basedate of Meetingrequest::submitMeetingRequest(). ( Ignorable by Annotation )

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

1533
				$this->submitMeetingRequest($this->message, $cancel, $prefix, /** @scrutinizer ignore-type */ false, $recurr, false, $modifiedRecips, $deletedRecips);
Loading history...
Bug introduced by
It seems like $prefix can also be of type false; however, parameter $prefix of Meetingrequest::submitMeetingRequest() 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

1533
				$this->submitMeetingRequest($this->message, $cancel, /** @scrutinizer ignore-type */ $prefix, false, $recurr, false, $modifiedRecips, $deletedRecips);
Loading history...
1534
1535
				// Then send all meeting request for all exceptions
1536
				$exceptions = $recurr->getAllExceptions();
1537
				if ($exceptions) {
1538
					foreach ($exceptions as $exceptionBasedate) {
1539
						$attach = $recurr->getExceptionAttachment($exceptionBasedate);
1540
1541
						if ($attach) {
1542
							$occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
1543
							$this->submitMeetingRequest($occurrenceItem, $cancel, false, $exceptionBasedate, $recurr, false, $modifiedRecips, $deletedRecips);
1544
							mapi_savechanges($attach);
1545
						}
1546
					}
1547
				}
1548
			}
1549
		}
1550
		else {
1551
			// Basedate found, an exception is to be sent
1552
			if ($basedate) {
1553
				$recurr = new Recurrence($this->openDefaultStore(), $this->message);
1554
1555
				if ($cancel) {
1556
					// @TODO: remove occurrence from Resource's Calendar if resource was booked for whole series
1557
					$this->submitMeetingRequest($this->message, $cancel, $prefix, $basedate, $recurr, false);
1558
				}
1559
				else {
1560
					$attach = $recurr->getExceptionAttachment($basedate);
1561
1562
					if ($attach) {
1563
						$occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
1564
1565
						// Book resource for this occurrence
1566
						$resourceRecipData = $this->bookResources($occurrenceItem, $cancel, $prefix, $basedate);
1567
1568
						if (!$this->errorSetResource) {
1569
							// Save all previous changes
1570
							mapi_savechanges($this->message);
1571
1572
							$this->submitMeetingRequest($occurrenceItem, $cancel, $prefix, $basedate, $recurr, true, $modifiedRecips, $deletedRecips);
1573
							mapi_savechanges($occurrenceItem);
1574
							mapi_savechanges($attach);
1575
						}
1576
					}
1577
				}
1578
			}
1579
			else {
1580
				// This is normal meeting
1581
				$resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
1582
1583
				if (!$this->errorSetResource) {
1584
					$this->submitMeetingRequest($this->message, $cancel, $prefix, false, false, false, $modifiedRecips, $deletedRecips);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type object expected by parameter $recurObject of Meetingrequest::submitMeetingRequest(). ( Ignorable by Annotation )

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

1584
					$this->submitMeetingRequest($this->message, $cancel, $prefix, false, /** @scrutinizer ignore-type */ false, false, $modifiedRecips, $deletedRecips);
Loading history...
1585
				}
1586
			}
1587
		}
1588
1589
		if (isset($this->errorSetResource) && $this->errorSetResource) {
1590
			return [
1591
				'error' => $this->errorSetResource,
1592
				'displayname' => $this->recipientDisplayname,
1593
			];
1594
		}
1595
1596
		return true;
1597
	}
1598
1599
	/**
1600
	 * Updates the message after an update has been performed (for example,
1601
	 * changing the time of the meeting). This must be called before re-sending
1602
	 * the meeting request. You can also call this function instead of 'setMeetingRequest()'
1603
	 * as it will automatically call setMeetingRequest on this object if it is the first
1604
	 * call to this function.
1605
	 *
1606
	 * @param mixed $basedate
1607
	 */
1608
	public function updateMeetingRequest($basedate = false) {
1609
		$messageprops = mapi_getprops($this->message, [$this->proptags['last_updatecounter'], $this->proptags['goid']]);
1610
1611
		if (!isset($messageprops[$this->proptags['goid']])) {
1612
			$this->setMeetingRequest($basedate);
1613
		}
1614
		else {
1615
			$counter = $messageprops[$this->proptags['last_updatecounter']] + 1;
1616
1617
			// increment value of last_updatecounter, last_updatecounter will be common for recurring series
1618
			// so even if you sending an exception only you need to update the last_updatecounter in the recurring series message
1619
			// this way we can make sure that every time we will be using a uniwue number for every operation
1620
			mapi_setprops($this->message, [$this->proptags['last_updatecounter'] => $counter]);
1621
		}
1622
	}
1623
1624
	/**
1625
	 * Returns TRUE if we are the organiser of the meeting. Can be used with any type of meeting object.
1626
	 */
1627
	public function isLocalOrganiser() {
1628
		$props = mapi_getprops($this->message, [$this->proptags['goid'], 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...
1629
1630
		if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS]) && !$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS]) && !$this->isMeetingCancellation($props[PR_MESSAGE_CLASS])) {
1631
			// we are checking with calendar item
1632
			$calendarItem = $this->message;
1633
		}
1634
		else {
1635
			// we are checking with meeting request / response / cancellation mail
1636
			// get calendar items
1637
			$calendarItem = $this->getCorrespondentCalendarItem(true);
1638
		}
1639
1640
		// even if we have received request/response for exception/occurrence then also
1641
		// we can check recurring series for organizer, no need to check with exception/occurrence
1642
1643
		if ($calendarItem !== false) {
1644
			$messageProps = mapi_getprops($calendarItem, [$this->proptags['responsestatus']]);
1645
1646
			if (isset($messageProps[$this->proptags['responsestatus']]) && $messageProps[$this->proptags['responsestatus']] === olResponseOrganized) {
1647
				return true;
1648
			}
1649
		}
1650
1651
		return false;
1652
	}
1653
1654
	/*
1655
	 * Support functions - INTERNAL ONLY
1656
	 ***************************************************************************************************
1657
	 */
1658
1659
	/**
1660
	 * Return the tracking status of a recipient based on the IPM class (passed).
1661
	 *
1662
	 * @param mixed $class
1663
	 */
1664
	public function getTrackStatus($class) {
1665
		$status = olRecipientTrackStatusNone;
1666
1667
		switch ($class) {
1668
			case 'IPM.Schedule.Meeting.Resp.Pos':
1669
				$status = olRecipientTrackStatusAccepted;
1670
				break;
1671
1672
			case 'IPM.Schedule.Meeting.Resp.Tent':
1673
				$status = olRecipientTrackStatusTentative;
1674
				break;
1675
1676
			case 'IPM.Schedule.Meeting.Resp.Neg':
1677
				$status = olRecipientTrackStatusDeclined;
1678
				break;
1679
		}
1680
1681
		return $status;
1682
	}
1683
1684
	/**
1685
	 * Function returns MAPIFolder resource of the folder that currently holds this meeting/meeting request
1686
	 * object.
1687
	 */
1688
	public function openParentFolder() {
1689
		$messageprops = mapi_getprops($this->message, [PR_PARENT_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_PARENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1690
1691
		return mapi_msgstore_openentry($this->store, $messageprops[PR_PARENT_ENTRYID]);
1692
	}
1693
1694
	/**
1695
	 * Function will return resource of the default calendar folder of store.
1696
	 *
1697
	 * @param MAPIStore $store {optional} user store whose default calendar should be opened
0 ignored issues
show
Bug introduced by
The type MAPIStore 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...
1698
	 *
1699
	 * @return MAPIFolder default calendar folder of store
0 ignored issues
show
Bug introduced by
The type MAPIFolder 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...
1700
	 */
1701
	public function openDefaultCalendar($store = false) {
1702
		return $this->openDefaultFolder(PR_IPM_APPOINTMENT_ENTRYID, $store);
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
It seems like $store can also be of type false; however, parameter $store of Meetingrequest::openDefaultFolder() does only seem to accept MAPIStore, 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

1702
		return $this->openDefaultFolder(PR_IPM_APPOINTMENT_ENTRYID, /** @scrutinizer ignore-type */ $store);
Loading history...
1703
	}
1704
1705
	/**
1706
	 * Function will return resource of the default outbox folder of store.
1707
	 *
1708
	 * @param MAPIStore $store {optional} user store whose default outbox should be opened
1709
	 *
1710
	 * @return MAPIFolder default outbox folder of store
1711
	 */
1712
	public function openDefaultOutbox($store = false) {
1713
		return $this->openBaseFolder(PR_IPM_OUTBOX_ENTRYID, $store);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_OUTBOX_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
It seems like $store can also be of type false; however, parameter $store of Meetingrequest::openBaseFolder() does only seem to accept MAPIStore, 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

1713
		return $this->openBaseFolder(PR_IPM_OUTBOX_ENTRYID, /** @scrutinizer ignore-type */ $store);
Loading history...
1714
	}
1715
1716
	/**
1717
	 * Function will return resource of the default wastebasket folder of store.
1718
	 *
1719
	 * @param MAPIStore $store {optional} user store whose default wastebasket should be opened
1720
	 *
1721
	 * @return MAPIFolder default wastebasket folder of store
1722
	 */
1723
	public function openDefaultWastebasket($store = false) {
1724
		return $this->openBaseFolder(PR_IPM_WASTEBASKET_ENTRYID, $store);
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
It seems like $store can also be of type false; however, parameter $store of Meetingrequest::openBaseFolder() does only seem to accept MAPIStore, 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

1724
		return $this->openBaseFolder(PR_IPM_WASTEBASKET_ENTRYID, /** @scrutinizer ignore-type */ $store);
Loading history...
1725
	}
1726
1727
	/**
1728
	 * Function will return resource of the default calendar folder of store.
1729
	 *
1730
	 * @param MAPIStore $store {optional} user store whose default calendar should be opened
1731
	 *
1732
	 * @return MAPIFolder default calendar folder of store
1733
	 */
1734
	public function getDefaultWastebasketEntryID($store = false) {
1735
		return $this->getBaseEntryID(PR_IPM_WASTEBASKET_ENTRYID, $store);
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
It seems like $store can also be of type false; however, parameter $store of Meetingrequest::getBaseEntryID() does only seem to accept MAPIStore, 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

1735
		return $this->getBaseEntryID(PR_IPM_WASTEBASKET_ENTRYID, /** @scrutinizer ignore-type */ $store);
Loading history...
1736
	}
1737
1738
	/**
1739
	 * Function will return resource of the default sent mail folder of store.
1740
	 *
1741
	 * @param MAPIStore $store {optional} user store whose default sent mail should be opened
1742
	 *
1743
	 * @return MAPIFolder default sent mail folder of store
1744
	 */
1745
	public function getDefaultSentmailEntryID($store = false) {
1746
		return $this->getBaseEntryID(PR_IPM_SENTMAIL_ENTRYID, $store);
0 ignored issues
show
Bug introduced by
It seems like $store can also be of type false; however, parameter $store of Meetingrequest::getBaseEntryID() does only seem to accept MAPIStore, 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

1746
		return $this->getBaseEntryID(PR_IPM_SENTMAIL_ENTRYID, /** @scrutinizer ignore-type */ $store);
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...
1747
	}
1748
1749
	/**
1750
	 * Function will return entryid of any default folder of store. This method is useful when you want
1751
	 * to get entryid of folder which is stored as properties of inbox folder
1752
	 * (PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_JOURNAL_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_TASK_ENTRYID).
1753
	 *
1754
	 * @param PropTag   $prop  proptag of the folder for which we want to get entryid
0 ignored issues
show
Bug introduced by
The type PropTag 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...
1755
	 * @param MAPIStore $store {optional} user store from which we need to get entryid of default folder
1756
	 *
1757
	 * @return BinString entryid of folder pointed by $prop
0 ignored issues
show
Bug introduced by
The type BinString 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...
1758
	 */
1759
	public function getDefaultFolderEntryID($prop, $store = false) {
1760
		try {
1761
			$inbox = mapi_msgstore_getreceivefolder($store ? $store : $this->store);
1762
			$inboxprops = mapi_getprops($inbox, [$prop]);
1763
			if (isset($inboxprops[$prop])) {
1764
				return $inboxprops[$prop];
1765
			}
1766
		}
1767
		catch (MAPIException $e) {
1768
			// public store doesn't support this method
1769
			if ($e->getCode() == MAPI_E_NO_SUPPORT) {
1770
				// don't propagate this error to parent handlers, if store doesn't support it
1771
				$e->setHandled();
1772
			}
1773
		}
1774
1775
		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 BinString.
Loading history...
1776
	}
1777
1778
	/**
1779
	 * Function will return resource of any default folder of store.
1780
	 *
1781
	 * @param PropTag   $prop  proptag of the folder that we want to open
1782
	 * @param MAPIStore $store {optional} user store from which we need to open default folder
1783
	 *
1784
	 * @return MAPIFolder default folder of store
1785
	 */
1786
	public function openDefaultFolder($prop, $store = false) {
1787
		$folder = false;
1788
		$entryid = $this->getDefaultFolderEntryID($prop, $store);
0 ignored issues
show
Bug introduced by
It seems like $store can also be of type false; however, parameter $store of Meetingrequest::getDefaultFolderEntryID() does only seem to accept MAPIStore, 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

1788
		$entryid = $this->getDefaultFolderEntryID($prop, /** @scrutinizer ignore-type */ $store);
Loading history...
1789
1790
		if ($entryid !== false) {
0 ignored issues
show
introduced by
The condition $entryid !== false is always true.
Loading history...
1791
			$folder = mapi_msgstore_openentry($store ? $store : $this->store, $entryid);
1792
		}
1793
1794
		return $folder;
1795
	}
1796
1797
	/**
1798
	 * Function will return entryid of default folder from store. This method is useful when you want
1799
	 * to get entryid of folder which is stored as store properties
1800
	 * (PR_IPM_FAVORITES_ENTRYID, PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID).
1801
	 *
1802
	 * @param PropTag   $prop  proptag of the folder whose entryid we want to get
1803
	 * @param MAPIStore $store {optional} user store from which we need to get entryid of default folder
1804
	 *
1805
	 * @return BinString entryid of default folder from store
1806
	 */
1807
	public function getBaseEntryID($prop, $store = false) {
1808
		$storeprops = mapi_getprops($store ? $store : $this->store, [$prop]);
1809
		if (!isset($storeprops[$prop])) {
1810
			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 BinString.
Loading history...
1811
		}
1812
1813
		return $storeprops[$prop];
1814
	}
1815
1816
	/**
1817
	 * Function will return resource of any default folder of store.
1818
	 *
1819
	 * @param PropTag   $prop  proptag of the folder that we want to open
1820
	 * @param MAPIStore $store {optional} user store from which we need to open default folder
1821
	 *
1822
	 * @return MAPIFolder default folder of store
1823
	 */
1824
	public function openBaseFolder($prop, $store = false) {
1825
		$folder = false;
1826
		$entryid = $this->getBaseEntryID($prop, $store);
0 ignored issues
show
Bug introduced by
It seems like $store can also be of type false; however, parameter $store of Meetingrequest::getBaseEntryID() does only seem to accept MAPIStore, 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

1826
		$entryid = $this->getBaseEntryID($prop, /** @scrutinizer ignore-type */ $store);
Loading history...
1827
1828
		if ($entryid !== false) {
0 ignored issues
show
introduced by
The condition $entryid !== false is always true.
Loading history...
1829
			$folder = mapi_msgstore_openentry($store ? $store : $this->store, $entryid);
1830
		}
1831
1832
		return $folder;
1833
	}
1834
1835
	/**
1836
	 * Function checks whether user has access over the specified folder or not.
1837
	 *
1838
	 * @param Binary    $entryid entryid The entryid of the folder to check
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...
1839
	 * @param MAPIStore $store   (optional) store from which folder should be opened
1840
	 *
1841
	 * @return bool true if user has an access over the folder, false if not
1842
	 */
1843
	public function checkFolderWriteAccess($entryid, $store = false) {
1844
		$accessToFolder = false;
1845
1846
		if (!empty($entryid)) {
1847
			if ($store === false) {
1848
				$store = $this->store;
1849
			}
1850
1851
			try {
1852
				$folder = mapi_msgstore_openentry($store, $entryid);
1853
				$folderProps = mapi_getprops($folder, [PR_ACCESS]);
0 ignored issues
show
Bug introduced by
The constant PR_ACCESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1854
				if (($folderProps[PR_ACCESS] & MAPI_ACCESS_CREATE_CONTENTS) === MAPI_ACCESS_CREATE_CONTENTS) {
1855
					$accessToFolder = true;
1856
				}
1857
			}
1858
			catch (MAPIException $e) {
1859
				// we don't have rights to open folder, so return false
1860
				if ($e->getCode() == MAPI_E_NO_ACCESS) {
1861
					return $accessToFolder;
1862
				}
1863
1864
				// rethrow other errors
1865
				throw $e;
1866
			}
1867
		}
1868
1869
		return $accessToFolder;
1870
	}
1871
1872
	/**
1873
	 * Function checks whether user has access over the specified folder or not.
1874
	 *
1875
	 * @param object MAPI Message Store Object
1876
	 * @param mixed $store
1877
	 *
1878
	 * @return bool true if user has an access over the folder, false if not
1879
	 */
1880
	public function checkCalendarWriteAccess($store = false) {
1881
		if ($store === false) {
1882
			// If this meeting request is received by a delegate then open delegator's store.
1883
			$messageProps = mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1884
			if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) {
1885
				$delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID]);
1886
1887
				$store = $delegatorStore['store'];
1888
			}
1889
			else {
1890
				$store = $this->store;
1891
			}
1892
		}
1893
1894
		// If the store is a public folder, the calendar folder is the PARENT_ENTRYID of the calendar item
1895
		$provider = mapi_getprops($store, [PR_MDB_PROVIDER]);
0 ignored issues
show
Bug introduced by
The constant PR_MDB_PROVIDER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1896
		if (isset($provider[PR_MDB_PROVIDER]) && $provider[PR_MDB_PROVIDER] === ZARAFA_STORE_PUBLIC_GUID) {
1897
			$entryid = mapi_getprops($this->message, [PR_PARENT_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_PARENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1898
			$entryid = $entryid[PR_PARENT_ENTRYID];
1899
		}
1900
		else {
1901
			$entryid = $this->getDefaultFolderEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store);
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...
1902
			if ($entryid === false) {
0 ignored issues
show
introduced by
The condition $entryid === false is always false.
Loading history...
1903
				$entryid = $this->getBaseEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store);
1904
			}
1905
1906
			if ($entryid === false) {
0 ignored issues
show
introduced by
The condition $entryid === false is always false.
Loading history...
1907
				return false;
1908
			}
1909
		}
1910
1911
		return $this->checkFolderWriteAccess($entryid, $store);
1912
	}
1913
1914
	/**
1915
	 * Function will resolve the user and open its store.
1916
	 *
1917
	 * @param string $ownerentryid the entryid of the user
1918
	 *
1919
	 * @return MAPIStore store of the user
1920
	 */
1921
	public function openCustomUserStore($ownerentryid) {
1922
		$ab = mapi_openaddressbook($this->session);
1923
1924
		try {
1925
			$mailuser = mapi_ab_openentry($ab, $ownerentryid);
1926
		}
1927
		catch (MAPIException $e) {
1928
			return;
1929
		}
1930
1931
		$mailuserprops = mapi_getprops($mailuser, [PR_EMAIL_ADDRESS]);
0 ignored issues
show
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1932
		$storeid = mapi_msgstore_createentryid($this->store, $mailuserprops[PR_EMAIL_ADDRESS]);
1933
1934
		return mapi_openmsgstore($this->session, $storeid);
1935
	}
1936
1937
	/**
1938
	 * Function which sends response to organizer when attendee accepts, declines or proposes new time to a received meeting request.
1939
	 *
1940
	 * @param int   $status              response status of attendee
1941
	 * @param array $proposeNewTimeProps properties of attendee's proposal
1942
	 * @param int   $basedate            date of occurrence which attendee has responded
1943
	 * @param mixed $body
1944
	 * @param mixed $store
1945
	 * @param mixed $calFolder
1946
	 */
1947
	public function createResponse($status, $proposeNewTimeProps = [], $body = false, $store, $basedate = false, $calFolder) {
1948
		$messageprops = mapi_getprops($this->message, [
1949
			PR_SENT_REPRESENTING_ENTRYID,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1950
			PR_SENT_REPRESENTING_EMAIL_ADDRESS,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1951
			PR_SENT_REPRESENTING_ADDRTYPE,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1952
			PR_SENT_REPRESENTING_NAME,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1953
			PR_SENT_REPRESENTING_SEARCH_KEY,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1954
			$this->proptags['goid'],
1955
			$this->proptags['goid2'],
1956
			$this->proptags['location'],
1957
			$this->proptags['startdate'],
1958
			$this->proptags['duedate'],
1959
			$this->proptags['recurring'],
1960
			$this->proptags['recurring_pattern'],
1961
			$this->proptags['recurrence_data'],
1962
			$this->proptags['timezone_data'],
1963
			$this->proptags['timezone'],
1964
			$this->proptags['updatecounter'],
1965
			PR_SUBJECT,
0 ignored issues
show
Bug introduced by
The constant PR_SUBJECT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1966
			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...
1967
			PR_OWNER_APPT_ID,
0 ignored issues
show
Bug introduced by
The constant PR_OWNER_APPT_ID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1968
			$this->proptags['is_exception'],
1969
		]);
1970
1971
		if ($basedate && !$this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $basedate of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1972
			// we are creating response from a recurring calendar item object
1973
			// We found basedate,so opened occurrence and get properties.
1974
			$recurr = new Recurrence($store, $this->message);
1975
			$exception = $recurr->getExceptionAttachment($basedate);
1976
1977
			if ($exception) {
1978
				// Exception found, Now retrieve properties
1979
				$imessage = mapi_attach_openobj($exception, 0);
1980
				$imsgprops = mapi_getprops($imessage);
1981
1982
				// If location is provided, copy it to the response
1983
				if (isset($imsgprops[$this->proptags['location']])) {
1984
					$messageprops[$this->proptags['location']] = $imsgprops[$this->proptags['location']];
1985
				}
1986
1987
				// Update $messageprops with timings of occurrence
1988
				$messageprops[$this->proptags['startdate']] = $imsgprops[$this->proptags['startdate']];
1989
				$messageprops[$this->proptags['duedate']] = $imsgprops[$this->proptags['duedate']];
1990
1991
				// Meeting related properties
1992
				$props[$this->proptags['meetingstatus']] = $imsgprops[$this->proptags['meetingstatus']];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$props was never initialized. Although not strictly required by PHP, it is generally a good practice to add $props = array(); before regardless.
Loading history...
1993
				$props[$this->proptags['responsestatus']] = $imsgprops[$this->proptags['responsestatus']];
1994
				$props[PR_SUBJECT] = $imsgprops[PR_SUBJECT];
1995
			}
1996
			else {
1997
				// Exceptions is deleted.
1998
				// Update $messageprops with timings of occurrence
1999
				$messageprops[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
2000
				$messageprops[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
2001
2002
				$props[$this->proptags['meetingstatus']] = olNonMeeting;
2003
				$props[$this->proptags['responsestatus']] = olResponseNone;
2004
			}
2005
2006
			$props[$this->proptags['recurring']] = false;
2007
			$props[$this->proptags['is_exception']] = true;
2008
		}
2009
		else {
2010
			// we are creating a response from meeting request mail (it could be recurring or non-recurring)
2011
			// Send all recurrence info in response, if this is a recurrence meeting.
2012
			$isRecurring = isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']];
2013
			$isException = isset($messageprops[$this->proptags['is_exception']]) && $messageprops[$this->proptags['is_exception']];
2014
			if ($isRecurring || $isException) {
2015
				if ($isRecurring) {
2016
					$props[$this->proptags['recurring']] = $messageprops[$this->proptags['recurring']];
2017
				}
2018
				if ($isException) {
2019
					$props[$this->proptags['is_exception']] = $messageprops[$this->proptags['is_exception']];
2020
				}
2021
				$calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
2022
2023
				$calendaritem = mapi_msgstore_openentry($store, $calendaritems[0]);
2024
				$recurr = new Recurrence($store, $calendaritem);
2025
			}
2026
		}
2027
2028
		// we are sending a response for recurring meeting request (or exception), so set some required properties
2029
		if (isset($recurr) && $recurr) {
2030
			if (!empty($messageprops[$this->proptags['recurring_pattern']])) {
2031
				$props[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
2032
			}
2033
2034
			if (!empty($messageprops[$this->proptags['recurrence_data']])) {
2035
				$props[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
2036
			}
2037
2038
			$props[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
2039
			$props[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
2040
2041
			$this->generateRecurDates($recurr, $messageprops, $props);
2042
		}
2043
2044
		// Create a response message
2045
		$recip = [];
2046
		$recip[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
1 ignored issue
show
Bug introduced by
The constant PR_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2047
		$recip[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
0 ignored issues
show
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2048
		$recip[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_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...
2049
		$recip[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
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...
2050
		$recip[PR_RECIPIENT_TYPE] = MAPI_TO;
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...
2051
		$recip[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_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...
2052
2053
		switch ($status) {
2054
			case olResponseAccepted:
2055
				$classpostfix = 'Pos';
2056
				$subjectprefix = dgettext('zarafa', 'Accepted');
2057
				break;
2058
2059
			case olResponseDeclined:
2060
				$classpostfix = 'Neg';
2061
				$subjectprefix = dgettext('zarafa', 'Declined');
2062
				break;
2063
2064
			case olResponseTentative:
2065
				$classpostfix = 'Tent';
2066
				$subjectprefix = dgettext('zarafa', 'Tentatively accepted');
2067
				break;
2068
		}
2069
2070
		if (!empty($proposeNewTimeProps)) {
2071
			// if attendee has proposed new time then change subject prefix
2072
			$subjectprefix = dgettext('zarafa', 'New Time Proposed');
2073
		}
2074
2075
		$props[PR_SUBJECT] = $subjectprefix . ': ' . $messageprops[PR_SUBJECT];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $subjectprefix does not seem to be defined for all execution paths leading up to this point.
Loading history...
2076
2077
		$props[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Resp.' . $classpostfix;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $classpostfix does not seem to be defined for all execution paths leading up to this point.
Loading history...
2078
		if (isset($messageprops[PR_OWNER_APPT_ID])) {
2079
			$props[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
2080
		}
2081
2082
		// Set GlobalId AND CleanGlobalId, if exception then also set basedate into GlobalId(0x3).
2083
		$props[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
2084
		$props[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
2085
		$props[$this->proptags['updatecounter']] = isset($messageprops[$this->proptags['updatecounter']]) ? $messageprops[$this->proptags['updatecounter']] : 0;
2086
2087
		if (!empty($proposeNewTimeProps)) {
2088
			// merge proposal properties to message properties which will be sent to organizer
2089
			$props = $proposeNewTimeProps + $props;
2090
		}
2091
2092
		// Set body message in Appointment
2093
		if (isset($body)) {
2094
			$props[PR_BODY] = $this->getMeetingTimeInfo() ? $this->getMeetingTimeInfo() : $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...
2095
		}
2096
2097
		// PR_START_DATE/PR_END_DATE is used in the UI in Outlook on the response message
2098
		$props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
0 ignored issues
show
Bug introduced by
The constant PR_START_DATE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2099
		$props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
0 ignored issues
show
Bug introduced by
The constant PR_END_DATE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2100
2101
		// Set startdate and duedate in response mail.
2102
		$props[$this->proptags['startdate']] = $messageprops[$this->proptags['startdate']];
2103
		$props[$this->proptags['duedate']] = $messageprops[$this->proptags['duedate']];
2104
2105
		// responselocation is used in the UI in Outlook on the response message
2106
		if (isset($messageprops[$this->proptags['location']])) {
2107
			$props[$this->proptags['responselocation']] = $messageprops[$this->proptags['location']];
2108
			$props[$this->proptags['location']] = $messageprops[$this->proptags['location']];
2109
		}
2110
2111
		$message = $this->createOutgoingMessage($store);
2112
2113
		mapi_setprops($message, $props);
2114
		mapi_message_modifyrecipients($message, MODRECIP_ADD, [$recip]);
2115
		mapi_savechanges($message);
2116
		mapi_message_submitmessage($message);
2117
	}
2118
2119
	/**
2120
	 * Function which finds items in calendar based on globalId and cleanGlobalId.
2121
	 *
2122
	 * @param binary     $goid             GlobalID(0x3) of item
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...
2123
	 * @param MAPIFolder $calendar         MAPI_folder of user (optional)
2124
	 * @param bool       $useCleanGlobalId if true then search should be performed on cleanGlobalId(0x23) else globalId(0x3)
2125
	 */
2126
	public function findCalendarItems($goid, $calendar = false, $useCleanGlobalId = false) {
2127
		if ($calendar === false) {
2128
			// Open the Calendar
2129
			$calendar = $this->openDefaultCalendar();
2130
		}
2131
2132
		// Find the item by restricting all items to the correct ID
2133
		$restrict = [
2134
			RES_PROPERTY,
2135
			[
2136
				RELOP => RELOP_EQ,
2137
				ULPROPTAG => ($useCleanGlobalId === true ? $this->proptags['goid2'] : $this->proptags['goid']),
2138
				VALUE => $goid,
2139
			],
2140
		];
2141
2142
		$calendarcontents = mapi_folder_getcontentstable($calendar);
2143
2144
		$rows = mapi_table_queryallrows($calendarcontents, [PR_ENTRYID], $restrict);
1 ignored issue
show
Bug introduced by
The constant PR_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2145
2146
		if (empty($rows)) {
2147
			return;
2148
		}
2149
2150
		$calendaritems = [];
2151
2152
		// In principle, there should only be one row, but we'll handle them all just in case
2153
		foreach ($rows as $row) {
2154
			$calendaritems[] = $row[PR_ENTRYID];
2155
		}
2156
2157
		return $calendaritems;
2158
	}
2159
2160
	// Returns TRUE if both entryid's are equal. Equality is defined by both entryid's pointing at the
2161
	// same SMTP address when converted to SMTP
2162
	public function compareABEntryIDs($entryid1, $entryid2) {
2163
		// If the session was not passed, just do a 'normal' compare.
2164
		if (!$this->session) {
2165
			return $entryid1 == $entryid2;
2166
		}
2167
2168
		$smtp1 = $this->getSMTPAddress($entryid1);
2169
		$smtp2 = $this->getSMTPAddress($entryid2);
2170
2171
		if ($smtp1 == $smtp2) {
2172
			return true;
2173
		}
2174
2175
		return false;
2176
	}
2177
2178
	// Gets the SMTP address of the passed addressbook entryid
2179
	public function getSMTPAddress($entryid) {
2180
		if (!$this->session) {
2181
			return false;
2182
		}
2183
2184
		try {
2185
			$ab = mapi_openaddressbook($this->session);
2186
			$abitem = mapi_ab_openentry($ab, $entryid);
2187
2188
			if (!$abitem) {
2189
				return '';
2190
			}
2191
		}
2192
		catch (MAPIException $e) {
2193
			return '';
2194
		}
2195
2196
		$props = mapi_getprops($abitem, [PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS]);
0 ignored issues
show
Bug introduced by
The constant PR_SMTP_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_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...
2197
2198
		if ($props[PR_ADDRTYPE] == 'SMTP') {
2199
			return $props[PR_EMAIL_ADDRESS];
2200
		}
2201
2202
		return $props[PR_SMTP_ADDRESS];
2203
	}
2204
2205
	/**
2206
	 * Gets the properties associated with the owner of the passed store:
2207
	 * PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_ADDRTYPE, PR_ENTRYID, PR_SEARCH_KEY.
2208
	 *
2209
	 * @param $store message store
2210
	 * @param $fallbackToLoggedInUser if true then return properties of logged in user instead of mailbox owner
0 ignored issues
show
Bug introduced by
The type if 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...
2211
	 * not used when passed store is public store. for public store we are always returning logged in user's info.
2212
	 *
2213
	 * @return properties of logged in user in an array in sequence of display_name, email address, address type,
0 ignored issues
show
Bug introduced by
The type properties 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...
2214
	 *                    entryid and search key
2215
	 */
2216
	public function getOwnerAddress($store, $fallbackToLoggedInUser = true) {
2217
		if (!$this->session) {
2218
			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 properties.
Loading history...
2219
		}
2220
2221
		$storeProps = mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID, PR_USER_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_USER_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...
2222
2223
		$ownerEntryId = false;
2224
		if (isset($storeProps[PR_USER_ENTRYID]) && $storeProps[PR_USER_ENTRYID]) {
2225
			$ownerEntryId = $storeProps[PR_USER_ENTRYID];
2226
		}
2227
2228
		if (isset($storeProps[PR_MAILBOX_OWNER_ENTRYID]) && $storeProps[PR_MAILBOX_OWNER_ENTRYID] && !$fallbackToLoggedInUser) {
2229
			$ownerEntryId = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
2230
		}
2231
2232
		if ($ownerEntryId) {
2233
			$ab = mapi_openaddressbook($this->session);
2234
2235
			$zarafaUser = mapi_ab_openentry($ab, $ownerEntryId);
2236
			if (!$zarafaUser) {
2237
				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 properties.
Loading history...
2238
			}
2239
2240
			$ownerProps = mapi_getprops($zarafaUser, [PR_ADDRTYPE, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, 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...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2241
2242
			$addrType = $ownerProps[PR_ADDRTYPE];
2243
			$name = $ownerProps[PR_DISPLAY_NAME];
2244
			$emailAddr = $ownerProps[PR_EMAIL_ADDRESS];
2245
			$searchKey = $ownerProps[PR_SEARCH_KEY];
2246
			$entryId = $ownerEntryId;
2247
2248
			return [$name, $emailAddr, $addrType, $entryId, $searchKey];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array($name, $ema..., $entryId, $searchKey) returns the type array which is incompatible with the documented return type properties.
Loading history...
2249
		}
2250
2251
		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 properties.
Loading history...
2252
	}
2253
2254
	// Opens this session's default message store
2255
	public function openDefaultStore() {
2256
		$storestable = mapi_getmsgstorestable($this->session);
2257
		$rows = mapi_table_queryallrows($storestable, [PR_ENTRYID, PR_DEFAULT_STORE]);
1 ignored issue
show
Bug introduced by
The constant PR_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DEFAULT_STORE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2258
2259
		foreach ($rows as $row) {
2260
			if (isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE]) {
2261
				$entryid = $row[PR_ENTRYID];
2262
				break;
2263
			}
2264
		}
2265
2266
		if (!$entryid) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $entryid does not seem to be defined for all execution paths leading up to this point.
Loading history...
2267
			return false;
2268
		}
2269
2270
		return mapi_openmsgstore($this->session, $entryid);
2271
	}
2272
2273
	/**
2274
	 *  Function which adds organizer to recipient list which is passed.
2275
	 *  This function also checks if it has organizer.
2276
	 *
2277
	 * @param array $messageProps message properties
2278
	 * @param array $recipients   recipients list of message
2279
	 * @param bool  $isException  true if we are processing recipient of exception
2280
	 */
2281
	public function addOrganizer($messageProps, &$recipients, $isException = false) {
2282
		$hasOrganizer = false;
2283
		// Check if meeting already has an organizer.
2284
		foreach ($recipients as $key => $recipient) {
2285
			if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
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...
2286
				$hasOrganizer = true;
2287
			}
2288
			elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) {
2289
				// Recipients for an occurrence
2290
				$recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
2291
			}
2292
		}
2293
2294
		if (!$hasOrganizer) {
2295
			// Create organizer.
2296
			$organizer = [];
2297
			$organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID];
1 ignored issue
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2298
			$organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2299
			$organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_EMAIL_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...
2300
			$organizer[PR_RECIPIENT_TYPE] = MAPI_TO;
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...
2301
			$organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_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...
2302
			$organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ADDRTYPE 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...
2303
			$organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
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...
2304
			$organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
2305
			$organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY];
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_SEARCH_KEY 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...
2306
2307
			// Add organizer to recipients list.
2308
			array_unshift($recipients, $organizer);
2309
		}
2310
	}
2311
2312
	/**
2313
	 * Function which removes an exception/occurrence from recurrencing meeting
2314
	 * when a meeting cancellation of an occurrence is processed.
2315
	 *
2316
	 * @param string   $basedate basedate of an occurrence
2317
	 * @param resource $message  recurring item from which occurrence has to be deleted
2318
	 * @param resource $store    MAPI_MSG_Store which contains the item
2319
	 */
2320
	public function doRemoveExceptionFromCalendar($basedate, $message, $store) {
2321
		$recurr = new Recurrence($store, $message);
2322
		$recurr->createException([], $basedate, true);
0 ignored issues
show
Bug introduced by
$basedate of type string is incompatible with the type date expected by parameter $base_date of Recurrence::createException(). ( Ignorable by Annotation )

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

2322
		$recurr->createException([], /** @scrutinizer ignore-type */ $basedate, true);
Loading history...
2323
		mapi_savechanges($message);
2324
	}
2325
2326
	/**
2327
	 * Function which returns basedate of an changed occurrence from globalID of meeting request.
2328
	 *
2329
	 *@param binary $goid globalID
2330
	 *
2331
	 *@return bool true if basedate is found else false it not found
2332
	 */
2333
	public function getBasedateFromGlobalID($goid) {
2334
		$hexguid = bin2hex($goid);
2335
		$hexbase = substr($hexguid, 32, 8);
2336
		$day = hexdec(substr($hexbase, 6, 2));
2337
		$month = hexdec(substr($hexbase, 4, 2));
2338
		$year = hexdec(substr($hexbase, 0, 4));
2339
2340
		if ($day && $month && $year) {
2341
			return gmmktime(0, 0, 0, $month, $day, $year);
0 ignored issues
show
Bug introduced by
It seems like $month can also be of type double; however, parameter $month of gmmktime() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

2341
			return gmmktime(0, 0, 0, /** @scrutinizer ignore-type */ $month, $day, $year);
Loading history...
Bug introduced by
It seems like $year can also be of type double; however, parameter $year of gmmktime() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

2341
			return gmmktime(0, 0, 0, $month, $day, /** @scrutinizer ignore-type */ $year);
Loading history...
Bug Best Practice introduced by
The expression return gmmktime(0, 0, 0, $month, $day, $year) returns the type integer which is incompatible with the documented return type boolean.
Loading history...
Bug introduced by
It seems like $day can also be of type double; however, parameter $day of gmmktime() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

2341
			return gmmktime(0, 0, 0, $month, /** @scrutinizer ignore-type */ $day, $year);
Loading history...
2342
		}
2343
2344
		return false;
2345
	}
2346
2347
	/**
2348
	 * Function which sets basedate in globalID of changed occurrence which is to be sent.
2349
	 *
2350
	 *@param binary $goid globalID
2351
	 *@param string basedate of changed occurrence
0 ignored issues
show
Bug introduced by
The type basedate 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...
2352
	 * @param mixed $basedate
2353
	 *
2354
	 *@return binary globalID with basedate in it
2355
	 */
2356
	public function setBasedateInGlobalID($goid, $basedate = false) {
2357
		$hexguid = bin2hex($goid);
2358
		$year = $basedate ? sprintf('%04s', dechex(gmdate('Y', $basedate))) : '0000';
0 ignored issues
show
Bug introduced by
gmdate('Y', $basedate) of type string is incompatible with the type integer expected by parameter $num of dechex(). ( Ignorable by Annotation )

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

2358
		$year = $basedate ? sprintf('%04s', dechex(/** @scrutinizer ignore-type */ gmdate('Y', $basedate))) : '0000';
Loading history...
2359
		$month = $basedate ? sprintf('%02s', dechex(gmdate('m', $basedate))) : '00';
2360
		$day = $basedate ? sprintf('%02s', dechex(gmdate('d', $basedate))) : '00';
2361
2362
		return hex2bin(strtoupper(substr($hexguid, 0, 32) . $year . $month . $day . substr($hexguid, 40)));
0 ignored issues
show
Bug Best Practice introduced by
The expression return hex2bin(strtouppe... substr($hexguid, 40))) returns the type string which is incompatible with the documented return type binary.
Loading history...
2363
	}
2364
2365
	/**
2366
	 * Function which replaces attachments with copy_from in copy_to.
2367
	 *
2368
	 * @param MAPIMessage $copy_from      MAPI_message from which attachments are to be copied
2369
	 * @param MAPIMessage $copy_to        MAPI_message to which attachment are to be copied
2370
	 * @param bool        $copyExceptions if true then all exceptions should also be sent as attachments
2371
	 * @param mixed       $copyFrom
2372
	 * @param mixed       $copyTo
2373
	 */
2374
	public function replaceAttachments($copyFrom, $copyTo, $copyExceptions = true) {
2375
		/* remove all old attachments */
2376
		$attachmentTable = mapi_message_getattachmenttable($copyTo);
2377
		if ($attachmentTable) {
2378
			$attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME]);
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_ATTACH_METHOD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EXCEPTION_STARTTIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2379
2380
			foreach ($attachments as $attachProps) {
2381
				/* remove exceptions too? */
2382
				if (!$copyExceptions && $attachProps[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG && isset($attachProps[PR_EXCEPTION_STARTTIME])) {
2383
					continue;
2384
				}
2385
				mapi_message_deleteattach($copyTo, $attachProps[PR_ATTACH_NUM]);
2386
			}
2387
		}
2388
		$attachmentTable = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $attachmentTable is dead and can be removed.
Loading history...
2389
2390
		/* copy new attachments */
2391
		$attachmentTable = mapi_message_getattachmenttable($copyFrom);
2392
		if ($attachmentTable) {
2393
			$attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME]);
2394
2395
			foreach ($attachments as $attachProps) {
2396
				if (!$copyExceptions && $attachProps[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG && isset($attachProps[PR_EXCEPTION_STARTTIME])) {
2397
					continue;
2398
				}
2399
2400
				$attachOld = mapi_message_openattach($copyFrom, (int) $attachProps[PR_ATTACH_NUM]);
2401
				$attachNewResourceMsg = mapi_message_createattach($copyTo);
2402
				mapi_copyto($attachOld, [], [], $attachNewResourceMsg, 0);
2403
				mapi_savechanges($attachNewResourceMsg);
2404
			}
2405
		}
2406
	}
2407
2408
	/**
2409
	 * Function which replaces recipients in copy_to with recipients from copyFrom.
2410
	 *
2411
	 * @param MAPIMessage $copyFrom   MAPI_message from which recipients are to be copied
2412
	 * @param MAPIMessage $copyTo     MAPI_message to which recipients are to be copied
2413
	 * @param bool        $isDelegate indicates delegate is processing
2414
	 *                                so don't copy delegate information to recipient table
2415
	 */
2416
	public function replaceRecipients($copyFrom, $copyTo, $isDelegate = false) {
2417
		$recipientTable = mapi_message_getrecipienttable($copyFrom);
2418
2419
		// If delegate, then do not add the delegate in recipients
2420
		if ($isDelegate) {
2421
			$delegate = mapi_getprops($copyFrom, [PR_RECEIVED_BY_EMAIL_ADDRESS]);
0 ignored issues
show
Bug introduced by
The constant PR_RECEIVED_BY_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2422
			$res = [
2423
				RES_PROPERTY,
2424
				[
2425
					RELOP => RELOP_NE,
2426
					ULPROPTAG => PR_EMAIL_ADDRESS,
0 ignored issues
show
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2427
					VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]],
2428
				],
2429
			];
2430
			$recipients = mapi_table_queryallrows($recipientTable, $this->recipprops, $res);
2431
		}
2432
		else {
2433
			$recipients = mapi_table_queryallrows($recipientTable, $this->recipprops);
2434
		}
2435
2436
		$copyToRecipientTable = mapi_message_getrecipienttable($copyTo);
2437
		$copyToRecipientRows = mapi_table_queryallrows($copyToRecipientTable, [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...
2438
2439
		mapi_message_modifyrecipients($copyTo, MODRECIP_REMOVE, $copyToRecipientRows);
2440
		mapi_message_modifyrecipients($copyTo, MODRECIP_ADD, $recipients);
2441
	}
2442
2443
	/**
2444
	 * Function creates meeting item in resource's calendar.
2445
	 *
2446
	 * @param resource $message  MAPI_message which is to create in resource's calendar
2447
	 * @param bool     $cancel   cancel meeting
2448
	 * @param string   $prefix   prefix for subject of meeting
2449
	 * @param mixed    $basedate
2450
	 */
2451
	public function bookResources($message, $cancel, $prefix, $basedate = false) {
2452
		if (!$this->enableDirectBooking) {
2453
			return [];
2454
		}
2455
2456
		// Get the properties of the message
2457
		$messageprops = mapi_getprops($message);
2458
2459
		if ($basedate) {
2460
			$recurrItemProps = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], $this->proptags['timezone_data'], $this->proptags['timezone'], PR_OWNER_APPT_ID]);
0 ignored issues
show
Bug introduced by
The constant PR_OWNER_APPT_ID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2461
2462
			$messageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid']], $basedate);
2463
			$messageprops[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']];
2464
2465
			// Delete properties which are not needed.
2466
			$deleteProps = [$this->proptags['basedate'], PR_DISPLAY_NAME, PR_ATTACHMENT_FLAGS, PR_ATTACHMENT_HIDDEN, PR_ATTACHMENT_LINKID, PR_ATTACH_FLAGS, PR_ATTACH_METHOD];
0 ignored issues
show
Bug introduced by
The constant PR_ATTACHMENT_LINKID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACHMENT_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_METHOD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACHMENT_HIDDEN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2467
			foreach ($deleteProps as $propID) {
2468
				if (isset($messageprops[$propID])) {
2469
					unset($messageprops[$propID]);
2470
				}
2471
			}
2472
2473
			if (isset($messageprops[$this->proptags['recurring']])) {
2474
				$messageprops[$this->proptags['recurring']] = false;
2475
			}
2476
2477
			// Set Outlook properties
2478
			$messageprops[$this->proptags['clipstart']] = $messageprops[$this->proptags['startdate']];
2479
			$messageprops[$this->proptags['clipend']] = $messageprops[$this->proptags['duedate']];
2480
			$messageprops[$this->proptags['timezone_data']] = $recurrItemProps[$this->proptags['timezone_data']];
2481
			$messageprops[$this->proptags['timezone']] = $recurrItemProps[$this->proptags['timezone']];
2482
			$messageprops[$this->proptags['attendee_critical_change']] = time();
2483
			$messageprops[$this->proptags['owner_critical_change']] = time();
2484
		}
2485
2486
		// Get resource recipients
2487
		$getResourcesRestriction = [
2488
			RES_PROPERTY,
2489
			[
2490
				RELOP => RELOP_EQ,	// Equals recipient type 3: Resource
2491
				ULPROPTAG => 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...
2492
				VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2493
			],
2494
		];
2495
		$recipienttable = mapi_message_getrecipienttable($message);
2496
		$resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
2497
2498
		$this->errorSetResource = false;
2499
		$resourceRecipData = [];
2500
2501
		// Put appointment into store resource users
2502
		$i = 0;
2503
		$len = count($resourceRecipients);
2504
		while (!$this->errorSetResource && $i < $len) {
2505
			$userStore = $this->openCustomUserStore($resourceRecipients[$i][PR_ENTRYID]);
1 ignored issue
show
Bug introduced by
The constant PR_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2506
2507
			// Open root folder
2508
			$userRoot = mapi_msgstore_openentry($userStore, null);
2509
2510
			// Get calendar entryID
2511
			$userRootProps = mapi_getprops($userRoot, [PR_STORE_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_FREEBUSY_ENTRYIDS]);
0 ignored issues
show
Bug introduced by
The constant PR_FREEBUSY_ENTRYIDS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_APPOINTMENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_STORE_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2512
2513
			// Open Calendar folder
2514
			$accessToFolder = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $accessToFolder is dead and can be removed.
Loading history...
2515
2516
			try {
2517
				// @FIXME this checks delegate has access to resource's calendar folder
2518
				// but it should use boss' credentials
2519
2520
				$accessToFolder = $this->checkCalendarWriteAccess($this->store);
2521
				if ($accessToFolder) {
2522
					$calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
2523
				}
2524
			}
2525
			catch (MAPIException $e) {
2526
				$e->setHandled();
2527
				$this->errorSetResource = 1; // No access
2528
			}
2529
2530
			if ($accessToFolder) {
2531
				/**
2532
				 * Get the LocalFreebusy message that contains the properties that
2533
				 * are set to accept or decline resource meeting requests.
2534
				 */
2535
				$localFreebusyMsg = FreeBusy::getLocalFreeBusyMessage($userStore);
2536
				if ($localFreebusyMsg) {
2537
					$props = mapi_getprops($localFreebusyMsg, [PR_SCHDINFO_AUTO_ACCEPT_APPTS, PR_SCHDINFO_DISALLOW_RECURRING_APPTS, PR_SCHDINFO_DISALLOW_OVERLAPPING_APPTS]);
0 ignored issues
show
Bug introduced by
The constant PR_SCHDINFO_DISALLOW_OVERLAPPING_APPTS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SCHDINFO_AUTO_ACCEPT_APPTS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SCHDINFO_DISALLOW_RECURRING_APPTS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2538
2539
					$acceptMeetingRequests = isset($props[PR_SCHDINFO_AUTO_ACCEPT_APPTS]) ? $props[PR_SCHDINFO_AUTO_ACCEPT_APPTS] : false;
2540
					$declineRecurringMeetingRequests = isset($props[PR_SCHDINFO_DISALLOW_RECURRING_APPTS]) ? $props[PR_SCHDINFO_DISALLOW_RECURRING_APPTS] : false;
2541
					$declineConflictingMeetingRequests = isset($props[PR_SCHDINFO_DISALLOW_OVERLAPPING_APPTS]) ? $props[PR_SCHDINFO_DISALLOW_OVERLAPPING_APPTS] : false;
2542
2543
					if (!$acceptMeetingRequests) {
2544
						/*
2545
						 * When a resource has not been set to automatically accept meeting requests,
2546
						 * the meeting request has to be sent to him rather than being put directly into
2547
						 * his calendar. No error should be returned.
2548
						 */
2549
						// $errorSetResource = 2;
2550
						$this->nonAcceptingResources[] = $resourceRecipients[$i];
2551
					}
2552
					else {
2553
						if ($declineRecurringMeetingRequests && !$cancel) {
2554
							// Check if appointment is recurring
2555
							if ($messageprops[$this->proptags['recurring']]) {
2556
								$this->errorSetResource = 3;
2557
							}
2558
						}
2559
						if ($declineConflictingMeetingRequests && !$cancel) {
2560
							// Check for conflicting items
2561
							if ($calFolder && $this->isMeetingConflicting($message, $userStore, $calFolder)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $calFolder does not seem to be defined for all execution paths leading up to this point.
Loading history...
Bug introduced by
$message of type resource is incompatible with the type MAPIMessage expected by parameter $message of Meetingrequest::isMeetingConflicting(). ( Ignorable by Annotation )

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

2561
							if ($calFolder && $this->isMeetingConflicting(/** @scrutinizer ignore-type */ $message, $userStore, $calFolder)) {
Loading history...
2562
								$this->errorSetResource = 4; // Conflict
2563
							}
2564
						}
2565
					}
2566
				}
2567
			}
2568
2569
			if (!$this->errorSetResource && $accessToFolder) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->errorSetResource of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2570
				/**
2571
				 * First search on GlobalID(0x3)
2572
				 * If (recurring and occurrence) If Resource was booked for only this occurrence then Resource should have only this occurrence in Calendar and not whole series.
2573
				 * If (normal meeting) then GlobalID(0x3) and CleanGlobalID(0x23) are same, so doesn't matter if search is based on GlobalID.
2574
				 */
2575
				$rows = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
2576
2577
				/*
2578
				 * If no entry is found then
2579
				 * 1) Resource doesn't have meeting in Calendar. Seriously!!
2580
				 * OR
2581
				 * 2) We were looking for occurrence item but Resource has whole series
2582
				 */
2583
				if (empty($rows)) {
2584
					/**
2585
					 * Now search on CleanGlobalID(0x23) WHY???
2586
					 * Because we are looking recurring item.
2587
					 *
2588
					 * Possible results of this search
2589
					 * 1) If Resource was booked for more than one occurrences then this search will return all those occurrence because search is perform on CleanGlobalID
2590
					 * 2) If Resource was booked for whole series then it should return series.
2591
					 */
2592
					$rows = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
2593
2594
					$newResourceMsg = false;
2595
					if (!empty($rows)) {
2596
						// Since we are looking for recurring item, open every result and check for 'recurring' property.
2597
						foreach ($rows as $row) {
2598
							$ResourceMsg = mapi_msgstore_openentry($userStore, $row);
2599
							$ResourceMsgProps = mapi_getprops($ResourceMsg, [$this->proptags['recurring']]);
2600
2601
							if (isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2602
								$newResourceMsg = $ResourceMsg;
2603
								break;
2604
							}
2605
						}
2606
					}
2607
2608
					// Still no results found. I giveup, create new message.
2609
					if (!$newResourceMsg) {
2610
						$newResourceMsg = mapi_folder_createmessage($calFolder);
2611
					}
2612
				}
2613
				else {
2614
					$newResourceMsg = mapi_msgstore_openentry($userStore, $rows[0]);
2615
				}
2616
2617
				// Prefix the subject if needed
2618
				if ($prefix && isset($messageprops[PR_SUBJECT])) {
0 ignored issues
show
Bug introduced by
The constant PR_SUBJECT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2619
					$messageprops[PR_SUBJECT] = $prefix . $messageprops[PR_SUBJECT];
2620
				}
2621
2622
				// Set status to cancelled if needed
2623
				$messageprops[$this->proptags['busystatus']] = fbBusy; // The default status (Busy)
2624
				if ($cancel) {
2625
					$messageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // The meeting has been canceled
2626
					$messageprops[$this->proptags['busystatus']] = fbFree; // Free
2627
				}
2628
				else {
2629
					$messageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
2630
				}
2631
				$messageprops[$this->proptags['responsestatus']] = olResponseAccepted; // The resource automatically accepts the appointment
2632
2633
				$messageprops[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...
2634
2635
				// Remove the PR_ICON_INDEX as it is not needed in the sent message.
2636
				$messageprops[PR_ICON_INDEX] = null;
0 ignored issues
show
Bug introduced by
The constant PR_ICON_INDEX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2637
				$messageprops[PR_RESPONSE_REQUESTED] = true;
0 ignored issues
show
Bug introduced by
The constant PR_RESPONSE_REQUESTED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2638
2639
				// get the store of organizer, in case of delegates it will be delegate store
2640
				$defaultStore = $this->openDefaultStore();
2641
2642
				$storeProps = mapi_getprops($this->store, [PR_ENTRYID]);
2643
				$defaultStoreProps = mapi_getprops($defaultStore, [PR_ENTRYID]);
2644
2645
				// @FIXME use entryid comparison functions here
2646
				if ($storeProps[PR_ENTRYID] !== $defaultStoreProps[PR_ENTRYID]) {
2647
					// get delegate information
2648
					$addrInfo = $this->getOwnerAddress($defaultStore, false);
2649
2650
					if ($addrInfo) {
2651
						list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrInfo;
2652
2653
						$messageprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2654
						$messageprops[PR_SENDER_NAME] = $ownername;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2655
						$messageprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2656
						$messageprops[PR_SENDER_ENTRYID] = $ownerentryid;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2657
						$messageprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2658
					}
2659
2660
					// get delegator information
2661
					$addrInfo = $this->getOwnerAddress($this->store, false);
2662
2663
					if ($addrInfo) {
2664
						list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrInfo;
2665
2666
						$messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2667
						$messageprops[PR_SENT_REPRESENTING_NAME] = $ownername;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2668
						$messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2669
						$messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2670
						$messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2671
					}
2672
				}
2673
				else {
2674
					// get organizer information
2675
					$addrinfo = $this->getOwnerAddress($this->store);
2676
2677
					if ($addrinfo) {
2678
						list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrinfo;
2679
2680
						$messageprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
2681
						$messageprops[PR_SENDER_NAME] = $ownername;
2682
						$messageprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
2683
						$messageprops[PR_SENDER_ENTRYID] = $ownerentryid;
2684
						$messageprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
2685
2686
						$messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
2687
						$messageprops[PR_SENT_REPRESENTING_NAME] = $ownername;
2688
						$messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
2689
						$messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
2690
						$messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
2691
					}
2692
				}
2693
2694
				$messageprops[$this->proptags['replytime']] = time();
2695
2696
				if ($basedate && isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2697
					$recurr = new Recurrence($userStore, $newResourceMsg);
0 ignored issues
show
Bug introduced by
$userStore of type MAPIStore is incompatible with the type resource expected by parameter $store of Recurrence::__construct(). ( Ignorable by Annotation )

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

2697
					$recurr = new Recurrence(/** @scrutinizer ignore-type */ $userStore, $newResourceMsg);
Loading history...
2698
2699
					// Copy recipients list
2700
					$reciptable = mapi_message_getrecipienttable($message);
2701
					$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2702
2703
					// add owner to recipient table
2704
					$this->addOrganizer($messageprops, $recips, true);
2705
2706
					// Update occurrence
2707
					if ($recurr->isException($basedate)) {
2708
						$recurr->modifyException($messageprops, $basedate, $recips);
2709
					}
2710
					else {
2711
						$recurr->createException($messageprops, $basedate, false, $recips);
2712
					}
2713
				}
2714
				else {
2715
					mapi_setprops($newResourceMsg, $messageprops);
2716
2717
					// Copy attachments
2718
					$this->replaceAttachments($message, $newResourceMsg);
2719
2720
					// Copy all recipients too
2721
					$this->replaceRecipients($message, $newResourceMsg);
0 ignored issues
show
Bug introduced by
$message of type resource is incompatible with the type MAPIMessage expected by parameter $copyFrom of Meetingrequest::replaceRecipients(). ( Ignorable by Annotation )

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

2721
					$this->replaceRecipients(/** @scrutinizer ignore-type */ $message, $newResourceMsg);
Loading history...
2722
2723
					// Now add organizer also to recipient table
2724
					$recips = [];
2725
					$this->addOrganizer($messageprops, $recips);
2726
2727
					mapi_message_modifyrecipients($newResourceMsg, MODRECIP_ADD, $recips);
2728
				}
2729
2730
				mapi_savechanges($newResourceMsg);
2731
2732
				$resourceRecipData[] = [
2733
					'store' => $userStore,
2734
					'folder' => $calFolder,
2735
					'msg' => $newResourceMsg,
2736
				];
2737
				$this->includesResources = true;
2738
			}
2739
			else {
2740
				/*
2741
				 * If no other errors occurred and you have no access to the
2742
				 * folder of the resource, throw an error=1.
2743
				 */
2744
				if (!$this->errorSetResource) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->errorSetResource of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2745
					$this->errorSetResource = 1;
2746
				}
2747
2748
				for ($j = 0, $len = count($resourceRecipData); $j < $len; ++$j) {
2749
					// Get the EntryID
2750
					$props = mapi_message_getprops($resourceRecipData[$j]['msg']);
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

2750
					$props = /** @scrutinizer ignore-call */ mapi_message_getprops($resourceRecipData[$j]['msg']);
Loading history...
2751
2752
					mapi_folder_deletemessages($resourceRecipData[$j]['folder'], [$props[PR_ENTRYID]], DELETE_HARD_DELETE);
2753
				}
2754
				$this->recipientDisplayname = $resourceRecipients[$i][PR_DISPLAY_NAME];
2755
			}
2756
			++$i;
2757
		}
2758
2759
		/*
2760
		 * Set the BCC-recipients (resources) tackstatus to accepted.
2761
		 */
2762
		// Get resource recipients
2763
		$getResourcesRestriction = [
2764
			RES_PROPERTY,
2765
			[
2766
				RELOP => RELOP_EQ,	// Equals recipient type 3: Resource
2767
				ULPROPTAG => PR_RECIPIENT_TYPE,
2768
				VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2769
			],
2770
		];
2771
		$recipienttable = mapi_message_getrecipienttable($message);
2772
		$resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
2773
		if (!empty($resourceRecipients)) {
2774
			// Set Tracking status of resource recipients to olResponseAccepted (3)
2775
			for ($i = 0, $len = count($resourceRecipients); $i < $len; ++$i) {
2776
				$resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusAccepted;
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...
2777
				$resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS_TIME] = 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...
2778
			}
2779
			mapi_message_modifyrecipients($message, MODRECIP_MODIFY, $resourceRecipients);
2780
		}
2781
2782
		return $resourceRecipData;
2783
	}
2784
2785
	/**
2786
	 * Function which save an exception into recurring item.
2787
	 *
2788
	 * @param resource $recurringItem  reference to MAPI_message of recurring item
2789
	 * @param resource $occurrenceItem reference to MAPI_message of occurrence
2790
	 * @param string   $basedate       basedate of occurrence
2791
	 * @param bool     $move           if true then occurrence item is deleted
2792
	 * @param bool     $tentative      true if user has tentatively accepted it or false if user has accepted it
2793
	 * @param bool     $userAction     true if user has manually responded to meeting request
2794
	 * @param resource $store          user store
2795
	 * @param bool     $isDelegate     true if delegate is processing this meeting request
2796
	 */
2797
	public function acceptException(&$recurringItem, &$occurrenceItem, $basedate, $move = false, $tentative, $userAction = false, $store, $isDelegate = false) {
2798
		$recurr = new Recurrence($store, $recurringItem);
2799
2800
		// Copy properties from meeting request
2801
		$exception_props = mapi_getprops($occurrenceItem);
2802
2803
		// Copy recipients list
2804
		$reciptable = mapi_message_getrecipienttable($occurrenceItem);
2805
		// If delegate, then do not add the delegate in recipients
2806
		if ($isDelegate) {
2807
			$delegate = mapi_getprops($this->message, [PR_RECEIVED_BY_EMAIL_ADDRESS]);
0 ignored issues
show
Bug introduced by
The constant PR_RECEIVED_BY_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2808
			$res = [
2809
				RES_PROPERTY,
2810
				[
2811
					RELOP => RELOP_NE,
2812
					ULPROPTAG => PR_EMAIL_ADDRESS,
0 ignored issues
show
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2813
					VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]],
2814
				],
2815
			];
2816
			$recips = mapi_table_queryallrows($reciptable, $this->recipprops, $res);
2817
		}
2818
		else {
2819
			$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2820
		}
2821
2822
		// add owner to recipient table
2823
		$this->addOrganizer($exception_props, $recips, true);
2824
2825
		// add delegator to meetings
2826
		if ($isDelegate) {
2827
			$this->addDelegator($exception_props, $recips);
2828
		}
2829
2830
		$exception_props[$this->proptags['meetingstatus']] = olMeetingReceived;
2831
		$exception_props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
2832
2833
		if (isset($exception_props[$this->proptags['intendedbusystatus']])) {
2834
			if ($tentative && $exception_props[$this->proptags['intendedbusystatus']] !== fbFree) {
2835
				$exception_props[$this->proptags['busystatus']] = fbTentative;
2836
			}
2837
			else {
2838
				$exception_props[$this->proptags['busystatus']] = $exception_props[$this->proptags['intendedbusystatus']];
2839
			}
2840
			// we already have intendedbusystatus value in $exception_props so no need to copy it
2841
		}
2842
		else {
2843
			$exception_props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
2844
		}
2845
2846
		if ($userAction) {
2847
			$addrInfo = $this->getOwnerAddress($this->store);
2848
2849
			// if user has responded then set replytime and name
2850
			$exception_props[$this->proptags['replytime']] = time();
2851
			if (!empty($addrInfo)) {
2852
				$exception_props[$this->proptags['apptreplyname']] = $addrInfo[0];
2853
			}
2854
		}
2855
2856
		if ($recurr->isException($basedate)) {
2857
			$recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
2858
		}
2859
		else {
2860
			$recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
0 ignored issues
show
Bug introduced by
$occurrenceItem of type resource is incompatible with the type mapi_message expected by parameter $copy_attach_from of Recurrence::createException(). ( Ignorable by Annotation )

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

2860
			$recurr->createException($exception_props, $basedate, false, $recips, /** @scrutinizer ignore-type */ $occurrenceItem);
Loading history...
Bug introduced by
$basedate of type string is incompatible with the type date expected by parameter $base_date of Recurrence::createException(). ( Ignorable by Annotation )

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

2860
			$recurr->createException($exception_props, /** @scrutinizer ignore-type */ $basedate, false, $recips, $occurrenceItem);
Loading history...
2861
		}
2862
2863
		// Move the occurrenceItem to the waste basket
2864
		if ($move) {
2865
			$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
0 ignored issues
show
Bug introduced by
It seems like $this->openDefaultStore() can also be of type false; however, parameter $store of Meetingrequest::openDefaultWastebasket() does only seem to accept MAPIStore, 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

2865
			$wastebasket = $this->openDefaultWastebasket(/** @scrutinizer ignore-type */ $this->openDefaultStore());
Loading history...
2866
			$sourcefolder = mapi_msgstore_openentry($store, $exception_props[PR_PARENT_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_PARENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2867
			mapi_folder_copymessages($sourcefolder, [$exception_props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1 ignored issue
show
Bug introduced by
The constant PR_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2868
		}
2869
2870
		mapi_savechanges($recurringItem);
2871
	}
2872
2873
	/**
2874
	 * Function which merges an exception mapi message to recurring message.
2875
	 * This will be used when we receive recurring meeting request and we already have an exception message
2876
	 * of same meeting in calendar and we need to remove that exception message and add it to attachment table
2877
	 * of recurring meeting.
2878
	 *
2879
	 * @param resource $recurringItem  reference to MAPI_message of recurring item
2880
	 * @param resource $occurrenceItem reference to MAPI_message of occurrence
2881
	 * @param string   $basedate       basedate of occurrence
2882
	 * @param resource $store          user store
2883
	 */
2884
	public function mergeException(&$recurringItem, &$occurrenceItem, $basedate, $store) {
2885
		$recurr = new Recurrence($store, $recurringItem);
2886
2887
		// Copy properties from meeting request
2888
		$exception_props = mapi_getprops($occurrenceItem);
2889
2890
		// Get recipient list from message and add it to exception attachment
2891
		$reciptable = mapi_message_getrecipienttable($occurrenceItem);
2892
		$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2893
2894
		if ($recurr->isException($basedate)) {
2895
			$recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
2896
		}
2897
		else {
2898
			$recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
0 ignored issues
show
Bug introduced by
$occurrenceItem of type resource is incompatible with the type mapi_message expected by parameter $copy_attach_from of Recurrence::createException(). ( Ignorable by Annotation )

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

2898
			$recurr->createException($exception_props, $basedate, false, $recips, /** @scrutinizer ignore-type */ $occurrenceItem);
Loading history...
Bug introduced by
$basedate of type string is incompatible with the type date expected by parameter $base_date of Recurrence::createException(). ( Ignorable by Annotation )

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

2898
			$recurr->createException($exception_props, /** @scrutinizer ignore-type */ $basedate, false, $recips, $occurrenceItem);
Loading history...
2899
		}
2900
2901
		// Move the occurrenceItem to the waste basket
2902
		$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
0 ignored issues
show
Bug introduced by
It seems like $this->openDefaultStore() can also be of type false; however, parameter $store of Meetingrequest::openDefaultWastebasket() does only seem to accept MAPIStore, 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

2902
		$wastebasket = $this->openDefaultWastebasket(/** @scrutinizer ignore-type */ $this->openDefaultStore());
Loading history...
2903
		$sourcefolder = mapi_msgstore_openentry($store, $exception_props[PR_PARENT_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_PARENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2904
		mapi_folder_copymessages($sourcefolder, [$exception_props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1 ignored issue
show
Bug introduced by
The constant PR_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2905
2906
		mapi_savechanges($recurringItem);
2907
	}
2908
2909
	/**
2910
	 * Function which submits meeting request based on arguments passed to it.
2911
	 *
2912
	 * @param resource $message        MAPI_message whose meeting request is to be sent
2913
	 * @param bool     $cancel         if true send request, else send cancellation
2914
	 * @param string   $prefix         subject prefix
2915
	 * @param int      $basedate       basedate for an occurrence
2916
	 * @param object   $recurObject    recurrence object of mr
2917
	 * @param bool     $copyExceptions When sending update mail for recurring item then we don't send exceptions in attachments
2918
	 * @param mixed    $modifiedRecips
2919
	 * @param mixed    $deletedRecips
2920
	 */
2921
	public function submitMeetingRequest($message, $cancel, $prefix, $basedate = false, $recurObject = false, $copyExceptions = true, $modifiedRecips = false, $deletedRecips = false) {
2922
		$newmessageprops = $messageprops = mapi_getprops($this->message);
2923
		$new = $this->createOutgoingMessage();
2924
2925
		// Copy the entire message into the new meeting request message
2926
		if ($basedate) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $basedate of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2927
			// messageprops contains properties of whole recurring series
2928
			// and newmessageprops contains properties of exception item
2929
			$newmessageprops = mapi_getprops($message);
2930
2931
			// Ensure that the correct basedate is set in the new message
2932
			$newmessageprops[$this->proptags['basedate']] = $basedate;
2933
2934
			// Set isRecurring to false, because this is an exception
2935
			$newmessageprops[$this->proptags['recurring']] = false;
2936
2937
			// set LID_IS_EXCEPTION to true
2938
			$newmessageprops[$this->proptags['is_exception']] = true;
2939
2940
			// Set to high importance
2941
			if ($cancel) {
2942
				$newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;
0 ignored issues
show
Bug introduced by
The constant PR_IMPORTANCE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2943
			}
2944
2945
			// Set startdate and enddate of exception
2946
			if ($cancel && $recurObject) {
2947
				$newmessageprops[$this->proptags['startdate']] = $recurObject->getOccurrenceStart($basedate);
2948
				$newmessageprops[$this->proptags['duedate']] = $recurObject->getOccurrenceEnd($basedate);
2949
			}
2950
2951
			// Set basedate in guid (0x3)
2952
			$newmessageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
2953
			$newmessageprops[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
2954
			$newmessageprops[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
0 ignored issues
show
Bug introduced by
The constant PR_OWNER_APPT_ID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2955
2956
			// Get deleted recipiets from exception msg
2957
			$restriction = [
2958
				RES_AND,
2959
				[
2960
					[
2961
						RES_BITMASK,
2962
						[
2963
							ULTYPE => BMR_NEZ,
2964
							ULPROPTAG => 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...
2965
							ULMASK => recipExceptionalDeleted,
2966
						],
2967
					],
2968
					[
2969
						RES_BITMASK,
2970
						[
2971
							ULTYPE => BMR_EQZ,
2972
							ULPROPTAG => PR_RECIPIENT_FLAGS,
2973
							ULMASK => recipOrganizer,
2974
						],
2975
					],
2976
				],
2977
			];
2978
2979
			// In direct-booking mode, we don't need to send cancellations to resources
2980
			if ($this->enableDirectBooking) {
2981
				$restriction[1][] = [
2982
					RES_PROPERTY,
2983
					[
2984
						RELOP => RELOP_NE,	// Does not equal recipient type: MAPI_BCC (Resource)
2985
						ULPROPTAG => 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...
2986
						VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2987
					],
2988
				];
2989
			}
2990
2991
			$recipienttable = mapi_message_getrecipienttable($message);
2992
			$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $restriction);
2993
2994
			if (!$deletedRecips) {
2995
				$deletedRecips = array_merge([], $recipients);
2996
			}
2997
			else {
2998
				$deletedRecips = array_merge($deletedRecips, $recipients);
2999
			}
3000
		}
3001
3002
		// Remove the PR_ICON_INDEX as it is not needed in the sent message.
3003
		$newmessageprops[PR_ICON_INDEX] = null;
0 ignored issues
show
Bug introduced by
The constant PR_ICON_INDEX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3004
		$newmessageprops[PR_RESPONSE_REQUESTED] = true;
0 ignored issues
show
Bug introduced by
The constant PR_RESPONSE_REQUESTED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3005
3006
		// PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
3007
		$newmessageprops[PR_START_DATE] = $newmessageprops[$this->proptags['startdate']];
0 ignored issues
show
Bug introduced by
The constant PR_START_DATE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3008
		$newmessageprops[PR_END_DATE] = $newmessageprops[$this->proptags['duedate']];
0 ignored issues
show
Bug introduced by
The constant PR_END_DATE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3009
3010
		// Set updatecounter/AppointmentSequenceNumber
3011
		// get the value of latest updatecounter for the whole series and use it
3012
		$newmessageprops[$this->proptags['updatecounter']] = $messageprops[$this->proptags['last_updatecounter']];
3013
3014
		$meetingTimeInfo = $this->getMeetingTimeInfo();
3015
3016
		if ($meetingTimeInfo) {
3017
			// Needs to unset PR_HTML and PR_RTF_COMPRESSED props
3018
			// because while canceling meeting requests with edit text
3019
			// will override the PR_BODY because body value is not consistent with
3020
			// PR_HTML and PR_RTF_COMPRESSED value so in this case PR_RTF_COMPRESSED will
3021
			// get priority which override the PR_BODY value.
3022
			unset($newmessageprops[PR_HTML], $newmessageprops[PR_RTF_COMPRESSED]);
0 ignored issues
show
Bug introduced by
The constant PR_RTF_COMPRESSED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_HTML was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3023
3024
			$newmessageprops[PR_BODY] = $meetingTimeInfo;
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...
3025
		}
3026
3027
		// Send all recurrence info in mail, if this is a recurrence meeting.
3028
		if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']]) {
3029
			if (!empty($messageprops[$this->proptags['recurring_pattern']])) {
3030
				$newmessageprops[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
3031
			}
3032
			$newmessageprops[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
3033
			$newmessageprops[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
3034
			$newmessageprops[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
3035
3036
			if ($recurObject) {
3037
				$this->generateRecurDates($recurObject, $messageprops, $newmessageprops);
3038
			}
3039
		}
3040
3041
		if (isset($newmessageprops[$this->proptags['counter_proposal']])) {
3042
			unset($newmessageprops[$this->proptags['counter_proposal']]);
3043
		}
3044
3045
		// Prefix the subject if needed
3046
		if ($prefix && isset($newmessageprops[PR_SUBJECT])) {
0 ignored issues
show
Bug introduced by
The constant PR_SUBJECT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3047
			$newmessageprops[PR_SUBJECT] = $prefix . $newmessageprops[PR_SUBJECT];
3048
		}
3049
3050
		if (isset($newmessageprops[$this->proptags['categories']]) &&
3051
			!empty($newmessageprops[$this->proptags['categories']])) {
3052
			unset($newmessageprops[$this->proptags['categories']]);
3053
		}
3054
		mapi_setprops($new, $newmessageprops);
3055
3056
		// Copy attachments
3057
		$this->replaceAttachments($message, $new, $copyExceptions);
3058
3059
		// Retrieve only those recipient who should receive this meeting request.
3060
		$stripResourcesRestriction = [
3061
			RES_AND,
3062
			[
3063
				[
3064
					RES_BITMASK,
3065
					[
3066
						ULTYPE => BMR_EQZ,
3067
						ULPROPTAG => PR_RECIPIENT_FLAGS,
3068
						ULMASK => recipExceptionalDeleted,
3069
					],
3070
				],
3071
				[
3072
					RES_BITMASK,
3073
					[
3074
						ULTYPE => BMR_EQZ,
3075
						ULPROPTAG => PR_RECIPIENT_FLAGS,
3076
						ULMASK => recipOrganizer,
3077
					],
3078
				],
3079
			],
3080
		];
3081
3082
		// In direct-booking mode, resources do not receive a meeting request
3083
		if ($this->enableDirectBooking) {
3084
			$stripResourcesRestriction[1][] = [
3085
				RES_PROPERTY,
3086
				[
3087
					RELOP => RELOP_NE,	// Does not equal recipient type: MAPI_BCC (Resource)
3088
					ULPROPTAG => PR_RECIPIENT_TYPE,
3089
					VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
3090
				],
3091
			];
3092
		}
3093
3094
		// If no recipients were explicitly provided, we will send the update to all
3095
		// recipients from the meeting.
3096
		if ($modifiedRecips === false) {
3097
			$recipienttable = mapi_message_getrecipienttable($message);
3098
			$modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
3099
3100
			if ($basedate && empty($modifiedRecips)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $basedate of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
3101
				// Retrieve full list
3102
				$recipienttable = mapi_message_getrecipienttable($this->message);
3103
				$modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops);
3104
3105
				// Save recipients in exceptions
3106
				mapi_message_modifyrecipients($message, MODRECIP_ADD, $modifiedRecips);
3107
3108
				// Now retrieve only those recipient who should receive this meeting request.
3109
				$modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
3110
			}
3111
		}
3112
3113
		// @TODO: handle nonAcceptingResources
3114
		/*
3115
		 * Add resource recipients that did not automatically accept the meeting request.
3116
		 * (note: meaning that they did not decline the meeting request)
3117
		 */ /*
3118
		for($i=0;$i<count($this->nonAcceptingResources);$i++){
3119
			$recipients[] = $this->nonAcceptingResources[$i];
3120
		}*/
3121
3122
		if (!empty($modifiedRecips)) {
3123
			// Strip out the sender/'owner' recipient
3124
			mapi_message_modifyrecipients($new, MODRECIP_ADD, $modifiedRecips);
3125
3126
			// Set some properties that are different in the sent request than
3127
			// in the item in our calendar
3128
3129
			// we should store busystatus value to intendedbusystatus property, because busystatus for outgoing meeting request
3130
			// should always be fbTentative
3131
			$newmessageprops[$this->proptags['intendedbusystatus']] = isset($newmessageprops[$this->proptags['busystatus']]) ? $newmessageprops[$this->proptags['busystatus']] : $messageprops[$this->proptags['busystatus']];
3132
			$newmessageprops[$this->proptags['busystatus']] = fbTentative; // The default status when not accepted
3133
			$newmessageprops[$this->proptags['responsestatus']] = olResponseNotResponded; // The recipient has not responded yet
3134
			$newmessageprops[$this->proptags['attendee_critical_change']] = time();
3135
			$newmessageprops[$this->proptags['owner_critical_change']] = time();
3136
			$newmessageprops[$this->proptags['meetingtype']] = mtgRequest;
3137
3138
			if ($cancel) {
3139
				$newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled';
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...
3140
				$newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
3141
				$newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
3142
			}
3143
			else {
3144
				$newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Request';
3145
				$newmessageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
3146
			}
3147
3148
			mapi_setprops($new, $newmessageprops);
3149
			mapi_savechanges($new);
3150
3151
			// Submit message to non-resource recipients
3152
			mapi_message_submitmessage($new);
3153
		}
3154
3155
		// Search through the deleted recipients, and see if any of them is also
3156
		// listed as a recipient to whom we have sent an update. As we don't
3157
		// want to send a cancellation message to recipients who will also receive
3158
		// an meeting update, we have to filter those recipients out.
3159
		if ($deletedRecips) {
3160
			$tmp = [];
3161
3162
			foreach ($deletedRecips as $delRecip) {
3163
				$found = false;
3164
3165
				// Search if the deleted recipient can be found inside
3166
				// the updated recipients as well.
3167
				foreach ($modifiedRecips as $recip) {
3168
					if ($this->compareABEntryIDs($recip[PR_ENTRYID], $delRecip[PR_ENTRYID])) {
1 ignored issue
show
Bug introduced by
The constant PR_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3169
						$found = true;
3170
						break;
3171
					}
3172
				}
3173
3174
				// If the recipient was not found, it truly is deleted,
3175
				// and we can safely send a cancellation message
3176
				if (!$found) {
3177
					$tmp[] = $delRecip;
3178
				}
3179
			}
3180
3181
			$deletedRecips = $tmp;
3182
		}
3183
3184
		// Send cancellation to deleted attendees
3185
		if ($deletedRecips && !empty($deletedRecips)) {
3186
			$new = $this->createOutgoingMessage();
3187
3188
			mapi_message_modifyrecipients($new, MODRECIP_ADD, $deletedRecips);
3189
3190
			$newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled';
3191
			$newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
3192
			$newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
3193
			$newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;	// HIGH Importance
3194
			if (isset($newmessageprops[PR_SUBJECT])) {
3195
				$newmessageprops[PR_SUBJECT] = dgettext('zarafa', 'Canceled') . ': ' . $newmessageprops[PR_SUBJECT];
3196
			}
3197
3198
			mapi_setprops($new, $newmessageprops);
3199
			mapi_savechanges($new);
3200
3201
			// Submit message to non-resource recipients
3202
			mapi_message_submitmessage($new);
3203
		}
3204
3205
		// Set properties on meeting object in calendar
3206
		// Set requestsent to 'true' (turns on 'tracking', etc)
3207
		$props = [];
3208
		$props[$this->proptags['meetingstatus']] = olMeeting;
3209
		$props[$this->proptags['responsestatus']] = olResponseOrganized;
3210
		// Only set the 'requestsent' property if it wasn't set previously yet,
3211
		// this ensures we will not accidentally set it from true to false.
3212
		if (!isset($messageprops[$this->proptags['requestsent']]) || $messageprops[$this->proptags['requestsent']] !== true) {
3213
			$props[$this->proptags['requestsent']] = !empty($modifiedRecips) || ($this->includesResources && !$this->errorSetResource);
3214
		}
3215
		$props[$this->proptags['attendee_critical_change']] = time();
3216
		$props[$this->proptags['owner_critical_change']] = time();
3217
		$props[$this->proptags['meetingtype']] = mtgRequest;
3218
		// save the new updatecounter to exception/recurring series/normal meeting
3219
		$props[$this->proptags['updatecounter']] = $newmessageprops[$this->proptags['updatecounter']];
3220
3221
		// PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
3222
		$props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
3223
		$props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
3224
3225
		mapi_setprops($message, $props);
3226
3227
		// saving of these properties on calendar item should be handled by caller function
3228
		// based on sending meeting request was successful or not
3229
	}
3230
3231
	/**
3232
	 * OL2007 uses these 4 properties to specify occurrence that should be updated.
3233
	 * ical generates RECURRENCE-ID property based on exception's basedate (PidLidExceptionReplaceTime),
3234
	 * but OL07 doesn't send this property, so ical will generate RECURRENCE-ID property based on date
3235
	 * from GlobalObjId and time from StartRecurTime property, so we are sending basedate property and
3236
	 * also additionally we are sending these properties.
3237
	 * Ref: MS-OXCICAL 2.2.1.20.20 Property: RECURRENCE-ID.
3238
	 *
3239
	 * @param object $recurObject     instance of recurrence class for this message
3240
	 * @param array  $messageprops    properties of meeting object that is going to be sent
3241
	 * @param array  $newmessageprops properties of meeting request/response that is going to be sent
3242
	 */
3243
	public function generateRecurDates($recurObject, $messageprops, &$newmessageprops) {
3244
		if ($messageprops[$this->proptags['startdate']] && $messageprops[$this->proptags['duedate']]) {
3245
			$startDate = date('Y:n:j:G:i:s', $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['startdate']]));
3246
			$endDate = date('Y:n:j:G:i:s', $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['duedate']]));
3247
3248
			$startDate = explode(':', $startDate);
3249
			$endDate = explode(':', $endDate);
3250
3251
			// [0] => year, [1] => month, [2] => day, [3] => hour, [4] => minutes, [5] => seconds
3252
			// RecurStartDate = year * 512 + month_number * 32 + day_number
3253
			$newmessageprops[$this->proptags['start_recur_date']] = (((int) $startDate[0]) * 512) + (((int) $startDate[1]) * 32) + ((int) $startDate[2]);
3254
			// RecurStartTime = hour * 4096 + minutes * 64 + seconds
3255
			$newmessageprops[$this->proptags['start_recur_time']] = (((int) $startDate[3]) * 4096) + (((int) $startDate[4]) * 64) + ((int) $startDate[5]);
3256
3257
			$newmessageprops[$this->proptags['end_recur_date']] = (((int) $endDate[0]) * 512) + (((int) $endDate[1]) * 32) + ((int) $endDate[2]);
3258
			$newmessageprops[$this->proptags['end_recur_time']] = (((int) $endDate[3]) * 4096) + (((int) $endDate[4]) * 64) + ((int) $endDate[5]);
3259
		}
3260
	}
3261
3262
	/**
3263
	 * Function will create a new outgoing message that will be used to send meeting mail.
3264
	 *
3265
	 * @param MAPIStore $store (optional) store that is used when creating response, if delegate is creating outgoing mail
3266
	 *                         then this would point to delegate store
3267
	 *
3268
	 * @return MAPIMessage outgoing mail that is created and can be used for sending it
3269
	 */
3270
	public function createOutgoingMessage($store = false) {
3271
		// get logged in user's store that will be used to send mail, for delegate this will be
3272
		// delegate store
3273
		$userStore = $this->openDefaultStore();
3274
3275
		$sentprops = [];
3276
		$outbox = $this->openDefaultOutbox($userStore);
0 ignored issues
show
Bug introduced by
It seems like $userStore can also be of type false; however, parameter $store of Meetingrequest::openDefaultOutbox() does only seem to accept MAPIStore, 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

3276
		$outbox = $this->openDefaultOutbox(/** @scrutinizer ignore-type */ $userStore);
Loading history...
3277
3278
		$outgoing = mapi_folder_createmessage($outbox);
3279
3280
		// check if $store is set and it is not equal to $defaultStore (means its the delegation case)
3281
		if ($store !== false) {
3282
			$storeProps = mapi_getprops($store, [PR_ENTRYID]);
1 ignored issue
show
Bug introduced by
The constant PR_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3283
			$userStoreProps = mapi_getprops($userStore, [PR_ENTRYID]);
3284
3285
			// @FIXME use entryid comparison functions here
3286
			if ($storeProps[PR_ENTRYID] !== $userStoreProps[PR_ENTRYID]) {
3287
				// get the delegator properties and set it into outgoing mail
3288
				$delegatorDetails = $this->getOwnerAddress($store, false);
3289
3290
				if ($delegatorDetails) {
0 ignored issues
show
introduced by
$delegatorDetails is of type properties, thus it always evaluated to true.
Loading history...
3291
					list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $delegatorDetails;
3292
					$sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3293
					$sentprops[PR_SENT_REPRESENTING_NAME] = $ownername;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3294
					$sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3295
					$sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3296
					$sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3297
				}
3298
3299
				// get the delegate properties and set it into outgoing mail
3300
				$delegateDetails = $this->getOwnerAddress($userStore, false);
3301
3302
				if ($delegateDetails) {
0 ignored issues
show
introduced by
$delegateDetails is of type properties, thus it always evaluated to true.
Loading history...
3303
					list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $delegateDetails;
3304
					$sentprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3305
					$sentprops[PR_SENDER_NAME] = $ownername;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3306
					$sentprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3307
					$sentprops[PR_SENDER_ENTRYID] = $ownerentryid;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3308
					$sentprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3309
				}
3310
			}
3311
		}
3312
		else {
3313
			// normal user is sending mail, so both set of properties will be same
3314
			$userDetails = $this->getOwnerAddress($userStore);
3315
3316
			if ($userDetails) {
0 ignored issues
show
introduced by
$userDetails is of type properties, thus it always evaluated to true.
Loading history...
3317
				list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $userDetails;
3318
				$sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
3319
				$sentprops[PR_SENT_REPRESENTING_NAME] = $ownername;
3320
				$sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
3321
				$sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
3322
				$sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
3323
3324
				$sentprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
3325
				$sentprops[PR_SENDER_NAME] = $ownername;
3326
				$sentprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
3327
				$sentprops[PR_SENDER_ENTRYID] = $ownerentryid;
3328
				$sentprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
3329
			}
3330
		}
3331
3332
		$sentprops[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID($userStore);
0 ignored issues
show
Bug introduced by
The constant PR_SENTMAIL_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
It seems like $userStore can also be of type false; however, parameter $store of Meetingrequest::getDefaultSentmailEntryID() does only seem to accept MAPIStore, 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

3332
		$sentprops[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID(/** @scrutinizer ignore-type */ $userStore);
Loading history...
3333
3334
		mapi_setprops($outgoing, $sentprops);
3335
3336
		return $outgoing;
3337
	}
3338
3339
	/**
3340
	 * Function which checks that meeting in attendee's calendar is already updated
3341
	 * and we are checking an old meeting request. This function also will update property
3342
	 * meetingtype to indicate that its out of date meeting request.
3343
	 *
3344
	 * @return bool true if meeting request is outofdate else false if it is new
3345
	 */
3346
	public function isMeetingOutOfDate() {
3347
		$result = false;
3348
3349
		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], $this->proptags['updatecounter'], $this->proptags['meetingtype'], $this->proptags['owner_critical_change']]);
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...
3350
3351
		if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS])) {
3352
			return $result;
3353
		}
3354
3355
		if (isset($props[$this->proptags['meetingtype']]) && ($props[$this->proptags['meetingtype']] & mtgOutOfDate) == mtgOutOfDate) {
3356
			return true;
3357
		}
3358
3359
		// get the basedate to check for exception
3360
		$basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]);
3361
3362
		$calendarItem = $this->getCorrespondentCalendarItem(true);
3363
3364
		// if basedate is provided and we could not find the item then it could be that we are checking
3365
		// an exception so get the exception and check it
3366
		if ($basedate && $calendarItem !== false) {
3367
			$exception = $this->getExceptionItem($calendarItem, $basedate);
0 ignored issues
show
Bug introduced by
$basedate of type true is incompatible with the type Unixtime expected by parameter $basedate of Meetingrequest::getExceptionItem(). ( Ignorable by Annotation )

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

3367
			$exception = $this->getExceptionItem($calendarItem, /** @scrutinizer ignore-type */ $basedate);
Loading history...
3368
3369
			if ($exception !== false) {
0 ignored issues
show
introduced by
The condition $exception !== false is always true.
Loading history...
3370
				// we are able to find the exception compare with it
3371
				$calendarItem = $exception;
3372
			}
3373
			// we are not able to find exception, could mean that a significant change has occurred on series
3374
				// and it deleted all exceptions, so compare with series
3375
				// $calendarItem already contains reference to series
3376
		}
3377
3378
		if ($calendarItem !== false) {
0 ignored issues
show
introduced by
The condition $calendarItem !== false is always true.
Loading history...
3379
			$calendarItemProps = mapi_getprops($calendarItem, [
3380
				$this->proptags['owner_critical_change'],
3381
				$this->proptags['updatecounter'],
3382
			]);
3383
3384
			$updateCounter = (isset($calendarItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]);
3385
3386
			$criticalChange = (isset($calendarItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $calendarItemProps[$this->proptags['owner_critical_change']]);
3387
3388
			if ($updateCounter || $criticalChange) {
3389
				// meeting request is out of date, set properties to indicate this
3390
				mapi_setprops($this->message, [$this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033]);
0 ignored issues
show
Bug introduced by
The constant PR_ICON_INDEX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3391
				mapi_savechanges($this->message);
3392
3393
				$result = true;
3394
			}
3395
		}
3396
3397
		return $result;
3398
	}
3399
3400
	/**
3401
	 * Function which checks that if we have received a meeting response for an updated meeting in organizer's calendar.
3402
	 *
3403
	 * @param Number $basedate basedate of the exception if we want to compare with exception
3404
	 *
3405
	 * @return bool true if meeting request is updated later
3406
	 */
3407
	public function isMeetingUpdated($basedate = false) {
3408
		$result = false;
3409
3410
		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['updatecounter']]);
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...
3411
3412
		if (!$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS])) {
3413
			return $result;
3414
		}
3415
3416
		$calendarItem = $this->getCorrespondentCalendarItem(true);
3417
3418
		if ($calendarItem !== false) {
0 ignored issues
show
introduced by
The condition $calendarItem !== false is always true.
Loading history...
3419
			// basedate is provided so open exception
3420
			if ($basedate !== false) {
3421
				$exception = $this->getExceptionItem($calendarItem, $basedate);
0 ignored issues
show
Bug introduced by
$basedate of type double|integer is incompatible with the type Unixtime expected by parameter $basedate of Meetingrequest::getExceptionItem(). ( Ignorable by Annotation )

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

3421
				$exception = $this->getExceptionItem($calendarItem, /** @scrutinizer ignore-type */ $basedate);
Loading history...
3422
3423
				if ($exception !== false) {
0 ignored issues
show
introduced by
The condition $exception !== false is always true.
Loading history...
3424
					// we are able to find the exception compare with it
3425
					$calendarItem = $exception;
3426
				}
3427
				// we are not able to find exception, could mean that a significant change has occurred on series
3428
					// and it deleted all exceptions, so compare with series
3429
					// $calendarItem already contains reference to series
3430
			}
3431
3432
			if ($calendarItem !== false) {
0 ignored issues
show
introduced by
The condition $calendarItem !== false is always true.
Loading history...
3433
				$calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['updatecounter']]);
3434
3435
				/*
3436
				 * if(message_counter < appointment_counter) meeting object is newer then meeting response (meeting is updated)
3437
				 * if(message_counter >= appointment_counter) meeting is not updated, do normal processing
3438
				 */
3439
				if (isset($calendarItemProps[$this->proptags['updatecounter']], $props[$this->proptags['updatecounter']])) {
3440
					if ($props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]) {
3441
						$result = true;
3442
					}
3443
				}
3444
			}
3445
		}
3446
3447
		return $result;
3448
	}
3449
3450
	/**
3451
	 * Checks if there has been any significant changes on appointment/meeting item.
3452
	 * Significant changes be:
3453
	 * 1) startdate has been changed
3454
	 * 2) duedate has been changed OR
3455
	 * 3) recurrence pattern has been created, modified or removed.
3456
	 *
3457
	 * @param array oldProps old props before an update
0 ignored issues
show
Bug introduced by
The type oldProps 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...
3458
	 * @param Number basedate basedate
3459
	 * @param bool isRecurrenceChanged for change in recurrence pattern.
0 ignored issues
show
Bug introduced by
The type isRecurrenceChanged 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...
3460
	 * isRecurrenceChanged true means Recurrence pattern has been changed, so clear all attendees response
3461
	 * @param mixed $oldProps
3462
	 * @param mixed $basedate
3463
	 * @param mixed $isRecurrenceChanged
3464
	 */
3465
	public function checkSignificantChanges($oldProps, $basedate, $isRecurrenceChanged = false) {
3466
		$message = null;
3467
		$attach = null;
3468
3469
		// If basedate is specified then we need to open exception message to clear recipient responses
3470
		if ($basedate) {
3471
			$recurrence = new Recurrence($this->store, $this->message);
3472
			if ($recurrence->isException($basedate)) {
3473
				$attach = $recurrence->getExceptionAttachment($basedate);
3474
				if ($attach) {
3475
					$message = mapi_attach_openobj($attach, MAPI_MODIFY);
3476
				}
3477
			}
3478
		}
3479
		else {
3480
			// use normal message or recurring series message
3481
			$message = $this->message;
3482
		}
3483
3484
		if (!$message) {
3485
			return;
3486
		}
3487
3488
		$newProps = mapi_getprops($message, [$this->proptags['startdate'], $this->proptags['duedate'], $this->proptags['updatecounter']]);
3489
3490
		// Check whether message is updated or not.
3491
		if (isset($newProps[$this->proptags['updatecounter']]) && $newProps[$this->proptags['updatecounter']] == 0) {
3492
			return;
3493
		}
3494
3495
		if (($newProps[$this->proptags['startdate']] != $oldProps[$this->proptags['startdate']]) ||
3496
			($newProps[$this->proptags['duedate']] != $oldProps[$this->proptags['duedate']]) ||
3497
			$isRecurrenceChanged) {
3498
			$this->clearRecipientResponse($message);
3499
3500
			mapi_setprops($message, [$this->proptags['owner_critical_change'] => time()]);
3501
3502
			mapi_savechanges($message);
3503
			if ($attach) { // Also save attachment Object.
3504
				mapi_savechanges($attach);
3505
			}
3506
		}
3507
	}
3508
3509
	/**
3510
	 * Clear responses of all attendees who have replied in past.
3511
	 *
3512
	 * @param MAPI_MESSAGE $message on which responses should be cleared
0 ignored issues
show
Bug introduced by
The type MAPI_MESSAGE 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...
3513
	 */
3514
	public function clearRecipientResponse($message) {
3515
		$recipTable = mapi_message_getrecipienttable($message);
3516
		$recipsRows = mapi_table_queryallrows($recipTable, $this->recipprops);
3517
3518
		foreach ($recipsRows as $recipient) {
3519
			if (($recipient[PR_RECIPIENT_FLAGS] & recipOrganizer) != recipOrganizer) {
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...
3520
				// Recipient is attendee, set the trackstatus to 'Not Responded'
3521
				$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
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...
3522
			}
3523
			else {
3524
				// Recipient is organizer, this is not possible, but for safety
3525
				// it is best to clear the trackstatus for him as well by setting
3526
				// the trackstatus to 'Organized'.
3527
				$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3528
			}
3529
			mapi_message_modifyrecipients($message, MODRECIP_MODIFY, [$recipient]);
3530
		}
3531
	}
3532
3533
	/**
3534
	 * Function returns correspondent calendar item attached with the meeting request/response/cancellation.
3535
	 * This will only check for actual MAPIMessages in calendar folder, so if a meeting request is
3536
	 * for exception then this function will return recurring series for that meeting request
3537
	 * after that you need to use getExceptionItem function to get exception item that will be
3538
	 * fetched from the attachment table of recurring series MAPIMessage.
3539
	 *
3540
	 * @param bool $open boolean to indicate the function should return entryid or MAPIMessage. Defaults to true.
3541
	 *
3542
	 * @return entryid or MAPIMessage resource of calendar item
0 ignored issues
show
Bug introduced by
The type entryid 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...
3543
	 */
3544
	public function getCorrespondentCalendarItem($open = true) {
3545
		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3546
3547
		if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS]) && !$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS]) && !$this->isMeetingCancellation($props[PR_MESSAGE_CLASS])) {
3548
			// can work only with meeting requests/responses/cancellations
3549
			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 entryid.
Loading history...
3550
		}
3551
3552
		$globalId = $props[$this->proptags['goid']];
3553
		$cleanGlobalId = $props[$this->proptags['goid2']];
3554
3555
		// If Delegate is processing Meeting Request/Response for Delegator then retrieve Delegator's store and calendar.
3556
		if (isset($props[PR_RCVD_REPRESENTING_ENTRYID])) {
3557
			$delegatorStore = $this->getDelegatorStore($props[PR_RCVD_REPRESENTING_ENTRYID], [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...
3558
3559
			$store = $delegatorStore['store'];
3560
			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
3561
		}
3562
		else {
3563
			$store = $this->store;
3564
			$calFolder = $this->openDefaultCalendar();
3565
		}
3566
3567
		$basedate = $this->getBasedateFromGlobalID($globalId);
3568
3569
		/**
3570
		 * First search for any appointments which correspond to the $globalId,
3571
		 * this can be the entire series (if the Meeting Request refers to the
3572
		 * entire series), or an particular Occurrence (if the meeting Request
3573
		 * contains a basedate).
3574
		 *
3575
		 * If we cannot find a corresponding item, and the $globalId contains
3576
		 * a $basedate, it might imply that a new exception will have to be
3577
		 * created for a series which is present in the calendar, we can look
3578
		 * that one up by searching for the $cleanGlobalId.
3579
		 */
3580
		$entryids = $this->findCalendarItems($globalId, $calFolder);
3581
		if ($basedate && empty($entryids)) {
3582
			$entryids = $this->findCalendarItems($cleanGlobalId, $calFolder, true);
3583
		}
3584
3585
		// there should be only one item returned
3586
		if (!empty($entryids) && count($entryids) === 1) {
3587
			// return only entryid
3588
			if ($open === false) {
3589
				return $entryids[0];
3590
			}
3591
3592
			// open calendar item and return it
3593
			return mapi_msgstore_openentry($store, $entryids[0]);
3594
		}
3595
3596
		// no items found in calendar
3597
		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 entryid.
Loading history...
3598
	}
3599
3600
	/**
3601
	 * Function returns exception item based on the basedate passed.
3602
	 *
3603
	 * @param MAPIMessage $recurringMessage Resource of Recurring meeting from calendar
3604
	 * @param Unixtime    $basedate         basedate of exception that needs to be returned
0 ignored issues
show
Bug introduced by
The type Unixtime 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...
3605
	 * @param MAPIStore   $store            store that contains the recurring calendar item
3606
	 *
3607
	 * @return entryid or MAPIMessage resource of exception item
3608
	 */
3609
	public function getExceptionItem($recurringMessage, $basedate, $store = false) {
3610
		$occurItem = false;
3611
3612
		$props = mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID, $this->proptags['recurring']]);
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3613
3614
		// check if the passed item is recurring series
3615
		if (isset($props[$this->proptags['recurring']]) && $props[$this->proptags['recurring']] !== false) {
3616
			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 entryid.
Loading history...
3617
		}
3618
3619
		if ($store === false) {
3620
			// If Delegate is processing Meeting Request/Response for Delegator then retrieve Delegator's store and calendar.
3621
			if (isset($props[PR_RCVD_REPRESENTING_ENTRYID])) {
3622
				$delegatorStore = $this->getDelegatorStore($props[PR_RCVD_REPRESENTING_ENTRYID]);
3623
				$store = $delegatorStore['store'];
3624
			}
3625
			else {
3626
				$store = $this->store;
3627
			}
3628
		}
3629
3630
		$recurr = new Recurrence($store, $recurringMessage);
0 ignored issues
show
Bug introduced by
$recurringMessage of type MAPIMessage is incompatible with the type resource expected by parameter $message of Recurrence::__construct(). ( Ignorable by Annotation )

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

3630
		$recurr = new Recurrence($store, /** @scrutinizer ignore-type */ $recurringMessage);
Loading history...
Bug introduced by
It seems like $store can also be of type MAPIStore; however, parameter $store of Recurrence::__construct() does only seem to accept resource, 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

3630
		$recurr = new Recurrence(/** @scrutinizer ignore-type */ $store, $recurringMessage);
Loading history...
3631
		$attach = $recurr->getExceptionAttachment($basedate);
3632
		if ($attach) {
3633
			$occurItem = mapi_attach_openobj($attach);
3634
		}
3635
3636
		return $occurItem;
3637
	}
3638
3639
	/**
3640
	 * Function which checks whether received meeting request is either conflicting with other appointments or not.
3641
	 *
3642
	 * @param MAPIMessage $message   meeting request item that should be checked for conflicts in calendar
3643
	 * @param MAPIStore   $userStore store containing calendar folder that will be used for confilict checking
3644
	 * @param MAPIFolder  $calFolder calendar folder for conflict checking
3645
	 *
3646
	 * @return mixed(boolean/integer) true if normal meeting is conflicting or an integer which specifies no of instances
3647
	 * conflict of recurring meeting and false if meeting is not conflicting
3648
	 * @return mixed if boolean then true/false for indicating conflict, if number then items that are conflicting with the message
3649
	 */
3650
	public function isMeetingConflicting($message = false, $userStore = false, $calFolder = false) {
3651
		$returnValue = false;
3652
		$noOfInstances = 0;
3653
3654
		if ($message === false) {
3655
			$message = $this->message;
3656
		}
3657
3658
		$messageProps = mapi_getprops(
3659
			$message,
3660
			[
3661
				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...
3662
				$this->proptags['goid'],
3663
				$this->proptags['goid2'],
3664
				$this->proptags['startdate'],
3665
				$this->proptags['duedate'],
3666
				$this->proptags['recurring'],
3667
				$this->proptags['clipstart'],
3668
				$this->proptags['clipend'],
3669
				PR_RCVD_REPRESENTING_ENTRYID,
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3670
				$this->proptags['basedate'],
3671
				PR_RCVD_REPRESENTING_NAME,
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3672
			]
3673
		);
3674
3675
		if ($userStore === false) {
3676
			$userStore = $this->store;
3677
3678
			// check if delegate is processing the response
3679
			if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) {
3680
				$delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID], [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...
3681
3682
				$userStore = $delegatorStore['store'];
3683
				$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
3684
			}
3685
		}
3686
3687
		if ($calFolder === false) {
3688
			$calFolder = $this->openDefaultCalendar($userStore);
3689
		}
3690
3691
		if ($calFolder) {
3692
			// Meeting request is recurring, so get all occurrence and check for each occurrence whether it conflicts with other appointments in Calendar.
3693
			if (isset($messageProps[$this->proptags['recurring']]) && $messageProps[$this->proptags['recurring']] === true) {
3694
				// Apply recurrence class and retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date')
3695
				$recurr = new Recurrence($userStore, $message);
0 ignored issues
show
Bug introduced by
It seems like $userStore can also be of type MAPIStore; however, parameter $store of Recurrence::__construct() does only seem to accept resource, 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

3695
				$recurr = new Recurrence(/** @scrutinizer ignore-type */ $userStore, $message);
Loading history...
Bug introduced by
It seems like $message can also be of type MAPIMessage; however, parameter $message of Recurrence::__construct() does only seem to accept resource, 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

3695
				$recurr = new Recurrence($userStore, /** @scrutinizer ignore-type */ $message);
Loading history...
3696
				$items = $recurr->getItems($messageProps[$this->proptags['clipstart']], $messageProps[$this->proptags['clipend']] * (24 * 24 * 60), 30);
0 ignored issues
show
Bug introduced by
$messageProps[$this->pro...ipend']] * 24 * 24 * 60 of type integer is incompatible with the type date expected by parameter $end of BaseRecurrence::getItems(). ( Ignorable by Annotation )

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

3696
				$items = $recurr->getItems($messageProps[$this->proptags['clipstart']], /** @scrutinizer ignore-type */ $messageProps[$this->proptags['clipend']] * (24 * 24 * 60), 30);
Loading history...
3697
3698
				foreach ($items as $item) {
3699
					// Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
3700
					$calendarItems = $recurr->getCalendarItems($userStore, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus']]);
3701
3702
					foreach ($calendarItems as $calendarItem) {
3703
						if ($calendarItem[$this->proptags['busystatus']] !== fbFree) {
3704
							/*
3705
							 * Only meeting requests have globalID, normal appointments do not have globalID
3706
							 * so if any normal appointment if found then it is assumed to be conflict.
3707
							 */
3708
							if (isset($calendarItem[$this->proptags['goid']])) {
3709
								if ($calendarItem[$this->proptags['goid']] !== $messageProps[$this->proptags['goid']]) {
3710
									++$noOfInstances;
3711
									break;
3712
								}
3713
							}
3714
							else {
3715
								++$noOfInstances;
3716
								break;
3717
							}
3718
						}
3719
					}
3720
				}
3721
3722
				if ($noOfInstances > 0) {
3723
					$returnValue = $noOfInstances;
3724
				}
3725
			}
3726
			else {
3727
				// Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
3728
				$items = getCalendarItems($userStore, $calFolder, $messageProps[$this->proptags['startdate']], $messageProps[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus']]);
3729
3730
				if (isset($messageProps[$this->proptags['basedate']]) && !empty($messageProps[$this->proptags['basedate']])) {
3731
					$basedate = $messageProps[$this->proptags['basedate']];
3732
					// Get the goid2 from recurring MR which further used to
3733
					// check the resource conflicts item.
3734
					$recurrItemProps = mapi_getprops($this->message, [$this->proptags['goid2']]);
3735
					$messageProps[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid2']], $basedate);
3736
					$messageProps[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']];
3737
				}
3738
3739
				foreach ($items as $item) {
3740
					if ($item[$this->proptags['busystatus']] !== fbFree) {
3741
						if (isset($item[$this->proptags['goid']])) {
3742
							if (($item[$this->proptags['goid']] !== $messageProps[$this->proptags['goid']]) &&
3743
								($item[$this->proptags['goid']] !== $messageProps[$this->proptags['goid2']])) {
3744
								$returnValue = true;
3745
								break;
3746
							}
3747
						}
3748
						else {
3749
							$returnValue = true;
3750
							break;
3751
						}
3752
					}
3753
				}
3754
			}
3755
		}
3756
3757
		return $returnValue;
3758
	}
3759
3760
	/**
3761
	 *  Function which adds organizer to recipient list which is passed.
3762
	 *  This function also checks if it has organizer.
3763
	 *
3764
	 * @param array $messageProps message properties
3765
	 * @param array $recipients   recipients list of message
3766
	 * @param bool  $isException  true if we are processing recipient of exception
3767
	 */
3768
	public function addDelegator($messageProps, &$recipients) {
3769
		$hasDelegator = false;
3770
		// Check if meeting already has an organizer.
3771
		foreach ($recipients as $key => $recipient) {
3772
			if (isset($messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) && $recipient[PR_EMAIL_ADDRESS] == $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) {
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_EMAIL_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...
3773
				$hasDelegator = true;
3774
			}
3775
		}
3776
3777
		if (!$hasDelegator) {
3778
			// Create delegator.
3779
			$delegator = [];
3780
			$delegator[PR_ENTRYID] = $messageProps[PR_RCVD_REPRESENTING_ENTRYID];
1 ignored issue
show
Bug introduced by
The constant PR_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_RCVD_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3781
			$delegator[PR_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_NAME 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...
3782
			$delegator[PR_EMAIL_ADDRESS] = $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS];
3783
			$delegator[PR_RECIPIENT_TYPE] = MAPI_TO;
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...
3784
			$delegator[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_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...
3785
			$delegator[PR_ADDRTYPE] = empty($messageProps[PR_RCVD_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_RCVD_REPRESENTING_ADDRTYPE];
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_ADDRTYPE 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...
3786
			$delegator[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
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...
3787
			$delegator[PR_RECIPIENT_FLAGS] = recipSendable;
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...
3788
			$delegator[PR_SEARCH_KEY] = $messageProps[PR_RCVD_REPRESENTING_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...
Bug introduced by
The constant PR_RCVD_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3789
3790
			// Add organizer to recipients list.
3791
			array_unshift($recipients, $delegator);
3792
		}
3793
	}
3794
3795
	/**
3796
	 * Function will return delegator's store and calendar folder for processing meetings.
3797
	 *
3798
	 * @param string $receivedRepresentingEnryid  entryid of the delegator user
3799
	 * @param array  $foldersToOpen               contains list of folder types that should be returned in result
3800
	 * @param mixed  $receivedRepresentingEntryId
3801
	 *
3802
	 * @return array contains store of the delegator and resource of folders if $foldersToOpen is not empty
3803
	 */
3804
	public function getDelegatorStore($receivedRepresentingEntryId, $foldersToOpen = []) {
3805
		$returnData = [];
3806
3807
		$delegatorStore = $this->openCustomUserStore($receivedRepresentingEntryId);
3808
		$returnData['store'] = $delegatorStore;
3809
3810
		if (!empty($foldersToOpen)) {
3811
			for ($index = 0, $len = count($foldersToOpen); $index < $len; ++$index) {
3812
				$folderType = $foldersToOpen[$index];
3813
3814
				// first try with default folders
3815
				$folder = $this->openDefaultFolder($folderType, $delegatorStore);
3816
3817
				// if folder not found then try with base folders
3818
				if ($folder === false) {
3819
					$folder = $this->openBaseFolder($folderType, $delegatorStore);
3820
				}
3821
3822
				if ($folder === false) {
3823
					// we are still not able to get the folder so give up
3824
					continue;
3825
				}
3826
3827
				$returnData[$folderType] = $folder;
3828
			}
3829
		}
3830
3831
		return $returnData;
3832
	}
3833
3834
	/**
3835
	 * Function returns extra info about meeting timing along with message body
3836
	 * which will be included in body while sending meeting request/response.
3837
	 *
3838
	 * @return string $meetingTimeInfo info about meeting timing along with message body
3839
	 */
3840
	public function getMeetingTimeInfo() {
3841
		return $this->meetingTimeInfo;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->meetingTimeInfo returns the type boolean which is incompatible with the documented return type string.
Loading history...
3842
	}
3843
3844
	/**
3845
	 * Function sets extra info about meeting timing along with message body
3846
	 * which will be included in body while sending meeting request/response.
3847
	 *
3848
	 * @param string $meetingTimeInfo info about meeting timing along with message body
3849
	 */
3850
	public function setMeetingTimeInfo($meetingTimeInfo) {
3851
		$this->meetingTimeInfo = $meetingTimeInfo;
3852
	}
3853
3854
	/**
3855
	 * Helper function which is use to get local categories of all occurrence.
3856
	 *
3857
	 * @param MAPIMessage $calendarItem meeting request item
3858
	 * @param MAPIStore   $store        store containing calendar folder
3859
	 * @param MAPIFolder  $calFolder    calendar folder
3860
	 *
3861
	 * @return array $localCategories which contain array of basedate along with categories
3862
	 */
3863
	public function getLocalCategories($calendarItem, $store, $calFolder) {
3864
		$calendarItemProps = mapi_getprops($calendarItem);
3865
		$recurrence = new Recurrence($store, $calendarItem);
0 ignored issues
show
Bug introduced by
$store of type MAPIStore is incompatible with the type resource expected by parameter $store of Recurrence::__construct(). ( Ignorable by Annotation )

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

3865
		$recurrence = new Recurrence(/** @scrutinizer ignore-type */ $store, $calendarItem);
Loading history...
Bug introduced by
$calendarItem of type MAPIMessage is incompatible with the type resource expected by parameter $message of Recurrence::__construct(). ( Ignorable by Annotation )

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

3865
		$recurrence = new Recurrence($store, /** @scrutinizer ignore-type */ $calendarItem);
Loading history...
3866
3867
		// Retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date')
3868
		$items = $recurrence->getItems($calendarItemProps[$this->proptags['clipstart']], $calendarItemProps[$this->proptags['clipend']] * (24 * 24 * 60), 30);
0 ignored issues
show
Bug introduced by
$calendarItemProps[$this...ipend']] * 24 * 24 * 60 of type integer is incompatible with the type date expected by parameter $end of BaseRecurrence::getItems(). ( Ignorable by Annotation )

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

3868
		$items = $recurrence->getItems($calendarItemProps[$this->proptags['clipstart']], /** @scrutinizer ignore-type */ $calendarItemProps[$this->proptags['clipend']] * (24 * 24 * 60), 30);
Loading history...
3869
		$localCategories = [];
3870
3871
		foreach ($items as $item) {
3872
			$recurrenceItems = $recurrence->getCalendarItems($store, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus'], $this->proptags['categories']]);
3873
			foreach ($recurrenceItems as $recurrenceItem) {
3874
				// Check if occurrence is exception then get the local categories of that occurrence.
3875
				if (isset($recurrenceItem[$this->proptags['goid']]) && $recurrenceItem[$this->proptags['goid']] == $calendarItemProps[$this->proptags['goid']]) {
3876
					$exceptionAttach = $recurrence->getExceptionAttachment($recurrenceItem['basedate']);
3877
3878
					if ($exceptionAttach) {
3879
						$exception = mapi_attach_openobj($exceptionAttach, 0);
3880
						$exceptionProps = mapi_getprops($exception, [$this->proptags['categories']]);
3881
						if (isset($exceptionProps[$this->proptags['categories']])) {
3882
							$localCategories[$recurrenceItem['basedate']] = $exceptionProps[$this->proptags['categories']];
3883
						}
3884
					}
3885
				}
3886
			}
3887
		}
3888
3889
		return $localCategories;
3890
	}
3891
3892
	/**
3893
	 * Helper function which is use to apply local categories on respective occurrences.
3894
	 *
3895
	 * @param MAPIMessage $calendarItem    meeting request item
3896
	 * @param MAPIStore   $store           store containing calendar folder
3897
	 * @param array       $localCategories array contains basedate and array of categories
3898
	 */
3899
	public function applyLocalCategories($calendarItem, $store, $localCategories) {
3900
		$calendarItemProps = mapi_getprops($calendarItem, [PR_PARENT_ENTRYID, PR_ENTRYID]);
1 ignored issue
show
Bug introduced by
The constant PR_ENTRYID 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...
3901
		$message = mapi_msgstore_openentry($store, $calendarItemProps[PR_ENTRYID]);
3902
		$recurrence = new Recurrence($store, $message);
0 ignored issues
show
Bug introduced by
$store of type MAPIStore is incompatible with the type resource expected by parameter $store of Recurrence::__construct(). ( Ignorable by Annotation )

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

3902
		$recurrence = new Recurrence(/** @scrutinizer ignore-type */ $store, $message);
Loading history...
3903
3904
		// Check for all occurrence if it is exception then modify the exception by setting up categories,
3905
		// Otherwise create new exception with categories.
3906
		foreach ($localCategories as $key => $value) {
3907
			if ($recurrence->isException($key)) {
3908
				$recurrence->modifyException([$this->proptags['categories'] => $value], $key);
3909
			}
3910
			else {
3911
				$recurrence->createException([$this->proptags['categories'] => $value], $key, false);
3912
			}
3913
			mapi_savechanges($message);
3914
		}
3915
	}
3916
}
3917