Passed
Push — master ( f66da6...7a87af )
by
unknown
02:36 queued 15s
created

class.meetingrequest.php (144 issues)

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-2024 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,
79
		PR_DISPLAY_NAME,
80
		PR_EMAIL_ADDRESS,
81
		PR_RECIPIENT_ENTRYID,
82
		PR_RECIPIENT_TYPE,
83
		PR_SEND_INTERNET_ENCODING,
84
		PR_SEND_RICH_INFO,
85
		PR_RECIPIENT_DISPLAY_NAME,
86
		PR_ADDRTYPE,
87
		PR_DISPLAY_TYPE,
88
		PR_DISPLAY_TYPE_EX,
89
		PR_RECIPIENT_TRACKSTATUS,
90
		PR_RECIPIENT_TRACKSTATUS_TIME,
91
		PR_RECIPIENT_FLAGS,
92
		PR_ROWID,
93
		PR_OBJECT_TYPE,
94
		PR_SEARCH_KEY,
95
		PR_SMTP_ADDRESS,
96
	];
97
98
	/**
99
	 * Indication whether the setting of resources in a Meeting Request is success (false) or if it
100
	 * has failed (integer).
101
	 *
102
	 * @var null|false|int
103
	 *
104
	 * @psalm-var 1|3|4|false|null
105
	 */
106
	public $errorSetResource;
107
108
	public $proptags;
109
	private $store;
110
	public $message;
111
	private $session;
112
113
	/**
114
	 * @var false|string
115
	 */
116
	private $meetingTimeInfo;
117
	private $enableDirectBooking;
118
119
	/**
120
	 * @var null|bool
121
	 */
122
	private $includesResources;
123
	private $nonAcceptingResources;
124
	private $recipientDisplayname;
125
126
	/**
127
	 * Constructor.
128
	 *
129
	 * Takes a store and a message. The message is an appointment item
130
	 * that should be converted into a meeting request or an incoming
131
	 * e-mail message that is a meeting request.
132
	 *
133
	 * The $session variable is optional, but required if the following features
134
	 * are to be used:
135
	 *
136
	 * - Sending meeting requests for meetings that are not in your own store
137
	 * - Sending meeting requests to resources, resource availability checking and resource freebusy updates
138
	 *
139
	 * @param mixed $store
140
	 * @param mixed $message
141
	 * @param mixed $session
142
	 * @param mixed $enableDirectBooking
143
	 */
144
	public function __construct($store, $message, $session = false, $enableDirectBooking = true) {
145
		$this->store = $store;
146
		$this->message = $message;
147
		$this->session = $session;
148
		// This variable string saves time information for the MR.
149
		$this->meetingTimeInfo = false;
150
		$this->enableDirectBooking = $enableDirectBooking;
151
152
		$properties = [];
153
		$properties['goid'] = 'PT_BINARY:PSETID_Meeting:0x3';
154
		$properties['goid2'] = 'PT_BINARY:PSETID_Meeting:0x23';
155
		$properties['type'] = 'PT_STRING8:PSETID_Meeting:0x24';
156
		$properties['meetingrecurring'] = 'PT_BOOLEAN:PSETID_Meeting:0x5';
157
		$properties['unknown2'] = 'PT_BOOLEAN:PSETID_Meeting:0xa';
158
		$properties['attendee_critical_change'] = 'PT_SYSTIME:PSETID_Meeting:0x1';
159
		$properties['owner_critical_change'] = 'PT_SYSTIME:PSETID_Meeting:0x1a';
160
		$properties['meetingstatus'] = 'PT_LONG:PSETID_Appointment:' . PidLidAppointmentStateFlags;
161
		$properties['responsestatus'] = 'PT_LONG:PSETID_Appointment:0x8218';
162
		$properties['unknown6'] = 'PT_LONG:PSETID_Meeting:0x4';
163
		$properties['replytime'] = 'PT_SYSTIME:PSETID_Appointment:0x8220';
164
		$properties['usetnef'] = 'PT_BOOLEAN:PSETID_Common:0x8582';
165
		$properties['recurrence_data'] = 'PT_BINARY:PSETID_Appointment:' . PidLidAppointmentRecur;
166
		$properties['reminderminutes'] = 'PT_LONG:PSETID_Common:' . PidLidReminderDelta;
167
		$properties['reminderset'] = 'PT_BOOLEAN:PSETID_Common:' . PidLidReminderSet;
168
		$properties['sendasical'] = 'PT_BOOLEAN:PSETID_Appointment:0x8200';
169
		$properties['updatecounter'] = 'PT_LONG:PSETID_Appointment:' . PidLidAppointmentSequence;					// AppointmentSequenceNumber
170
		$properties['unknown7'] = 'PT_LONG:PSETID_Appointment:0x8202';
171
		$properties['last_updatecounter'] = 'PT_LONG:PSETID_Appointment:0x8203';			// AppointmentLastSequence
172
		$properties['busystatus'] = 'PT_LONG:PSETID_Appointment:' . PidLidBusyStatus;
173
		$properties['intendedbusystatus'] = 'PT_LONG:PSETID_Appointment:' . PidLidIntendedBusyStatus;
174
		$properties['start'] = 'PT_SYSTIME:PSETID_Appointment:' . PidLidAppointmentStartWhole;
175
		$properties['responselocation'] = 'PT_STRING8:PSETID_Meeting:0x2';
176
		$properties['location'] = 'PT_STRING8:PSETID_Appointment:' . PidLidLocation;
177
		$properties['requestsent'] = 'PT_BOOLEAN:PSETID_Appointment:0x8229';		// PidLidFInvited, MeetingRequestWasSent
178
		$properties['startdate'] = 'PT_SYSTIME:PSETID_Appointment:' . PidLidAppointmentStartWhole;
179
		$properties['duedate'] = 'PT_SYSTIME:PSETID_Appointment:' . PidLidAppointmentEndWhole;
180
		$properties['flagdueby'] = 'PT_SYSTIME:PSETID_Common:' . PidLidReminderSignalTime;
181
		$properties['commonstart'] = 'PT_SYSTIME:PSETID_Common:0x8516';
182
		$properties['commonend'] = 'PT_SYSTIME:PSETID_Common:0x8517';
183
		$properties['recurring'] = 'PT_BOOLEAN:PSETID_Appointment:' . PidLidRecurring;
184
		$properties['clipstart'] = 'PT_SYSTIME:PSETID_Appointment:' . PidLidClipStart;
185
		$properties['clipend'] = 'PT_SYSTIME:PSETID_Appointment:' . PidLidClipEnd;
186
		$properties['start_recur_date'] = 'PT_LONG:PSETID_Meeting:0xD';				// StartRecurTime
187
		$properties['start_recur_time'] = 'PT_LONG:PSETID_Meeting:0xE';				// StartRecurTime
188
		$properties['end_recur_date'] = 'PT_LONG:PSETID_Meeting:0xF';				// EndRecurDate
189
		$properties['end_recur_time'] = 'PT_LONG:PSETID_Meeting:0x10';				// EndRecurTime
190
		$properties['is_exception'] = 'PT_BOOLEAN:PSETID_Meeting:0xA';				// LID_IS_EXCEPTION
191
		$properties['apptreplyname'] = 'PT_STRING8:PSETID_Appointment:0x8230';
192
		// Propose new time properties
193
		$properties['proposed_start_whole'] = 'PT_SYSTIME:PSETID_Appointment:' . PidLidAppointmentProposedStartWhole;
194
		$properties['proposed_end_whole'] = 'PT_SYSTIME:PSETID_Appointment:' . PidLidAppointmentProposedEndWhole;
195
		$properties['proposed_duration'] = 'PT_LONG:PSETID_Appointment:0x8256';
196
		$properties['counter_proposal'] = 'PT_BOOLEAN:PSETID_Appointment:' . PidLidAppointmentCounterProposal;
197
		$properties['recurring_pattern'] = 'PT_STRING8:PSETID_Appointment:0x8232';
198
		$properties['basedate'] = 'PT_SYSTIME:PSETID_Appointment:' . PidLidExceptionReplaceTime;
199
		$properties['meetingtype'] = 'PT_LONG:PSETID_Meeting:0x26';
200
		$properties['timezone_data'] = 'PT_BINARY:PSETID_Appointment:' . PidLidTimeZoneStruct;
201
		$properties['timezone'] = 'PT_STRING8:PSETID_Appointment:' . PidLidTimeZoneDescription;
202
		$properties['categories'] = 'PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords';
203
		$properties['private'] = 'PT_BOOLEAN:PSETID_Common:' . PidLidPrivate;
204
		$properties['alldayevent'] = 'PT_BOOLEAN:PSETID_Appointment:' . PidLidAppointmentSubType;
205
		$properties['toattendeesstring'] = 'PT_STRING8:PSETID_Appointment:0x823B';
206
		$properties['ccattendeesstring'] = 'PT_STRING8:PSETID_Appointment:0x823C';
207
208
		$this->proptags = getPropIdsFromStrings($store, $properties);
209
	}
210
211
	/**
212
	 * Sets the direct booking property. This is an alternative to the setting of the direct booking
213
	 * property through the constructor. However, setting it in the constructor is preferred.
214
	 *
215
	 * @param bool $directBookingSetting
216
	 */
217
	public function setDirectBooking($directBookingSetting): void {
218
		$this->enableDirectBooking = $directBookingSetting;
219
	}
220
221
	/**
222
	 * Returns TRUE if the message pointed to is an incoming meeting request and should
223
	 * therefore be replied to with doAccept or doDecline().
224
	 *
225
	 * @param string $messageClass message class to use for checking
226
	 *
227
	 * @return bool returns true if this is a meeting request else false
228
	 */
229
	public function isMeetingRequest($messageClass = false) {
230
		if ($messageClass === false) {
231
			$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
0 ignored issues
show
The function mapi_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

231
			$props = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
Loading history...
232
			$messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false;
233
		}
234
235
		if ($messageClass !== false && stripos($messageClass, 'ipm.schedule.meeting.request') === 0) {
236
			return true;
237
		}
238
239
		return false;
240
	}
241
242
	/**
243
	 * Returns TRUE if the message pointed to is a returning meeting request response.
244
	 *
245
	 * @param string $messageClass message class to use for checking
246
	 *
247
	 * @return bool returns true if this is a meeting request else false
248
	 */
249
	public function isMeetingRequestResponse($messageClass = false) {
250
		if ($messageClass === false) {
251
			$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
0 ignored issues
show
The function mapi_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

251
			$props = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
Loading history...
252
			$messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false;
253
		}
254
255
		if ($messageClass !== false && stripos($messageClass, 'ipm.schedule.meeting.resp') === 0) {
256
			return true;
257
		}
258
259
		return false;
260
	}
261
262
	/**
263
	 * Returns TRUE if the message pointed to is a cancellation request.
264
	 *
265
	 * @param string $messageClass message class to use for checking
266
	 *
267
	 * @return bool returns true if this is a meeting request else false
268
	 */
269
	public function isMeetingCancellation($messageClass = false) {
270
		if ($messageClass === false) {
271
			$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
0 ignored issues
show
The function mapi_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

271
			$props = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
Loading history...
272
			$messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false;
273
		}
274
275
		if ($messageClass !== false && stripos($messageClass, 'ipm.schedule.meeting.canceled') === 0) {
276
			return true;
277
		}
278
279
		return false;
280
	}
281
282
	/**
283
	 * Function is used to get the last update counter of meeting request.
284
	 *
285
	 * @return bool|int false when last_updatecounter not found else return last_updatecounter
286
	 */
287
	public function getLastUpdateCounter() {
288
		$calendarItemProps = mapi_getprops($this->message, [$this->proptags['last_updatecounter']]);
0 ignored issues
show
The function mapi_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

288
		$calendarItemProps = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [$this->proptags['last_updatecounter']]);
Loading history...
289
		if (isset($calendarItemProps) && !empty($calendarItemProps)) {
290
			return $calendarItemProps[$this->proptags['last_updatecounter']];
291
		}
292
293
		return false;
294
	}
295
296
	/**
297
	 * Process an incoming meeting request response. This updates the appointment
298
	 * in your calendar to show whether the user has accepted or declined.
299
	 */
300
	public function processMeetingRequestResponse() {
301
		if (!$this->isMeetingRequestResponse()) {
302
			return;
303
		}
304
305
		if (!$this->isLocalOrganiser()) {
306
			return;
307
		}
308
309
		// Get information we need from the response message
310
		$messageprops = mapi_getprops($this->message, [
0 ignored issues
show
The function mapi_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

310
		$messageprops = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [
Loading history...
311
			$this->proptags['goid'],
312
			$this->proptags['goid2'],
313
			PR_OWNER_APPT_ID,
314
			PR_SENT_REPRESENTING_EMAIL_ADDRESS,
315
			PR_SENT_REPRESENTING_NAME,
316
			PR_SENT_REPRESENTING_ADDRTYPE,
317
			PR_SENT_REPRESENTING_ENTRYID,
318
			PR_SENT_REPRESENTING_SEARCH_KEY,
319
			PR_MESSAGE_DELIVERY_TIME,
320
			PR_MESSAGE_CLASS,
321
			PR_PROCESSED,
322
			PR_RCVD_REPRESENTING_ENTRYID,
323
			$this->proptags['proposed_start_whole'],
324
			$this->proptags['proposed_end_whole'],
325
			$this->proptags['proposed_duration'],
326
			$this->proptags['counter_proposal'],
327
			$this->proptags['attendee_critical_change'],
328
		]);
329
330
		$goid2 = $messageprops[$this->proptags['goid2']];
331
332
		if (!isset($goid2) || !isset($messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS])) {
333
			return;
334
		}
335
336
		// Find basedate in GlobalID(0x3), this can be a response for an occurrence
337
		$basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
338
339
		// check if delegate is processing the response
340
		if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) {
341
			$delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
342
			$userStore = $delegatorStore['store'];
343
		}
344
		else {
345
			$userStore = $this->store;
346
		}
347
348
		// check for calendar access
349
		if ($this->checkCalendarWriteAccess($userStore) !== true) {
350
			// Throw an exception that we don't have write permissions on calendar folder,
351
			// allow caller to fill the error message
352
			throw new MAPIException(null, MAPI_E_NO_ACCESS);
353
		}
354
355
		$calendarItem = $this->getCorrespondentCalendarItem(true);
356
357
		// Open the calendar items, and update all the recipients of the calendar item that match
358
		// the email address of the response.
359
		if ($calendarItem !== false) {
360
			$this->processResponse($userStore, $calendarItem, $basedate, $messageprops);
361
		}
362
	}
363
364
	/**
365
	 * Process every incoming MeetingRequest response.This updates the appointment
366
	 * in your calendar to show whether the user has accepted or declined.
367
	 *
368
	 * @param resource $store        contains the userStore in which the meeting is created
369
	 * @param mixed    $calendarItem resource of the calendar item for which this response has arrived
370
	 * @param mixed    $basedate     if present the create an exception
371
	 * @param array    $messageprops contains message properties
372
	 *
373
	 * @return null|false
374
	 */
375
	public function processResponse($store, $calendarItem, $basedate, $messageprops) {
376
		$senderentryid = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
377
		$messageclass = $messageprops[PR_MESSAGE_CLASS];
378
		$deliverytime = $messageprops[PR_MESSAGE_DELIVERY_TIME];
379
		$recurringItem = 0;
380
381
		// Open the calendar item, find the sender in the recipient table and update all the recipients of the calendar item that match
382
		// the email address of the response.
383
		$calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['recurring'], PR_STORE_ENTRYID, PR_PARENT_ENTRYID, PR_ENTRYID, $this->proptags['updatecounter']]);
0 ignored issues
show
The function mapi_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

383
		$calendarItemProps = /** @scrutinizer ignore-call */ mapi_getprops($calendarItem, [$this->proptags['recurring'], PR_STORE_ENTRYID, PR_PARENT_ENTRYID, PR_ENTRYID, $this->proptags['updatecounter']]);
Loading history...
384
385
		// check if meeting response is already processed
386
		if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
387
			// meeting is already processed
388
			return;
389
		}
390
		mapi_setprops($this->message, [PR_PROCESSED => true]);
0 ignored issues
show
The function mapi_setprops 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

390
		/** @scrutinizer ignore-call */ 
391
  mapi_setprops($this->message, [PR_PROCESSED => true]);
Loading history...
391
		mapi_savechanges($this->message);
0 ignored issues
show
The function mapi_savechanges 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

391
		/** @scrutinizer ignore-call */ 
392
  mapi_savechanges($this->message);
Loading history...
392
393
		// if meeting is updated in organizer's calendar then we don't need to process
394
		// old response
395
		if ($this->isMeetingUpdated($basedate)) {
396
			return;
397
		}
398
399
		// If basedate is found, then create/modify exception msg and do processing
400
		if ($basedate && isset($calendarItemProps[$this->proptags['recurring']]) && $calendarItemProps[$this->proptags['recurring']] === true) {
401
			$recurr = new Recurrence($store, $calendarItem);
402
403
			// Copy properties from meeting request
404
			$exception_props = mapi_getprops($this->message, [
405
				PR_OWNER_APPT_ID,
406
				$this->proptags['proposed_start_whole'],
407
				$this->proptags['proposed_end_whole'],
408
				$this->proptags['proposed_duration'],
409
				$this->proptags['counter_proposal'],
410
			]);
411
412
			// Create/modify exception
413
			if ($recurr->isException($basedate)) {
414
				$recurr->modifyException($exception_props, $basedate);
415
			}
416
			else {
417
				// When we are creating an exception we need copy recipients from main recurring item
418
				$recipTable = mapi_message_getrecipienttable($calendarItem);
0 ignored issues
show
The function mapi_message_getrecipienttable 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

418
				$recipTable = /** @scrutinizer ignore-call */ mapi_message_getrecipienttable($calendarItem);
Loading history...
419
				$recips = mapi_table_queryallrows($recipTable, $this->recipprops);
0 ignored issues
show
The function mapi_table_queryallrows 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

419
				$recips = /** @scrutinizer ignore-call */ mapi_table_queryallrows($recipTable, $this->recipprops);
Loading history...
420
421
				// Retrieve actual start/due dates from calendar item.
422
				$exception_props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
423
				$exception_props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
424
425
				$recurr->createException($exception_props, $basedate, false, $recips);
426
			}
427
428
			mapi_savechanges($calendarItem);
429
430
			$attach = $recurr->getExceptionAttachment($basedate);
431
			if ($attach) {
432
				$recurringItem = $calendarItem;
433
				$calendarItem = mapi_attach_openobj($attach, MAPI_MODIFY);
0 ignored issues
show
The function mapi_attach_openobj 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

433
				$calendarItem = /** @scrutinizer ignore-call */ mapi_attach_openobj($attach, MAPI_MODIFY);
Loading history...
434
			}
435
			else {
436
				return false;
437
			}
438
		}
439
440
		// Get the recipients of the calendar item
441
		$reciptable = mapi_message_getrecipienttable($calendarItem);
442
		$recipients = mapi_table_queryallrows($reciptable, $this->recipprops);
443
444
		// FIXME we should look at the updatecounter property and compare it
445
		// to the counter in the recipient to see if this update is actually
446
		// newer than the status in the calendar item
447
		$found = false;
448
449
		$totalrecips = 0;
450
		$acceptedrecips = 0;
451
		foreach ($recipients as $recipient) {
452
			++$totalrecips;
453
			if (isset($recipient[PR_ENTRYID]) && $this->compareABEntryIDs($recipient[PR_ENTRYID], $senderentryid)) {
454
				$found = true;
455
456
				/*
457
				 * If value of attendee_critical_change on meeting response mail is less than PR_RECIPIENT_TRACKSTATUS_TIME
458
				 * on the corresponding recipientRow of meeting then we ignore this response mail.
459
				 */
460
				if (isset($recipient[PR_RECIPIENT_TRACKSTATUS_TIME]) && ($messageprops[$this->proptags['attendee_critical_change']] < $recipient[PR_RECIPIENT_TRACKSTATUS_TIME])) {
461
					continue;
462
				}
463
464
				// The email address matches, update the row
465
				$recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
466
				if (isset($messageprops[$this->proptags['attendee_critical_change']])) {
467
					$recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $messageprops[$this->proptags['attendee_critical_change']];
468
				}
469
470
				// If this is a counter proposal, set the proposal properties in the recipient row
471
				if (isset($messageprops[$this->proptags['counter_proposal']]) && $messageprops[$this->proptags['counter_proposal']]) {
472
					$recipient[PR_RECIPIENT_PROPOSEDSTARTTIME] = $messageprops[$this->proptags['proposed_start_whole']];
473
					$recipient[PR_RECIPIENT_PROPOSEDENDTIME] = $messageprops[$this->proptags['proposed_end_whole']];
474
					$recipient[PR_RECIPIENT_PROPOSED] = $messageprops[$this->proptags['counter_proposal']];
475
				}
476
477
				// Update the recipient information
478
				mapi_message_modifyrecipients($calendarItem, MODRECIP_REMOVE, [$recipient]);
0 ignored issues
show
The function mapi_message_modifyrecipients 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

478
				/** @scrutinizer ignore-call */ 
479
    mapi_message_modifyrecipients($calendarItem, MODRECIP_REMOVE, [$recipient]);
Loading history...
479
				mapi_message_modifyrecipients($calendarItem, MODRECIP_ADD, [$recipient]);
480
			}
481
			if (isset($recipient[PR_RECIPIENT_TRACKSTATUS]) && $recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted) {
482
				++$acceptedrecips;
483
			}
484
		}
485
486
		// If the recipient was not found in the original calendar item,
487
		// then add the recpient as a new optional recipient
488
		if (!$found) {
489
			$recipient = [];
490
			$recipient[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
491
			$recipient[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
492
			$recipient[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
493
			$recipient[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
494
			$recipient[PR_RECIPIENT_TYPE] = MAPI_CC;
495
			$recipient[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
496
			$recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
497
			$recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $deliverytime;
498
499
			// If this is a counter proposal, set the proposal properties in the recipient row
500
			if (isset($messageprops[$this->proptags['counter_proposal']])) {
501
				$recipient[PR_RECIPIENT_PROPOSEDSTARTTIME] = $messageprops[$this->proptags['proposed_start_whole']];
502
				$recipient[PR_RECIPIENT_PROPOSEDENDTIME] = $messageprops[$this->proptags['proposed_end_whole']];
503
				$recipient[PR_RECIPIENT_PROPOSED] = $messageprops[$this->proptags['counter_proposal']];
504
			}
505
506
			mapi_message_modifyrecipients($calendarItem, MODRECIP_ADD, [$recipient]);
507
			++$totalrecips;
508
			if ($recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted) {
509
				++$acceptedrecips;
510
			}
511
		}
512
513
		// TODO: Update counter proposal number property on message
514
		/*
515
		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.
516
		*/
517
		// If this is a counter proposal, set the counter proposal indicator boolean
518
		if (isset($messageprops[$this->proptags['counter_proposal']])) {
519
			$props = [];
520
			if ($messageprops[$this->proptags['counter_proposal']]) {
521
				$props[$this->proptags['counter_proposal']] = true;
522
			}
523
			else {
524
				$props[$this->proptags['counter_proposal']] = false;
525
			}
526
527
			mapi_setprops($calendarItem, $props);
528
		}
529
530
		mapi_savechanges($calendarItem);
531
		if (isset($attach)) {
532
			mapi_savechanges($attach);
533
			mapi_savechanges($recurringItem);
534
		}
535
	}
536
537
	/**
538
	 * Process an incoming meeting request cancellation. This updates the
539
	 * appointment in your calendar to show that the meeting has been cancelled.
540
	 */
541
	public function processMeetingCancellation() {
542
		if (!$this->isMeetingCancellation()) {
543
			return;
544
		}
545
546
		if ($this->isLocalOrganiser()) {
547
			return;
548
		}
549
550
		if (!$this->isInCalendar()) {
551
			return;
552
		}
553
554
		$listProperties = $this->proptags;
555
		$listProperties['subject'] = PR_SUBJECT;
556
		$listProperties['sent_representing_name'] = PR_SENT_REPRESENTING_NAME;
557
		$listProperties['sent_representing_address_type'] = PR_SENT_REPRESENTING_ADDRTYPE;
558
		$listProperties['sent_representing_email_address'] = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
559
		$listProperties['sent_representing_entryid'] = PR_SENT_REPRESENTING_ENTRYID;
560
		$listProperties['sent_representing_search_key'] = PR_SENT_REPRESENTING_SEARCH_KEY;
561
		$listProperties['rcvd_representing_name'] = PR_RCVD_REPRESENTING_NAME;
562
		$listProperties['rcvd_representing_address_type'] = PR_RCVD_REPRESENTING_ADDRTYPE;
563
		$listProperties['rcvd_representing_email_address'] = PR_RCVD_REPRESENTING_EMAIL_ADDRESS;
564
		$listProperties['rcvd_representing_entryid'] = PR_RCVD_REPRESENTING_ENTRYID;
565
		$listProperties['rcvd_representing_search_key'] = PR_RCVD_REPRESENTING_SEARCH_KEY;
566
		$messageProps = mapi_getprops($this->message, $listProperties);
0 ignored issues
show
The function mapi_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

566
		$messageProps = /** @scrutinizer ignore-call */ mapi_getprops($this->message, $listProperties);
Loading history...
567
568
		$goid = $messageProps[$this->proptags['goid']];	// GlobalID (0x3)
569
		if (!isset($goid)) {
570
			return;
571
		}
572
573
		// get delegator store, if delegate is processing this cancellation
574
		if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) {
575
			$delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
576
577
			$store = $delegatorStore['store'];
578
		}
579
		else {
580
			$store = $this->store;
581
		}
582
583
		// check for calendar access
584
		if ($this->checkCalendarWriteAccess($store) !== true) {
585
			// Throw an exception that we don't have write permissions on calendar folder,
586
			// allow caller to fill the error message
587
			throw new MAPIException(null, MAPI_E_NO_ACCESS);
588
		}
589
590
		$calendarItem = $this->getCorrespondentCalendarItem(true);
591
		$basedate = $this->getBasedateFromGlobalID($goid);
592
593
		if ($calendarItem !== false) {
594
			// if basedate is provided and we could not find the item then it could be that we are processing
595
			// an exception so get the exception and process it
596
			if ($basedate) {
597
				$calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['recurring']]);
598
				if ($calendarItemProps[$this->proptags['recurring']] === true) {
599
					$recurr = new Recurrence($store, $calendarItem);
600
601
					// Set message class
602
					$messageProps[PR_MESSAGE_CLASS] = 'IPM.Appointment';
603
604
					if ($recurr->isException($basedate)) {
605
						$recurr->modifyException($messageProps, $basedate);
606
					}
607
					else {
608
						$recurr->createException($messageProps, $basedate);
609
					}
610
				}
611
			}
612
			else {
613
				// set the properties of the cancellation object
614
				mapi_setprops($calendarItem, $messageProps);
0 ignored issues
show
The function mapi_setprops 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

614
				/** @scrutinizer ignore-call */ 
615
    mapi_setprops($calendarItem, $messageProps);
Loading history...
615
			}
616
617
			mapi_savechanges($calendarItem);
0 ignored issues
show
The function mapi_savechanges 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

617
			/** @scrutinizer ignore-call */ 
618
   mapi_savechanges($calendarItem);
Loading history...
618
		}
619
	}
620
621
	/**
622
	 * Returns true if the corresponding calendar items exists in the celendar folder for this
623
	 * meeting request/response/cancellation.
624
	 */
625
	public function isInCalendar(): bool {
626
		// @TODO check for deleted exceptions
627
		return $this->getCorrespondentCalendarItem(false) !== false;
628
	}
629
630
	/**
631
	 * Accepts the meeting request by moving the item to the calendar
632
	 * and sending a confirmation message back to the sender. If $tentative
633
	 * is TRUE, then the item is accepted tentatively. After accepting, you
634
	 * can't use this class instance any more. The message is closed. If you
635
	 * specify TRUE for 'move', then the item is actually moved (from your
636
	 * inbox probably) to the calendar. If you don't, it is copied into
637
	 * your calendar.
638
	 *
639
	 * @param bool  $tentative            true if user as tentative accepted the meeting
640
	 * @param bool  $sendresponse         true if a response has to be sent to organizer
641
	 * @param bool  $move                 true if the meeting request should be moved to the deleted items after processing
642
	 * @param mixed $newProposedStartTime contains starttime if user has proposed other time
643
	 * @param mixed $newProposedEndTime   contains endtime if user has proposed other time
644
	 * @param mixed $body
645
	 * @param mixed $userAction
646
	 * @param mixed $store
647
	 * @param mixed $basedate             start of day of occurrence for which user has accepted the recurrent meeting
648
	 * @param bool  $isImported           true to indicate that MR is imported from .ics or .vcs file else it false.
649
	 *
650
	 * @return bool|string $entryid entryid of item which created/updated in calendar
651
	 */
652
	public function doAccept($tentative, $sendresponse, $move, $newProposedStartTime = false, $newProposedEndTime = false, $body = false, $userAction = false, $store = false, $basedate = false, $isImported = false) {
653
		if ($this->isLocalOrganiser()) {
654
			return false;
655
		}
656
657
		// Remove any previous calendar items with this goid and appt id
658
		$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]);
0 ignored issues
show
The function mapi_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

658
		$messageprops = /** @scrutinizer ignore-call */ 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]);
Loading history...
659
660
		// If this meeting request is received by a delegate then open delegator's store.
661
		if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID], $messageprops[PR_RECEIVED_BY_ENTRYID]) &&
662
			!compareEntryIds($messageprops[PR_RCVD_REPRESENTING_ENTRYID], $messageprops[PR_RECEIVED_BY_ENTRYID])) {
663
			$delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
664
665
			$store = $delegatorStore['store'];
666
			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
667
		}
668
		else {
669
			$calFolder = $this->openDefaultCalendar();
670
			$store = $this->store;
671
		}
672
673
		// check for calendar access
674
		if ($this->checkCalendarWriteAccess($store) !== true) {
675
			// Throw an exception that we don't have write permissions on calendar folder,
676
			// allow caller to fill the error message
677
			throw new MAPIException(null, MAPI_E_NO_ACCESS);
678
		}
679
680
		// if meeting is out dated then don't process it
681
		if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS]) && $this->isMeetingOutOfDate()) {
682
			return false;
683
		}
684
685
		/*
686
		 *	if this function is called automatically with meeting request object then there will be
687
		 *	two possibilitites
688
		 *	1) meeting request is opened first time, in this case make a tentative appointment in
689
		 *		recipient's calendar
690
		 *	2) after this every subsequent request to open meeting request will not do any processing
691
		 */
692
		if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS]) && $userAction == false) {
693
			if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
694
				// if meeting request is already processed then don't do anything
695
				return false;
696
			}
697
698
			// if correspondent calendar item is already processed then don't do anything
699
			$calendarItem = $this->getCorrespondentCalendarItem();
700
			if ($calendarItem) {
701
				$calendarItemProps = mapi_getprops($calendarItem, [PR_PROCESSED]);
702
				if (isset($calendarItemProps[PR_PROCESSED]) && $calendarItemProps[PR_PROCESSED] == true) {
703
					// mark meeting-request mail as processed as well
704
					mapi_setprops($this->message, [PR_PROCESSED => true]);
0 ignored issues
show
The function mapi_setprops 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

704
					/** @scrutinizer ignore-call */ 
705
     mapi_setprops($this->message, [PR_PROCESSED => true]);
Loading history...
705
					mapi_savechanges($this->message);
0 ignored issues
show
The function mapi_savechanges 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

705
					/** @scrutinizer ignore-call */ 
706
     mapi_savechanges($this->message);
Loading history...
706
707
					return false;
708
				}
709
			}
710
		}
711
712
		// Retrieve basedate from globalID, if it is not received as argument
713
		if (!$basedate) {
714
			$basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
715
		}
716
717
		// set counter proposal properties in calendar item when proposing new time
718
		$proposeNewTimeProps = [];
719
		if ($newProposedStartTime && $newProposedEndTime) {
720
			$proposeNewTimeProps[$this->proptags['proposed_start_whole']] = $newProposedStartTime;
721
			$proposeNewTimeProps[$this->proptags['proposed_end_whole']] = $newProposedEndTime;
722
			$proposeNewTimeProps[$this->proptags['proposed_duration']] = round($newProposedEndTime - $newProposedStartTime) / 60;
723
			$proposeNewTimeProps[$this->proptags['counter_proposal']] = true;
724
		}
725
726
		// While sender is receiver then we have to process the meeting request as per the intended busy status
727
		// instead of tentative, and accept the same as per the intended busystatus.
728
		$senderEntryId = isset($messageprops[PR_SENT_REPRESENTING_ENTRYID]) ? $messageprops[PR_SENT_REPRESENTING_ENTRYID] : $messageprops[PR_SENDER_ENTRYID];
729
		if (isset($messageprops[PR_RECEIVED_BY_ENTRYID]) && compareEntryIds($senderEntryId, $messageprops[PR_RECEIVED_BY_ENTRYID])) {
730
			$entryid = $this->accept(false, $sendresponse, $move, $proposeNewTimeProps, $body, true, $store, $calFolder, $basedate);
731
		}
732
		else {
733
			$entryid = $this->accept($tentative, $sendresponse, $move, $proposeNewTimeProps, $body, $userAction, $store, $calFolder, $basedate);
734
		}
735
736
		// if we have first time processed this meeting then set PR_PROCESSED property
737
		if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS]) && $userAction === false && $isImported === false) {
738
			if (!isset($messageprops[PR_PROCESSED]) || $messageprops[PR_PROCESSED] != true) {
739
				// set processed flag
740
				mapi_setprops($this->message, [PR_PROCESSED => true]);
741
				mapi_savechanges($this->message);
742
			}
743
		}
744
745
		return $entryid;
746
	}
747
748
	/**
749
	 * @param (float|mixed|true)[] $proposeNewTimeProps
750
	 * @param resource             $calFolder
751
	 * @param mixed                $body
752
	 * @param mixed                $store
753
	 * @param mixed                $basedate
754
	 *
755
	 * @psalm-param array<float|mixed|true> $proposeNewTimeProps
756
	 */
757
	public function accept(bool $tentative, bool $sendresponse, bool $move, array $proposeNewTimeProps, $body, bool $userAction, $store, $calFolder, $basedate = false) {
758
		$messageprops = mapi_getprops($this->message);
0 ignored issues
show
The function mapi_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

758
		$messageprops = /** @scrutinizer ignore-call */ mapi_getprops($this->message);
Loading history...
759
		$isDelegate = isset($messageprops[PR_RCVD_REPRESENTING_NAME]);
760
		$entryid = '';
761
762
		if ($sendresponse) {
763
			$this->createResponse($tentative ? olResponseTentative : olResponseAccepted, $proposeNewTimeProps, $body, $store, $basedate, $calFolder);
764
		}
765
766
		/*
767
		 * Further processing depends on what user is receiving. User can receive recurring item, a single occurrence or a normal meeting.
768
		 * 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.
769
		 * 2) If single occurrence then find occurrence itself using globalID and if item is not found then use cleanGlobalID to find main recurring item
770
		 * 3) Normal meeting req are handled normally as they were handled previously.
771
		 *
772
		 * 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
773
		 * 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.
774
		 * If user is responding from calendar then item is opened and properties are set such as meetingstatus, responsestatus, busystatus etc.
775
		 */
776
		if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS])) {
777
			// This meeting request item is recurring, so find all occurrences and saves them all as exceptions to this meeting request item.
778
			if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']] == true && $basedate == false) {
779
				$calendarItem = false;
780
781
				// Find main recurring item based on GlobalID (0x3)
782
				$items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
783
				if (is_array($items)) {
784
					foreach ($items as $entryid) {
785
						$calendarItem = mapi_msgstore_openentry($store, $entryid);
0 ignored issues
show
The function mapi_msgstore_openentry 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

785
						$calendarItem = /** @scrutinizer ignore-call */ mapi_msgstore_openentry($store, $entryid);
Loading history...
786
					}
787
				}
788
789
				$processed = false;
790
				if (!$calendarItem) {
791
					// Recurring item not found, so create new meeting in Calendar
792
					$calendarItem = mapi_folder_createmessage($calFolder);
0 ignored issues
show
The function mapi_folder_createmessage 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

792
					$calendarItem = /** @scrutinizer ignore-call */ mapi_folder_createmessage($calFolder);
Loading history...
793
				}
794
				else {
795
					// we have found the main recurring item, check if this meeting request is already processed
796
					if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
797
						// only set required properties, other properties are already copied when processing this meeting request
798
						// for the first time
799
						$processed = true;
800
					}
801
					// While we applying updates of MR then all local categories will be removed,
802
					// So get the local categories of all occurrence before applying update from organiser.
803
					$localCategories = $this->getLocalCategories($calendarItem, $store, $calFolder);
804
				}
805
806
				if (!$processed) {
807
					// get all the properties and copy that to calendar item
808
					$props = mapi_getprops($this->message);
809
					// reset the PidLidMeetingType to Unspecified for outlook display the item
810
					$props[$this->proptags['meetingtype']] = mtgEmpty;
811
					/*
812
					 * the client which has sent this meeting request can generate wrong flagdueby
813
					 * time (mainly OL), so regenerate that property so we will always show reminder
814
					 * on right time
815
					 */
816
					if (isset($props[$this->proptags['reminderminutes']])) {
817
						$props[$this->proptags['flagdueby']] = $props[$this->proptags['startdate']] - ($props[$this->proptags['reminderminutes']] * 60);
818
					}
819
				}
820
				else {
821
					// only get required properties so we will not overwrite existing updated properties from calendar
822
					$props = mapi_getprops($this->message, [PR_ENTRYID]);
823
				}
824
825
				$props[PR_MESSAGE_CLASS] = 'IPM.Appointment';
826
				// When meeting requests are generated by third-party solutions, we might be missing the updatecounter property.
827
				if (!isset($props[$this->proptags['updatecounter']])) {
828
					$props[$this->proptags['updatecounter']] = 0;
829
				}
830
				$props[$this->proptags['meetingstatus']] = olMeetingReceived;
831
				// when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
832
				$props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
833
834
				if (isset($props[$this->proptags['intendedbusystatus']])) {
835
					if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
836
						$props[$this->proptags['busystatus']] = fbTentative;
837
					}
838
					else {
839
						$props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
840
					}
841
				// we already have intendedbusystatus value in $props so no need to copy it
842
				}
843
				else {
844
					$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
845
				}
846
847
				if ($userAction) {
848
					$addrInfo = $this->getOwnerAddress($this->store);
849
850
					// if user has responded then set replytime and name
851
					$props[$this->proptags['replytime']] = time();
852
					if (!empty($addrInfo)) {
853
						// @FIXME conditionally set this property only for delegation case
854
						$props[$this->proptags['apptreplyname']] = $addrInfo[0];
855
					}
856
				}
857
858
				mapi_setprops($calendarItem, $props);
0 ignored issues
show
The function mapi_setprops 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

858
				/** @scrutinizer ignore-call */ 
859
    mapi_setprops($calendarItem, $props);
Loading history...
859
860
				// we have already processed attachments and recipients, so no need to do it again
861
				if (!$processed) {
862
					// Copy attachments too
863
					$this->replaceAttachments($this->message, $calendarItem);
864
					// Copy recipients too
865
					$this->replaceRecipients($this->message, $calendarItem, $isDelegate);
866
				}
867
868
				// Find all occurrences based on CleanGlobalID (0x23)
869
				// there will be no exceptions left if $processed is true, but even if it doesn't hurt to recheck
870
				$items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
871
				if (is_array($items)) {
872
					// Save all existing occurrence as exceptions
873
					foreach ($items as $entryid) {
874
						// Open occurrence
875
						$occurrenceItem = mapi_msgstore_openentry($store, $entryid);
876
877
						// Save occurrence into main recurring item as exception
878
						if ($occurrenceItem) {
879
							$occurrenceItemProps = mapi_getprops($occurrenceItem, [$this->proptags['goid'], $this->proptags['recurring']]);
880
881
							// Find basedate of occurrence item
882
							$basedate = $this->getBasedateFromGlobalID($occurrenceItemProps[$this->proptags['goid']]);
883
							if ($basedate && $occurrenceItemProps[$this->proptags['recurring']] != true) {
884
								$this->mergeException($calendarItem, $occurrenceItem, $basedate, $store);
885
							}
886
						}
887
					}
888
				}
889
890
				if (!isset($props[$this->proptags["recurring_pattern"]])) {
891
					$recurr = new Recurrence($store, $calendarItem);
892
					$recurr->saveRecurrencePattern();
893
				}
894
895
				mapi_savechanges($calendarItem);
0 ignored issues
show
The function mapi_savechanges 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

895
				/** @scrutinizer ignore-call */ 
896
    mapi_savechanges($calendarItem);
Loading history...
896
897
				// After applying update of organiser all local categories of occurrence was removed,
898
				// So if local categories exist then apply it on respective occurrence.
899
				if (!empty($localCategories)) {
900
					$this->applyLocalCategories($calendarItem, $store, $localCategories);
901
				}
902
903
				if ($move) {
904
					// open wastebasket of currently logged in user and move the meeting request to it
905
					// for delegates this will be delegate's wastebasket folder
906
					$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
907
					$sourcefolder = $this->openParentFolder();
908
					mapi_folder_copymessages($sourcefolder, [$props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
0 ignored issues
show
The function mapi_folder_copymessages 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

908
					/** @scrutinizer ignore-call */ 
909
     mapi_folder_copymessages($sourcefolder, [$props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
Loading history...
909
				}
910
911
				$entryid = $props[PR_ENTRYID];
912
			}
913
			else {
914
				/**
915
				 * This meeting request is not recurring, so can be an exception or normal meeting.
916
				 * If exception then find main recurring item and update exception
917
				 * If main recurring item is not found then put exception into Calendar as normal meeting.
918
				 */
919
				$calendarItem = false;
920
921
				// We found basedate in GlobalID of this meeting request, so this meeting request if for an occurrence.
922
				if ($basedate) {
923
					// Find main recurring item from CleanGlobalID of this meeting request
924
					$items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
925
					if (is_array($items)) {
926
						foreach ($items as $entryid) {
927
							$calendarItem = mapi_msgstore_openentry($store, $entryid);
928
						}
929
					}
930
931
					// Main recurring item is found, so now update exception
932
					if ($calendarItem) {
933
						$this->acceptException($calendarItem, $this->message, $basedate, $move, $tentative, $userAction, $store, $isDelegate);
934
						$calendarItemProps = mapi_getprops($calendarItem, [PR_ENTRYID]);
935
						$entryid = $calendarItemProps[PR_ENTRYID];
936
					}
937
				}
938
939
				if (!$calendarItem) {
940
					$items = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
941
					if (is_array($items)) {
942
						// Get local categories before deleting MR.
943
						$message = mapi_msgstore_openentry($store, $items[0]);
944
						$localCategories = mapi_getprops($message, [$this->proptags['categories']]);
945
						mapi_folder_deletemessages($calFolder, $items);
0 ignored issues
show
The function mapi_folder_deletemessages 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

945
						/** @scrutinizer ignore-call */ 
946
      mapi_folder_deletemessages($calFolder, $items);
Loading history...
946
					}
947
948
					if ($move) {
949
						// All we have to do is open the default calendar,
950
						// set the message class correctly to be an appointment item
951
						// and move it to the calendar folder
952
						$sourcefolder = $this->openParentFolder();
953
954
						// create a new calendar message, and copy the message to there,
955
						// since we want to delete (move to wastebasket) the original message
956
						$old_entryid = mapi_getprops($this->message, [PR_ENTRYID]);
957
						$calmsg = mapi_folder_createmessage($calFolder);
958
						mapi_copyto($this->message, [], [], $calmsg); /* includes attachments and recipients */
0 ignored issues
show
The function mapi_copyto 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

958
						/** @scrutinizer ignore-call */ 
959
      mapi_copyto($this->message, [], [], $calmsg); /* includes attachments and recipients */
Loading history...
959
						// reset the PidLidMeetingType to Unspecified for outlook display the item
960
						$tmp_props = [];
961
						$tmp_props[$this->proptags['meetingtype']] = mtgEmpty;
962
						// OL needs this field always being set, or it will not display item
963
						$tmp_props[$this->proptags['recurring']] = false;
964
						mapi_setprops($calmsg, $tmp_props);
965
966
						// After creating new MR, If local categories exist then apply it on new MR.
967
						if (!empty($localCategories)) {
968
							mapi_setprops($calmsg, $localCategories);
969
						}
970
971
						$calItemProps = [];
972
						$calItemProps[PR_MESSAGE_CLASS] = 'IPM.Appointment';
973
974
						/*
975
						 * the client which has sent this meeting request can generate wrong flagdueby
976
						 * time (mainly OL), so regenerate that property so we will always show reminder
977
						 * on right time
978
						 */
979
						if (isset($messageprops[$this->proptags['reminderminutes']])) {
980
							$calItemProps[$this->proptags['flagdueby']] = $messageprops[$this->proptags['startdate']] - ($messageprops[$this->proptags['reminderminutes']] * 60);
981
						}
982
983
						if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
984
							if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
985
								$calItemProps[$this->proptags['busystatus']] = fbTentative;
986
							}
987
							else {
988
								$calItemProps[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
989
							}
990
							$calItemProps[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
991
						}
992
						else {
993
							$calItemProps[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
994
						}
995
996
						// when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
997
						$calItemProps[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
998
						if ($userAction) {
999
							$addrInfo = $this->getOwnerAddress($this->store);
1000
1001
							// if user has responded then set replytime and name
1002
							$calItemProps[$this->proptags['replytime']] = time();
1003
							if (!empty($addrInfo)) {
1004
								$calItemProps[$this->proptags['apptreplyname']] = $addrInfo[0];
1005
							}
1006
						}
1007
1008
						$calItemProps[$this->proptags['recurring_pattern']] = '';
1009
						$calItemProps[$this->proptags['alldayevent']] = $messageprops[$this->proptags['alldayevent']] ?? false;
1010
						$calItemProps[$this->proptags['private']] = $messageprops[$this->proptags['private']] ?? false;
1011
						$calItemProps[$this->proptags['meetingstatus']] = $messageprops[$this->proptags['meetingstatus']] ?? olMeetingReceived;
1012
						if (isset($messageprops[$this->proptags['startdate']])) {
1013
							$calItemProps[$this->proptags['commonstart']] = $calItemProps[$this->proptags['startdate']] = $messageprops[$this->proptags['startdate']];
1014
						}
1015
						if (isset($messageprops[$this->proptags['duedate']])) {
1016
							$calItemProps[$this->proptags['commonend']] = $calItemProps[$this->proptags['duedate']] = $messageprops[$this->proptags['duedate']];
1017
						}
1018
1019
						mapi_setprops($calmsg, $proposeNewTimeProps + $calItemProps);
1020
1021
						// get properties which stores owner information in meeting request mails
1022
						$props = mapi_getprops($calmsg, [
1023
							PR_SENT_REPRESENTING_ENTRYID,
1024
							PR_SENT_REPRESENTING_NAME,
1025
							PR_SENT_REPRESENTING_EMAIL_ADDRESS,
1026
							PR_SENT_REPRESENTING_ADDRTYPE,
1027
							PR_SENT_REPRESENTING_SEARCH_KEY,
1028
							PR_SENT_REPRESENTING_SMTP_ADDRESS,
1029
						]);
1030
1031
						// add owner to recipient table
1032
						$recips = [];
1033
						$this->addOrganizer($props, $recips);
1034
						mapi_message_modifyrecipients($calmsg, MODRECIP_ADD, $recips);
0 ignored issues
show
The function mapi_message_modifyrecipients 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

1034
						/** @scrutinizer ignore-call */ 
1035
      mapi_message_modifyrecipients($calmsg, MODRECIP_ADD, $recips);
Loading history...
1035
						mapi_savechanges($calmsg);
1036
1037
						// Move the message to the wastebasket
1038
						$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
1039
						mapi_folder_copymessages($sourcefolder, [$old_entryid[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1040
1041
						$messageprops = mapi_getprops($calmsg, [PR_ENTRYID]);
1042
						$entryid = $messageprops[PR_ENTRYID];
1043
					}
1044
					else {
1045
						// Create a new appointment with duplicate properties and recipient, but as an IPM.Appointment
1046
						$new = mapi_folder_createmessage($calFolder);
1047
						$props = mapi_getprops($this->message);
1048
1049
						$props[$this->proptags['recurring_pattern']] = '';
1050
						$props[$this->proptags['alldayevent']] = $props[$this->proptags['alldayevent']] ?? false;
1051
						$props[$this->proptags['private']] = $props[$this->proptags['private']] ?? false;
1052
						$props[$this->proptags['meetingstatus']] = $props[$this->proptags['meetingstatus']] ?? olMeetingReceived;
1053
						if (isset($props[$this->proptags['startdate']])) {
1054
							$props[$this->proptags['commonstart']] = $props[$this->proptags['startdate']];
1055
						}
1056
						if (isset($props[$this->proptags['duedate']])) {
1057
							$props[$this->proptags['commonend']] = $props[$this->proptags['duedate']];
1058
						}
1059
1060
						$props[PR_MESSAGE_CLASS] = 'IPM.Appointment';
1061
						// reset the PidLidMeetingType to Unspecified for outlook display the item
1062
						$props[$this->proptags['meetingtype']] = mtgEmpty;
1063
						// OL needs this field always being set, or it will not display item
1064
						$props[$this->proptags['recurring']] = false;
1065
1066
						// After creating new MR, If local categories exist then apply it on new MR.
1067
						if (!empty($localCategories)) {
1068
							mapi_setprops($new, $localCategories);
1069
						}
1070
1071
						/*
1072
						 * the client which has sent this meeting request can generate wrong flagdueby
1073
						 * time (mainly OL), so regenerate that property so we will always show reminder
1074
						 * on right time
1075
						 */
1076
						if (isset($props[$this->proptags['reminderminutes']])) {
1077
							$props[$this->proptags['flagdueby']] = $props[$this->proptags['startdate']] - ($props[$this->proptags['reminderminutes']] * 60);
1078
						}
1079
1080
						// When meeting requests are generated by third-party solutions, we might be missing the updatecounter property.
1081
						if (!isset($props[$this->proptags['updatecounter']])) {
1082
							$props[$this->proptags['updatecounter']] = 0;
1083
						}
1084
						// when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
1085
						$props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
1086
1087
						if (isset($props[$this->proptags['intendedbusystatus']])) {
1088
							if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
1089
								$props[$this->proptags['busystatus']] = fbTentative;
1090
							}
1091
							else {
1092
								$props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
1093
							}
1094
						// we already have intendedbusystatus value in $props so no need to copy it
1095
						}
1096
						else {
1097
							$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
1098
						}
1099
1100
						if ($userAction) {
1101
							$addrInfo = $this->getOwnerAddress($this->store);
1102
1103
							// if user has responded then set replytime and name
1104
							$props[$this->proptags['replytime']] = time();
1105
							if (!empty($addrInfo)) {
1106
								$props[$this->proptags['apptreplyname']] = $addrInfo[0];
1107
							}
1108
						}
1109
1110
						mapi_setprops($new, $proposeNewTimeProps + $props);
1111
1112
						$reciptable = mapi_message_getrecipienttable($this->message);
0 ignored issues
show
The function mapi_message_getrecipienttable 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

1112
						$reciptable = /** @scrutinizer ignore-call */ mapi_message_getrecipienttable($this->message);
Loading history...
1113
1114
						$recips = [];
1115
						// If delegate, then do not add the delegate in recipients
1116
						if ($isDelegate) {
1117
							$delegate = mapi_getprops($this->message, [PR_RECEIVED_BY_EMAIL_ADDRESS]);
1118
							$res = [
1119
								RES_PROPERTY,
1120
								[
1121
									RELOP => RELOP_NE,
1122
									ULPROPTAG => PR_EMAIL_ADDRESS,
1123
									VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]],
1124
								],
1125
							];
1126
							$recips = mapi_table_queryallrows($reciptable, $this->recipprops, $res);
0 ignored issues
show
The function mapi_table_queryallrows 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

1126
							$recips = /** @scrutinizer ignore-call */ mapi_table_queryallrows($reciptable, $this->recipprops, $res);
Loading history...
1127
						}
1128
						else {
1129
							$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
1130
						}
1131
1132
						$this->addOrganizer($props, $recips);
1133
						mapi_message_modifyrecipients($new, MODRECIP_ADD, $recips);
1134
						mapi_savechanges($new);
1135
1136
						$props = mapi_getprops($new, [PR_ENTRYID]);
1137
						$entryid = $props[PR_ENTRYID];
1138
					}
1139
				}
1140
			}
1141
		}
1142
		else {
1143
			// Here only properties are set on calendaritem, because user is responding from calendar.
1144
			$props = [];
1145
			$props[$this->proptags['responsestatus']] = $tentative ? olResponseTentative : olResponseAccepted;
1146
1147
			if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
1148
				if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
1149
					$props[$this->proptags['busystatus']] = fbTentative;
1150
				}
1151
				else {
1152
					$props[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
1153
				}
1154
				$props[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
1155
			}
1156
			else {
1157
				$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
1158
			}
1159
1160
			$props[$this->proptags['meetingstatus']] = olMeetingReceived;
1161
1162
			$addrInfo = $this->getOwnerAddress($this->store);
1163
1164
			// if user has responded then set replytime and name
1165
			$props[$this->proptags['replytime']] = time();
1166
			if (!empty($addrInfo)) {
1167
				$props[$this->proptags['apptreplyname']] = $addrInfo[0];
1168
			}
1169
1170
			if ($basedate) {
1171
				$recurr = new Recurrence($store, $this->message);
1172
1173
				// Copy recipients list
1174
				$reciptable = mapi_message_getrecipienttable($this->message);
1175
				$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
1176
1177
				if ($recurr->isException($basedate)) {
1178
					$recurr->modifyException($proposeNewTimeProps + $props, $basedate, $recips);
1179
				}
1180
				else {
1181
					$props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
1182
					$props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
1183
1184
					$props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
1185
					$props[PR_SENT_REPRESENTING_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
1186
					$props[PR_SENT_REPRESENTING_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
1187
					$props[PR_SENT_REPRESENTING_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
1188
					$props[PR_SENT_REPRESENTING_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
1189
1190
					$recurr->createException($proposeNewTimeProps + $props, $basedate, false, $recips);
1191
				}
1192
			}
1193
			else {
1194
				mapi_setprops($this->message, $proposeNewTimeProps + $props);
1195
			}
1196
			mapi_savechanges($this->message);
1197
1198
			$entryid = $messageprops[PR_ENTRYID];
1199
		}
1200
1201
		return $entryid;
1202
	}
1203
1204
	/**
1205
	 * Declines the meeting request by moving the item to the deleted
1206
	 * items folder and sending a decline message. After declining, you
1207
	 * can't use this class instance any more. The message is closed.
1208
	 * When an occurrence is decline then false is returned because that
1209
	 * occurrence is deleted not the recurring item.
1210
	 *
1211
	 * @param bool  $sendresponse true if a response has to be sent to organizer
1212
	 * @param mixed $basedate     if specified contains starttime of day of an occurrence
1213
	 * @param mixed $body
1214
	 *
1215
	 * @return bool true if item is deleted from Calendar else false
1216
	 */
1217
	public function doDecline($sendresponse, $basedate = false, $body = false) {
1218
		if ($this->isLocalOrganiser()) {
1219
			return false;
1220
		}
1221
1222
		$result = false;
1223
		$calendaritem = false;
1224
1225
		// Remove any previous calendar items with this goid and appt id
1226
		$messageprops = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_ENTRYID]);
0 ignored issues
show
The function mapi_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

1226
		$messageprops = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_ENTRYID]);
Loading history...
1227
1228
		// If this meeting request is received by a delegate then open delegator's store.
1229
		if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) {
1230
			$delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
1231
1232
			$store = $delegatorStore['store'];
1233
			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
1234
		}
1235
		else {
1236
			$calFolder = $this->openDefaultCalendar();
1237
			$store = $this->store;
1238
		}
1239
1240
		// check for calendar access before deleting the calendar item
1241
		if ($this->checkCalendarWriteAccess($store) !== true) {
1242
			// Throw an exception that we don't have write permissions on calendar folder,
1243
			// allow caller to fill the error message
1244
			throw new MAPIException(null, MAPI_E_NO_ACCESS);
1245
		}
1246
1247
		$goid = $messageprops[$this->proptags['goid']];
1248
1249
		// First, find the items in the calendar by GlobalObjid (0x3)
1250
		$entryids = $this->findCalendarItems($goid, $calFolder);
1251
1252
		if (!$basedate) {
1253
			$basedate = $this->getBasedateFromGlobalID($goid);
1254
		}
1255
1256
		if ($sendresponse) {
1257
			$this->createResponse(olResponseDeclined, [], $body, $store, $basedate, $calFolder);
1258
		}
1259
1260
		if ($basedate) {
1261
			// use CleanGlobalObjid (0x23)
1262
			$calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
1263
1264
			if (is_array($calendaritems)) {
1265
				foreach ($calendaritems as $entryid) {
1266
					// Open each calendar item and set the properties of the cancellation object
1267
					$calendaritem = mapi_msgstore_openentry($store, $entryid);
0 ignored issues
show
The function mapi_msgstore_openentry 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

1267
					$calendaritem = /** @scrutinizer ignore-call */ mapi_msgstore_openentry($store, $entryid);
Loading history...
1268
1269
					// Recurring item is found, now delete exception
1270
					if ($calendaritem) {
1271
						$this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store);
1272
						$result = true;
1273
					}
1274
				}
1275
			}
1276
1277
			if ($this->isMeetingRequest()) {
1278
				$calendaritem = false;
1279
			}
1280
		}
1281
1282
		if (!$calendaritem) {
1283
			$calendar = $this->openDefaultCalendar($store);
1284
1285
			if (!empty($entryids)) {
1286
				mapi_folder_deletemessages($calendar, $entryids);
0 ignored issues
show
The function mapi_folder_deletemessages 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

1286
				/** @scrutinizer ignore-call */ 
1287
    mapi_folder_deletemessages($calendar, $entryids);
Loading history...
1287
			}
1288
1289
			// All we have to do to decline, is to move the item to the waste basket
1290
			$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
1291
			$sourcefolder = $this->openParentFolder();
1292
1293
			$messageprops = mapi_getprops($this->message, [PR_ENTRYID]);
1294
1295
			// Release the message
1296
			$this->message = null;
1297
1298
			// Move the message to the waste basket
1299
			mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
0 ignored issues
show
The function mapi_folder_copymessages 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

1299
			/** @scrutinizer ignore-call */ 
1300
   mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
Loading history...
1300
1301
			$result = true;
1302
		}
1303
1304
		return $result;
1305
	}
1306
1307
	/**
1308
	 * Removes a meeting request from the calendar when the user presses the
1309
	 * 'remove from calendar' button in response to a meeting cancellation.
1310
	 *
1311
	 * @param mixed $basedate if specified contains starttime of day of an occurrence
1312
	 *
1313
	 * @return null|false
1314
	 */
1315
	public function doRemoveFromCalendar($basedate) {
1316
		if ($this->isLocalOrganiser()) {
1317
			return false;
1318
		}
1319
1320
		$messageprops = mapi_getprops($this->message, [PR_ENTRYID, $this->proptags['goid'], PR_RCVD_REPRESENTING_ENTRYID, PR_MESSAGE_CLASS]);
0 ignored issues
show
The function mapi_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

1320
		$messageprops = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [PR_ENTRYID, $this->proptags['goid'], PR_RCVD_REPRESENTING_ENTRYID, PR_MESSAGE_CLASS]);
Loading history...
1321
1322
		$goid = $messageprops[$this->proptags['goid']];
1323
1324
		if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) {
1325
			$delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
1326
1327
			$store = $delegatorStore['store'];
1328
			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
1329
		}
1330
		else {
1331
			$store = $this->store;
1332
			$calFolder = $this->openDefaultCalendar();
1333
		}
1334
1335
		// check for calendar access before deleting the calendar item
1336
		if ($this->checkCalendarWriteAccess($store) !== true) {
1337
			// Throw an exception that we don't have write permissions on calendar folder,
1338
			// allow caller to fill the error message
1339
			throw new MAPIException(null, MAPI_E_NO_ACCESS);
1340
		}
1341
1342
		$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
1343
		// get the source folder of the meeting message
1344
		$sourcefolder = $this->openParentFolder();
1345
1346
		// Check if the message is a meeting request in the inbox or a calendaritem by checking the message class
1347
		if ($this->isMeetingCancellation($messageprops[PR_MESSAGE_CLASS])) {
1348
			// get the basedate to check for exception
1349
			$basedate = $this->getBasedateFromGlobalID($goid);
1350
1351
			$calendarItem = $this->getCorrespondentCalendarItem(true);
1352
1353
			if ($calendarItem !== false) {
1354
				// basedate is provided so open exception
1355
				if ($basedate) {
1356
					$exception = $this->getExceptionItem($calendarItem, $basedate);
1357
1358
					if ($exception !== false) {
1359
						// exception found, remove it from calendar
1360
						$this->doRemoveExceptionFromCalendar($basedate, $calendarItem, $store);
1361
					}
1362
				}
1363
				else {
1364
					// remove normal / recurring series from calendar
1365
					$entryids = mapi_getprops($calendarItem, [PR_ENTRYID]);
1366
1367
					$entryids = [$entryids[PR_ENTRYID]];
1368
1369
					mapi_folder_copymessages($calFolder, $entryids, $wastebasket, MESSAGE_MOVE);
0 ignored issues
show
The function mapi_folder_copymessages 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

1369
					/** @scrutinizer ignore-call */ 
1370
     mapi_folder_copymessages($calFolder, $entryids, $wastebasket, MESSAGE_MOVE);
Loading history...
1370
				}
1371
			}
1372
1373
			// Release the message, because we are going to move it to wastebasket
1374
			$this->message = null;
1375
1376
			// Move the cancellation mail to wastebasket
1377
			mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1378
		}
1379
		else {
1380
			// Here only properties are set on calendaritem, because user is responding from calendar.
1381
			if ($basedate) {
1382
				// remove the occurrence
1383
				$this->doRemoveExceptionFromCalendar($basedate, $this->message, $store);
1384
			}
1385
			else {
1386
				// remove normal/recurring meeting item.
1387
				// Move the message to the waste basket
1388
				mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1389
			}
1390
		}
1391
	}
1392
1393
	/**
1394
	 * Function can be used to cancel any existing meeting and send cancellation mails to attendees.
1395
	 * Should only be called from meeting object from calendar.
1396
	 *
1397
	 * @param mixed $basedate (optional) basedate of occurrence which should be cancelled
1398
	 *
1399
	 * @FIXME cancellation mail is also sent to attendee which has declined the meeting
1400
	 * @FIXME don't send canellation mail when cancelling meeting from past
1401
	 */
1402
	public function doCancelInvitation($basedate = false) {
1403
		if (!$this->isLocalOrganiser()) {
1404
			return;
1405
		}
1406
1407
		// check write access for delegate
1408
		if ($this->checkCalendarWriteAccess($this->store) !== true) {
1409
			// Throw an exception that we don't have write permissions on calendar folder,
1410
			// error message will be filled by module
1411
			throw new MAPIException(null, MAPI_E_NO_ACCESS);
1412
		}
1413
1414
		$messageProps = mapi_getprops($this->message, [PR_ENTRYID, $this->proptags['recurring']]);
0 ignored issues
show
The function mapi_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

1414
		$messageProps = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [PR_ENTRYID, $this->proptags['recurring']]);
Loading history...
1415
1416
		if (isset($messageProps[$this->proptags['recurring']]) && $messageProps[$this->proptags['recurring']] === true) {
1417
			// cancellation of recurring series or one occurrence
1418
			$recurrence = new Recurrence($this->store, $this->message);
1419
1420
			// if basedate is specified then we are cancelling only one occurrence, so create exception for that occurrence
1421
			if ($basedate) {
1422
				$recurrence->createException([], $basedate, true);
1423
			}
1424
1425
			// update the meeting request
1426
			$this->updateMeetingRequest();
1427
1428
			// send cancellation mails
1429
			$this->sendMeetingRequest(true, dgettext('zarafa', 'Canceled') . ': ', $basedate);
1430
1431
			// save changes in the message
1432
			mapi_savechanges($this->message);
0 ignored issues
show
The function mapi_savechanges 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

1432
			/** @scrutinizer ignore-call */ 
1433
   mapi_savechanges($this->message);
Loading history...
1433
		}
1434
		else {
1435
			// cancellation of normal meeting request
1436
			// Send the cancellation
1437
			$this->updateMeetingRequest();
1438
			$this->sendMeetingRequest(true, dgettext('zarafa', 'Canceled') . ': ');
1439
1440
			// save changes in the message
1441
			mapi_savechanges($this->message);
1442
		}
1443
1444
		// if basedate is specified then we have already created exception of it so nothing should be done now
1445
		// but when cancelling normal / recurring meeting request we need to remove meeting from calendar
1446
		if ($basedate === false) {
1447
			// get the wastebasket folder, for delegate this will give wastebasket of delegate
1448
			$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
1449
1450
			// get the source folder of the meeting message
1451
			$sourcefolder = $this->openParentFolder();
1452
1453
			// Move the message to the deleted items
1454
			mapi_folder_copymessages($sourcefolder, [$messageProps[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
0 ignored issues
show
The function mapi_folder_copymessages 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

1454
			/** @scrutinizer ignore-call */ 
1455
   mapi_folder_copymessages($sourcefolder, [$messageProps[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
Loading history...
1455
		}
1456
	}
1457
1458
	/**
1459
	 * Convert epoch to MAPI FileTime, number of 100-nanosecond units since
1460
	 * the start of January 1, 1601.
1461
	 * https://msdn.microsoft.com/en-us/library/office/cc765906.aspx.
1462
	 *
1463
	 * @param int $epoch the current epoch
1464
	 *
1465
	 * @return int the MAPI FileTime equalevent to the given epoch time
1466
	 */
1467
	public function epochToMapiFileTime($epoch) {
1468
		$nanoseconds_between_epoch = 116444736000000000;
1469
1470
		return ($epoch * 10000000) + $nanoseconds_between_epoch;
1471
	}
1472
1473
	/**
1474
	 * Sets the properties in the message so that is can be sent
1475
	 * as a meeting request. The caller has to submit the message. This
1476
	 * is only used for new MeetingRequests. Pass the appointment item as $message
1477
	 * in the constructor to do this.
1478
	 *
1479
	 * @param mixed $basedate
1480
	 */
1481
	public function setMeetingRequest($basedate = false): void {
1482
		$props = mapi_getprops($this->message, [$this->proptags['updatecounter']]);
0 ignored issues
show
The function mapi_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

1482
		$props = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [$this->proptags['updatecounter']]);
Loading history...
1483
1484
		// Create a new global id for this item
1485
		// https://msdn.microsoft.com/en-us/library/ee160198(v=exchg.80).aspx
1486
		$goid = pack('H*', '040000008200E00074C5B7101A82E00800000000');
1487
		/*
1488
		$year = gmdate('Y');
1489
		$month = gmdate('n');
1490
		$day = gmdate('j');
1491
		$goid .= pack('n', $year);
1492
		$goid .= pack('C', $month);
1493
		$goid .= pack('C', $day);
1494
		*/
1495
		// Creation Time
1496
		$time = $this->epochToMapiFileTime(time());
1497
		$goid .= pack('V', $time & 0xFFFFFFFF);
1498
		$goid .= pack('V', $time >> 32);
1499
		// 8 Zeros
1500
		$goid .= pack('H*', '0000000000000000');
1501
		// Length of the random data
1502
		$goid .= pack('V', 16);
1503
		// Random data.
1504
		for ($i = 0; $i < 16; ++$i) {
1505
			$goid .= chr(rand(0, 255));
1506
		}
1507
1508
		// Create a new appointment id for this item
1509
		$apptid = rand();
1510
1511
		$props[PR_OWNER_APPT_ID] = $apptid;
1512
		$props[PR_ICON_INDEX] = 1026;
1513
		$props[$this->proptags['goid']] = $goid;
1514
		$props[$this->proptags['goid2']] = $goid;
1515
1516
		if (!isset($props[$this->proptags['updatecounter']])) {
1517
			$props[$this->proptags['updatecounter']] = 0;			// OL also starts sequence no with zero.
1518
			$props[$this->proptags['last_updatecounter']] = 0;
1519
		}
1520
1521
		mapi_setprops($this->message, $props);
0 ignored issues
show
The function mapi_setprops 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

1521
		/** @scrutinizer ignore-call */ 
1522
  mapi_setprops($this->message, $props);
Loading history...
1522
	}
1523
1524
	/**
1525
	 * Sends a meeting request by copying it to the outbox, converting
1526
	 * the message class, adding some properties that are required only
1527
	 * for sending the message and submitting the message. Set cancel to
1528
	 * true if you wish to completely cancel the meeting request. You can
1529
	 * specify an optional 'prefix' to prefix the sent message, which is normally
1530
	 * 'Canceled: '.
1531
	 *
1532
	 * @param mixed $cancel
1533
	 * @param mixed $prefix
1534
	 * @param mixed $basedate
1535
	 * @param mixed $modifiedRecips
1536
	 * @param mixed $deletedRecips
1537
	 *
1538
	 * @return (int|mixed)[]|true
1539
	 *
1540
	 * @psalm-return array{error: 1|3|4, displayname: mixed}|true
1541
	 */
1542
	public function sendMeetingRequest($cancel, $prefix = false, $basedate = false, $modifiedRecips = false, $deletedRecips = false) {
1543
		$this->includesResources = false;
1544
		$this->nonAcceptingResources = [];
1545
1546
		// Get the properties of the message
1547
		$messageprops = mapi_getprops($this->message, [$this->proptags['recurring']]);
0 ignored issues
show
The function mapi_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

1547
		$messageprops = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [$this->proptags['recurring']]);
Loading history...
1548
1549
		/*
1550
		 * Submit message to non-resource recipients
1551
		 */
1552
		// Set BusyStatus to olTentative (1)
1553
		// Set MeetingStatus to olMeetingReceived
1554
		// Set ResponseStatus to olResponseNotResponded
1555
1556
		/*
1557
		 * While sending recurrence meeting exceptions are not sent as attachments
1558
		 * because first all exceptions are sent and then recurrence meeting is sent.
1559
		 */
1560
		if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']] && !$basedate) {
1561
			// Book resource
1562
			$this->bookResources($this->message, $cancel, $prefix);
1563
1564
			if (!$this->errorSetResource) {
1565
				$recurr = new Recurrence($this->openDefaultStore(), $this->message);
0 ignored issues
show
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

1565
				$recurr = new Recurrence(/** @scrutinizer ignore-type */ $this->openDefaultStore(), $this->message);
Loading history...
1566
1567
				// First send meetingrequest for recurring item
1568
				$this->submitMeetingRequest($this->message, $cancel, $prefix, false, $recurr, false, $modifiedRecips, $deletedRecips);
1569
1570
				// Then send all meeting request for all exceptions
1571
				$exceptions = $recurr->getAllExceptions();
1572
				if ($exceptions) {
1573
					foreach ($exceptions as $exceptionBasedate) {
1574
						$attach = $recurr->getExceptionAttachment($exceptionBasedate);
1575
1576
						if ($attach) {
1577
							$occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
0 ignored issues
show
The function mapi_attach_openobj 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

1577
							$occurrenceItem = /** @scrutinizer ignore-call */ mapi_attach_openobj($attach, MAPI_MODIFY);
Loading history...
1578
							$this->submitMeetingRequest($occurrenceItem, $cancel, false, $exceptionBasedate, $recurr, false, $modifiedRecips, $deletedRecips);
1579
							mapi_savechanges($attach);
0 ignored issues
show
The function mapi_savechanges 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

1579
							/** @scrutinizer ignore-call */ 
1580
       mapi_savechanges($attach);
Loading history...
1580
						}
1581
					}
1582
				}
1583
			}
1584
		}
1585
		else {
1586
			// Basedate found, an exception is to be sent
1587
			if ($basedate) {
1588
				$recurr = new Recurrence($this->openDefaultStore(), $this->message);
1589
1590
				if ($cancel) {
1591
					// @TODO: remove occurrence from Resource's Calendar if resource was booked for whole series
1592
					$this->submitMeetingRequest($this->message, $cancel, $prefix, $basedate, $recurr, false);
1593
				}
1594
				else {
1595
					$attach = $recurr->getExceptionAttachment($basedate);
1596
1597
					if ($attach) {
1598
						$occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
1599
1600
						// Book resource for this occurrence
1601
						$resourceRecipData = $this->bookResources($occurrenceItem, $cancel, $prefix, $basedate);
1602
1603
						if (!$this->errorSetResource) {
1604
							// Save all previous changes
1605
							mapi_savechanges($this->message);
1606
1607
							$this->submitMeetingRequest($occurrenceItem, $cancel, $prefix, $basedate, $recurr, true, $modifiedRecips, $deletedRecips);
1608
							mapi_savechanges($occurrenceItem);
1609
							mapi_savechanges($attach);
1610
						}
1611
					}
1612
				}
1613
			}
1614
			else {
1615
				// This is normal meeting
1616
				$resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
1617
1618
				if (!$this->errorSetResource) {
1619
					$this->submitMeetingRequest($this->message, $cancel, $prefix, false, false, false, $modifiedRecips, $deletedRecips);
1620
				}
1621
			}
1622
		}
1623
1624
		if (isset($this->errorSetResource) && $this->errorSetResource) {
1625
			return [
1626
				'error' => $this->errorSetResource,
1627
				'displayname' => $this->recipientDisplayname,
1628
			];
1629
		}
1630
1631
		return true;
1632
	}
1633
1634
	/**
1635
	 * Updates the message after an update has been performed (for example,
1636
	 * changing the time of the meeting). This must be called before re-sending
1637
	 * the meeting request. You can also call this function instead of 'setMeetingRequest()'
1638
	 * as it will automatically call setMeetingRequest on this object if it is the first
1639
	 * call to this function.
1640
	 *
1641
	 * @param mixed $basedate
1642
	 */
1643
	public function updateMeetingRequest($basedate = false): void {
1644
		$messageprops = mapi_getprops($this->message, [$this->proptags['last_updatecounter'], $this->proptags['goid']]);
0 ignored issues
show
The function mapi_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

1644
		$messageprops = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [$this->proptags['last_updatecounter'], $this->proptags['goid']]);
Loading history...
1645
1646
		if (!isset($messageprops[$this->proptags['goid']])) {
1647
			$this->setMeetingRequest($basedate);
1648
		}
1649
		else {
1650
			$counter = (isset($messageprops[$this->proptags['last_updatecounter']]) ?? 0) + 1;
1651
1652
			// increment value of last_updatecounter, last_updatecounter will be common for recurring series
1653
			// so even if you sending an exception only you need to update the last_updatecounter in the recurring series message
1654
			// this way we can make sure that every time we will be using a uniwue number for every operation
1655
			mapi_setprops($this->message, [$this->proptags['last_updatecounter'] => $counter]);
0 ignored issues
show
The function mapi_setprops 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

1655
			/** @scrutinizer ignore-call */ 
1656
   mapi_setprops($this->message, [$this->proptags['last_updatecounter'] => $counter]);
Loading history...
1656
		}
1657
	}
1658
1659
	/**
1660
	 * Returns TRUE if we are the organiser of the meeting. Can be used with any type of meeting object.
1661
	 */
1662
	public function isLocalOrganiser(): bool {
1663
		$props = mapi_getprops($this->message, [$this->proptags['goid'], PR_MESSAGE_CLASS]);
0 ignored issues
show
The function mapi_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

1663
		$props = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [$this->proptags['goid'], PR_MESSAGE_CLASS]);
Loading history...
1664
1665
		if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS]) && !$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS]) && !$this->isMeetingCancellation($props[PR_MESSAGE_CLASS])) {
1666
			// we are checking with calendar item
1667
			$calendarItem = $this->message;
1668
		}
1669
		else {
1670
			// we are checking with meeting request / response / cancellation mail
1671
			// get calendar items
1672
			$calendarItem = $this->getCorrespondentCalendarItem(true);
1673
		}
1674
1675
		// even if we have received request/response for exception/occurrence then also
1676
		// we can check recurring series for organizer, no need to check with exception/occurrence
1677
1678
		if ($calendarItem !== false) {
1679
			$messageProps = mapi_getprops($calendarItem, [$this->proptags['responsestatus']]);
1680
1681
			if (isset($messageProps[$this->proptags['responsestatus']]) && $messageProps[$this->proptags['responsestatus']] === olResponseOrganized) {
1682
				return true;
1683
			}
1684
		}
1685
1686
		return false;
1687
	}
1688
1689
	/*
1690
	 * Support functions - INTERNAL ONLY
1691
	 ***************************************************************************************************
1692
	 */
1693
1694
	/**
1695
	 * Return the tracking status of a recipient based on the IPM class (passed).
1696
	 *
1697
	 * @param mixed $class
1698
	 */
1699
	public function getTrackStatus($class) {
1700
		$status = olRecipientTrackStatusNone;
1701
1702
		switch ($class) {
1703
			case 'IPM.Schedule.Meeting.Resp.Pos':
1704
				$status = olRecipientTrackStatusAccepted;
1705
				break;
1706
1707
			case 'IPM.Schedule.Meeting.Resp.Tent':
1708
				$status = olRecipientTrackStatusTentative;
1709
				break;
1710
1711
			case 'IPM.Schedule.Meeting.Resp.Neg':
1712
				$status = olRecipientTrackStatusDeclined;
1713
				break;
1714
		}
1715
1716
		return $status;
1717
	}
1718
1719
	/**
1720
	 * Function returns MAPIFolder resource of the folder that currently holds this meeting/meeting request
1721
	 * object.
1722
	 */
1723
	public function openParentFolder() {
1724
		$messageprops = mapi_getprops($this->message, [PR_PARENT_ENTRYID]);
0 ignored issues
show
The function mapi_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

1724
		$messageprops = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [PR_PARENT_ENTRYID]);
Loading history...
1725
1726
		return mapi_msgstore_openentry($this->store, $messageprops[PR_PARENT_ENTRYID]);
0 ignored issues
show
The function mapi_msgstore_openentry 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

1726
		return /** @scrutinizer ignore-call */ mapi_msgstore_openentry($this->store, $messageprops[PR_PARENT_ENTRYID]);
Loading history...
1727
	}
1728
1729
	/**
1730
	 * Function will return resource of the default calendar folder of store.
1731
	 *
1732
	 * @param mixed $store {optional} user store whose default calendar should be opened
1733
	 *
1734
	 * @return resource default calendar folder of store
1735
	 */
1736
	public function openDefaultCalendar($store = false) {
1737
		return $this->openDefaultFolder(PR_IPM_APPOINTMENT_ENTRYID, $store);
1738
	}
1739
1740
	/**
1741
	 * Function will return resource of the default outbox folder of store.
1742
	 *
1743
	 * @param mixed $store {optional} user store whose default outbox should be opened
1744
	 *
1745
	 * @return resource default outbox folder of store
1746
	 */
1747
	public function openDefaultOutbox($store = false) {
1748
		return $this->openBaseFolder(PR_IPM_OUTBOX_ENTRYID, $store);
1749
	}
1750
1751
	/**
1752
	 * Function will return resource of the default wastebasket folder of store.
1753
	 *
1754
	 * @param mixed $store {optional} user store whose default wastebasket should be opened
1755
	 *
1756
	 * @return resource default wastebasket folder of store
1757
	 */
1758
	public function openDefaultWastebasket($store = false) {
1759
		return $this->openBaseFolder(PR_IPM_WASTEBASKET_ENTRYID, $store);
1760
	}
1761
1762
	/**
1763
	 * Function will return resource of the default calendar folder of store.
1764
	 *
1765
	 * @param mixed $store {optional} user store whose default calendar should be opened
1766
	 *
1767
	 * @return bool|string default calendar folder of store
1768
	 */
1769
	public function getDefaultWastebasketEntryID($store = false) {
1770
		return $this->getBaseEntryID(PR_IPM_WASTEBASKET_ENTRYID, $store);
1771
	}
1772
1773
	/**
1774
	 * Function will return resource of the default sent mail folder of store.
1775
	 *
1776
	 * @param mixed $store {optional} user store whose default sent mail should be opened
1777
	 *
1778
	 * @return bool|string default sent mail folder of store
1779
	 */
1780
	public function getDefaultSentmailEntryID($store = false) {
1781
		return $this->getBaseEntryID(PR_IPM_SENTMAIL_ENTRYID, $store);
1782
	}
1783
1784
	/**
1785
	 * Function will return entryid of any default folder of store. This method is useful when you want
1786
	 * to get entryid of folder which is stored as properties of inbox folder
1787
	 * (PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_JOURNAL_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_TASK_ENTRYID).
1788
	 *
1789
	 * @param int   $prop  proptag of the folder for which we want to get entryid
1790
	 * @param mixed $store {optional} user store from which we need to get entryid of default folder
1791
	 *
1792
	 * @return bool|string entryid of folder pointed by $prop
1793
	 */
1794
	public function getDefaultFolderEntryID($prop, $store = false) {
1795
		try {
1796
			$inbox = mapi_msgstore_getreceivefolder($store ? $store : $this->store);
0 ignored issues
show
The function mapi_msgstore_getreceivefolder 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

1796
			$inbox = /** @scrutinizer ignore-call */ mapi_msgstore_getreceivefolder($store ? $store : $this->store);
Loading history...
1797
			$inboxprops = mapi_getprops($inbox, [$prop]);
0 ignored issues
show
The function mapi_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

1797
			$inboxprops = /** @scrutinizer ignore-call */ mapi_getprops($inbox, [$prop]);
Loading history...
1798
			if (isset($inboxprops[$prop])) {
1799
				return $inboxprops[$prop];
1800
			}
1801
		}
1802
		catch (MAPIException $e) {
1803
			// public store doesn't support this method
1804
			if ($e->getCode() == MAPI_E_NO_SUPPORT) {
1805
				// don't propagate this error to parent handlers, if store doesn't support it
1806
				$e->setHandled();
1807
			}
1808
		}
1809
1810
		return false;
1811
	}
1812
1813
	/**
1814
	 * Function will return resource of any default folder of store.
1815
	 *
1816
	 * @param int   $prop  proptag of the folder that we want to open
1817
	 * @param mixed $store {optional} user store from which we need to open default folder
1818
	 *
1819
	 * @return resource default folder of store
1820
	 */
1821
	public function openDefaultFolder($prop, $store = false) {
1822
		$folder = false;
1823
		$entryid = $this->getDefaultFolderEntryID($prop, $store);
1824
1825
		if ($entryid !== false) {
1826
			$folder = mapi_msgstore_openentry($store ? $store : $this->store, $entryid);
0 ignored issues
show
The function mapi_msgstore_openentry 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

1826
			$folder = /** @scrutinizer ignore-call */ mapi_msgstore_openentry($store ? $store : $this->store, $entryid);
Loading history...
1827
		}
1828
1829
		return $folder;
1830
	}
1831
1832
	/**
1833
	 * Function will return entryid of default folder from store. This method is useful when you want
1834
	 * to get entryid of folder which is stored as store properties
1835
	 * (PR_IPM_FAVORITES_ENTRYID, PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID).
1836
	 *
1837
	 * @param int   $prop  proptag of the folder whose entryid we want to get
1838
	 * @param mixed $store {optional} user store from which we need to get entryid of default folder
1839
	 *
1840
	 * @return bool|string entryid of default folder from store
1841
	 */
1842
	public function getBaseEntryID($prop, $store = false) {
1843
		$storeprops = mapi_getprops($store ? $store : $this->store, [$prop]);
0 ignored issues
show
The function mapi_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

1843
		$storeprops = /** @scrutinizer ignore-call */ mapi_getprops($store ? $store : $this->store, [$prop]);
Loading history...
1844
		if (!isset($storeprops[$prop])) {
1845
			return false;
1846
		}
1847
1848
		return $storeprops[$prop];
1849
	}
1850
1851
	/**
1852
	 * Function will return resource of any default folder of store.
1853
	 *
1854
	 * @param int   $prop  proptag of the folder that we want to open
1855
	 * @param mixed $store {optional} user store from which we need to open default folder
1856
	 *
1857
	 * @return resource default folder of store
1858
	 */
1859
	public function openBaseFolder($prop, $store = false) {
1860
		$folder = false;
1861
		$entryid = $this->getBaseEntryID($prop, $store);
1862
1863
		if ($entryid !== false) {
1864
			$folder = mapi_msgstore_openentry($store ? $store : $this->store, $entryid);
0 ignored issues
show
The function mapi_msgstore_openentry 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

1864
			$folder = /** @scrutinizer ignore-call */ mapi_msgstore_openentry($store ? $store : $this->store, $entryid);
Loading history...
1865
		}
1866
1867
		return $folder;
1868
	}
1869
1870
	/**
1871
	 * Function checks whether user has access over the specified folder or not.
1872
	 *
1873
	 * @param string $entryid entryid The entryid of the folder to check
1874
	 * @param mixed  $store   (optional) store from which folder should be opened
1875
	 *
1876
	 * @return bool true if user has an access over the folder, false if not
1877
	 */
1878
	public function checkFolderWriteAccess($entryid, $store = false) {
1879
		$accessToFolder = false;
1880
1881
		if (!empty($entryid)) {
1882
			if ($store === false) {
1883
				$store = $this->store;
1884
			}
1885
1886
			try {
1887
				$folder = mapi_msgstore_openentry($store, $entryid);
0 ignored issues
show
The function mapi_msgstore_openentry 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

1887
				$folder = /** @scrutinizer ignore-call */ mapi_msgstore_openentry($store, $entryid);
Loading history...
1888
				$folderProps = mapi_getprops($folder, [PR_ACCESS]);
0 ignored issues
show
The function mapi_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

1888
				$folderProps = /** @scrutinizer ignore-call */ mapi_getprops($folder, [PR_ACCESS]);
Loading history...
1889
				if (($folderProps[PR_ACCESS] & MAPI_ACCESS_CREATE_CONTENTS) === MAPI_ACCESS_CREATE_CONTENTS) {
1890
					$accessToFolder = true;
1891
				}
1892
			}
1893
			catch (MAPIException $e) {
1894
				// we don't have rights to open folder, so return false
1895
				if ($e->getCode() == MAPI_E_NO_ACCESS) {
1896
					return $accessToFolder;
1897
				}
1898
1899
				// rethrow other errors
1900
				throw $e;
1901
			}
1902
		}
1903
1904
		return $accessToFolder;
1905
	}
1906
1907
	/**
1908
	 * Function checks whether user has access over the specified folder or not.
1909
	 *
1910
	 * @param mixed $store
1911
	 *
1912
	 * @return bool true if user has an access over the folder, false if not
1913
	 */
1914
	public function checkCalendarWriteAccess($store = false) {
1915
		if ($store === false) {
1916
			// If this meeting request is received by a delegate then open delegator's store.
1917
			$messageProps = mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID]);
0 ignored issues
show
The function mapi_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

1917
			$messageProps = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID]);
Loading history...
1918
			if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) {
1919
				$delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID]);
1920
1921
				$store = $delegatorStore['store'];
1922
			}
1923
			else {
1924
				$store = $this->store;
1925
			}
1926
		}
1927
1928
		// If the store is a public folder, the calendar folder is the PARENT_ENTRYID of the calendar item
1929
		$provider = mapi_getprops($store, [PR_MDB_PROVIDER]);
1930
		if (isset($provider[PR_MDB_PROVIDER]) && $provider[PR_MDB_PROVIDER] === ZARAFA_STORE_PUBLIC_GUID) {
1931
			$entryid = mapi_getprops($this->message, [PR_PARENT_ENTRYID]);
1932
			$entryid = $entryid[PR_PARENT_ENTRYID];
1933
		}
1934
		else {
1935
			$entryid = $this->getDefaultFolderEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store);
1936
			if ($entryid === false) {
1937
				$entryid = $this->getBaseEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store);
1938
			}
1939
1940
			if ($entryid === false) {
1941
				return false;
1942
			}
1943
		}
1944
1945
		return $this->checkFolderWriteAccess($entryid, $store);
1946
	}
1947
1948
	/**
1949
	 * Function will resolve the user and open its store.
1950
	 *
1951
	 * @param string $ownerentryid the entryid of the user
1952
	 *
1953
	 * @return resource store of the user
1954
	 */
1955
	public function openCustomUserStore($ownerentryid) {
1956
		$ab = mapi_openaddressbook($this->session);
0 ignored issues
show
The function mapi_openaddressbook 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

1956
		$ab = /** @scrutinizer ignore-call */ mapi_openaddressbook($this->session);
Loading history...
1957
1958
		try {
1959
			$mailuser = mapi_ab_openentry($ab, $ownerentryid);
0 ignored issues
show
The function mapi_ab_openentry 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

1959
			$mailuser = /** @scrutinizer ignore-call */ mapi_ab_openentry($ab, $ownerentryid);
Loading history...
1960
		}
1961
		catch (MAPIException $e) {
1962
			return;
1963
		}
1964
1965
		$mailuserprops = mapi_getprops($mailuser, [PR_EMAIL_ADDRESS]);
0 ignored issues
show
The function mapi_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

1965
		$mailuserprops = /** @scrutinizer ignore-call */ mapi_getprops($mailuser, [PR_EMAIL_ADDRESS]);
Loading history...
1966
		$storeid = mapi_msgstore_createentryid($this->store, $mailuserprops[PR_EMAIL_ADDRESS]);
0 ignored issues
show
The function mapi_msgstore_createentryid 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

1966
		$storeid = /** @scrutinizer ignore-call */ mapi_msgstore_createentryid($this->store, $mailuserprops[PR_EMAIL_ADDRESS]);
Loading history...
1967
1968
		return mapi_openmsgstore($this->session, $storeid);
0 ignored issues
show
The function mapi_openmsgstore 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

1968
		return /** @scrutinizer ignore-call */ mapi_openmsgstore($this->session, $storeid);
Loading history...
1969
	}
1970
1971
	/**
1972
	 * Function which sends response to organizer when attendee accepts, declines or proposes new time to a received meeting request.
1973
	 *
1974
	 * @param int   $status              response status of attendee
1975
	 * @param array $proposeNewTimeProps properties of attendee's proposal
1976
	 * @param mixed $body
1977
	 * @param mixed $store
1978
	 * @param mixed $basedate            date of occurrence which attendee has responded
1979
	 * @param mixed $calFolder
1980
	 */
1981
	public function createResponse($status, $proposeNewTimeProps, $body, $store, $basedate, $calFolder): void {
1982
		$messageprops = mapi_getprops($this->message, [
0 ignored issues
show
The function mapi_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

1982
		$messageprops = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [
Loading history...
1983
			PR_SENT_REPRESENTING_ENTRYID,
1984
			PR_SENT_REPRESENTING_EMAIL_ADDRESS,
1985
			PR_SENT_REPRESENTING_ADDRTYPE,
1986
			PR_SENT_REPRESENTING_NAME,
1987
			PR_SENT_REPRESENTING_SEARCH_KEY,
1988
			$this->proptags['goid'],
1989
			$this->proptags['goid2'],
1990
			$this->proptags['location'],
1991
			$this->proptags['startdate'],
1992
			$this->proptags['duedate'],
1993
			$this->proptags['recurring'],
1994
			$this->proptags['recurring_pattern'],
1995
			$this->proptags['recurrence_data'],
1996
			$this->proptags['timezone_data'],
1997
			$this->proptags['timezone'],
1998
			$this->proptags['updatecounter'],
1999
			PR_SUBJECT,
2000
			PR_MESSAGE_CLASS,
2001
			PR_OWNER_APPT_ID,
2002
			$this->proptags['is_exception'],
2003
		]);
2004
2005
		$props = [];
2006
2007
		if ($basedate !== false && !$this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS])) {
2008
			// we are creating response from a recurring calendar item object
2009
			// We found basedate,so opened occurrence and get properties.
2010
			$recurr = new Recurrence($store, $this->message);
2011
			$exception = $recurr->getExceptionAttachment($basedate);
2012
2013
			if ($exception) {
2014
				// Exception found, Now retrieve properties
2015
				$imessage = mapi_attach_openobj($exception, 0);
0 ignored issues
show
The function mapi_attach_openobj 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

2015
				$imessage = /** @scrutinizer ignore-call */ mapi_attach_openobj($exception, 0);
Loading history...
2016
				$imsgprops = mapi_getprops($imessage);
2017
2018
				// If location is provided, copy it to the response
2019
				if (isset($imsgprops[$this->proptags['location']])) {
2020
					$messageprops[$this->proptags['location']] = $imsgprops[$this->proptags['location']];
2021
				}
2022
2023
				// Update $messageprops with timings of occurrence
2024
				$messageprops[$this->proptags['startdate']] = $imsgprops[$this->proptags['startdate']];
2025
				$messageprops[$this->proptags['duedate']] = $imsgprops[$this->proptags['duedate']];
2026
2027
				// Meeting related properties
2028
				$props[$this->proptags['meetingstatus']] = $imsgprops[$this->proptags['meetingstatus']];
2029
				$props[$this->proptags['responsestatus']] = $imsgprops[$this->proptags['responsestatus']];
2030
				$props[PR_SUBJECT] = $imsgprops[PR_SUBJECT];
2031
			}
2032
			else {
2033
				// Exceptions is deleted.
2034
				// Update $messageprops with timings of occurrence
2035
				$messageprops[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
2036
				$messageprops[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
2037
2038
				$props[$this->proptags['meetingstatus']] = olNonMeeting;
2039
				$props[$this->proptags['responsestatus']] = olResponseNone;
2040
			}
2041
2042
			$props[$this->proptags['recurring']] = false;
2043
			$props[$this->proptags['is_exception']] = true;
2044
		}
2045
		else {
2046
			// we are creating a response from meeting request mail (it could be recurring or non-recurring)
2047
			// Send all recurrence info in response, if this is a recurrence meeting.
2048
			$isRecurring = isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']];
2049
			$isException = isset($messageprops[$this->proptags['is_exception']]) && $messageprops[$this->proptags['is_exception']];
2050
			if ($isRecurring || $isException) {
2051
				if ($isRecurring) {
2052
					$props[$this->proptags['recurring']] = $messageprops[$this->proptags['recurring']];
2053
				}
2054
				if ($isException) {
2055
					$props[$this->proptags['is_exception']] = $messageprops[$this->proptags['is_exception']];
2056
				}
2057
				$calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
2058
2059
				$calendaritem = mapi_msgstore_openentry($store, $calendaritems[0]);
0 ignored issues
show
The function mapi_msgstore_openentry 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

2059
				$calendaritem = /** @scrutinizer ignore-call */ mapi_msgstore_openentry($store, $calendaritems[0]);
Loading history...
2060
				$recurr = new Recurrence($store, $calendaritem);
2061
			}
2062
		}
2063
2064
		// we are sending a response for recurring meeting request (or exception), so set some required properties
2065
		if (isset($recurr) && $recurr) {
2066
			if (!empty($messageprops[$this->proptags['recurring_pattern']])) {
2067
				$props[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
2068
			}
2069
2070
			if (!empty($messageprops[$this->proptags['recurrence_data']])) {
2071
				$props[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
2072
			}
2073
2074
			$props[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
2075
			$props[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
2076
2077
			$this->generateRecurDates($recurr, $messageprops, $props);
2078
		}
2079
2080
		// Create a response message
2081
		$recip = [];
2082
		$recip[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
2083
		$recip[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
2084
		$recip[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
2085
		$recip[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
2086
		$recip[PR_RECIPIENT_TYPE] = MAPI_TO;
2087
		$recip[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
2088
2089
		$subjectprefix = '';
2090
		$classpostfix = '';
2091
2092
		switch ($status) {
2093
			case olResponseAccepted:
2094
				$classpostfix = 'Pos';
2095
				$subjectprefix = dgettext('zarafa', 'Accepted');
2096
				break;
2097
2098
			case olResponseDeclined:
2099
				$classpostfix = 'Neg';
2100
				$subjectprefix = dgettext('zarafa', 'Declined');
2101
				break;
2102
2103
			case olResponseTentative:
2104
				$classpostfix = 'Tent';
2105
				$subjectprefix = dgettext('zarafa', 'Tentatively accepted');
2106
				break;
2107
		}
2108
2109
		if (!empty($proposeNewTimeProps)) {
2110
			// if attendee has proposed new time then change subject prefix
2111
			$subjectprefix = dgettext('zarafa', 'New Time Proposed');
2112
		}
2113
2114
		$props[PR_SUBJECT] = $subjectprefix . ': ' . $messageprops[PR_SUBJECT];
2115
2116
		$props[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Resp.' . $classpostfix;
2117
		if (isset($messageprops[PR_OWNER_APPT_ID])) {
2118
			$props[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
2119
		}
2120
2121
		// Set GlobalId AND CleanGlobalId, if exception then also set basedate into GlobalId(0x3).
2122
		$props[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
2123
		$props[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
2124
		$props[$this->proptags['updatecounter']] = isset($messageprops[$this->proptags['updatecounter']]) ? $messageprops[$this->proptags['updatecounter']] : 0;
2125
2126
		if (!empty($proposeNewTimeProps)) {
2127
			// merge proposal properties to message properties which will be sent to organizer
2128
			$props = $proposeNewTimeProps + $props;
2129
		}
2130
2131
		// Set body message in Appointment
2132
		if (isset($body)) {
2133
			$props[PR_BODY] = $this->getMeetingTimeInfo() ? $this->getMeetingTimeInfo() : $body;
2134
		}
2135
2136
		// PR_START_DATE/PR_END_DATE is used in the UI in Outlook on the response message
2137
		$props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
2138
		$props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
2139
2140
		// Set startdate and duedate in response mail.
2141
		$props[$this->proptags['startdate']] = $messageprops[$this->proptags['startdate']];
2142
		$props[$this->proptags['duedate']] = $messageprops[$this->proptags['duedate']];
2143
2144
		// responselocation is used in the UI in Outlook on the response message
2145
		if (isset($messageprops[$this->proptags['location']])) {
2146
			$props[$this->proptags['responselocation']] = $messageprops[$this->proptags['location']];
2147
			$props[$this->proptags['location']] = $messageprops[$this->proptags['location']];
2148
		}
2149
2150
		$message = $this->createOutgoingMessage($store);
2151
2152
		mapi_setprops($message, $props);
0 ignored issues
show
The function mapi_setprops 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

2152
		/** @scrutinizer ignore-call */ 
2153
  mapi_setprops($message, $props);
Loading history...
2153
		mapi_message_modifyrecipients($message, MODRECIP_ADD, [$recip]);
0 ignored issues
show
The function mapi_message_modifyrecipients 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

2153
		/** @scrutinizer ignore-call */ 
2154
  mapi_message_modifyrecipients($message, MODRECIP_ADD, [$recip]);
Loading history...
2154
		mapi_savechanges($message);
0 ignored issues
show
The function mapi_savechanges 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

2154
		/** @scrutinizer ignore-call */ 
2155
  mapi_savechanges($message);
Loading history...
2155
		mapi_message_submitmessage($message);
0 ignored issues
show
The function mapi_message_submitmessage 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

2155
		/** @scrutinizer ignore-call */ 
2156
  mapi_message_submitmessage($message);
Loading history...
2156
	}
2157
2158
	/**
2159
	 * Function which finds items in calendar based on globalId and cleanGlobalId.
2160
	 *
2161
	 * @param string $goid             GlobalID(0x3) of item
2162
	 * @param mixed  $calendar         MAPI_folder of user (optional)
2163
	 * @param bool   $useCleanGlobalId if true then search should be performed on cleanGlobalId(0x23) else globalId(0x3)
2164
	 *
2165
	 * @return mixed
2166
	 */
2167
	public function findCalendarItems($goid, $calendar = false, $useCleanGlobalId = false) {
2168
		if ($calendar === false) {
2169
			// Open the Calendar
2170
			$calendar = $this->openDefaultCalendar();
2171
		}
2172
2173
		// Find the item by restricting all items to the correct ID
2174
		$restrict = [
2175
			RES_PROPERTY,
2176
			[
2177
				RELOP => RELOP_EQ,
2178
				ULPROPTAG => ($useCleanGlobalId === true ? $this->proptags['goid2'] : $this->proptags['goid']),
2179
				VALUE => $goid,
2180
			],
2181
		];
2182
2183
		$calendarcontents = mapi_folder_getcontentstable($calendar);
0 ignored issues
show
The function mapi_folder_getcontentstable 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

2183
		$calendarcontents = /** @scrutinizer ignore-call */ mapi_folder_getcontentstable($calendar);
Loading history...
2184
2185
		$rows = mapi_table_queryallrows($calendarcontents, [PR_ENTRYID], $restrict);
0 ignored issues
show
The function mapi_table_queryallrows 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

2185
		$rows = /** @scrutinizer ignore-call */ mapi_table_queryallrows($calendarcontents, [PR_ENTRYID], $restrict);
Loading history...
2186
2187
		if (empty($rows)) {
2188
			return;
2189
		}
2190
2191
		$calendaritems = [];
2192
2193
		// In principle, there should only be one row, but we'll handle them all just in case
2194
		foreach ($rows as $row) {
2195
			$calendaritems[] = $row[PR_ENTRYID];
2196
		}
2197
2198
		return $calendaritems;
2199
	}
2200
2201
	// Returns TRUE if both entryid's are equal. Equality is defined by both entryid's pointing at the
2202
	// same SMTP address when converted to SMTP
2203
	public function compareABEntryIDs($entryid1, $entryid2): bool {
2204
		// If the session was not passed, just do a 'normal' compare.
2205
		if (!$this->session) {
2206
			return $entryid1 == $entryid2;
2207
		}
2208
2209
		$smtp1 = $this->getSMTPAddress($entryid1);
2210
		$smtp2 = $this->getSMTPAddress($entryid2);
2211
2212
		if ($smtp1 == $smtp2) {
2213
			return true;
2214
		}
2215
2216
		return false;
2217
	}
2218
2219
	// Gets the SMTP address of the passed addressbook entryid
2220
	public function getSMTPAddress($entryid) {
2221
		if (!$this->session) {
2222
			return false;
2223
		}
2224
2225
		try {
2226
			$ab = mapi_openaddressbook($this->session);
0 ignored issues
show
The function mapi_openaddressbook 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

2226
			$ab = /** @scrutinizer ignore-call */ mapi_openaddressbook($this->session);
Loading history...
2227
			$abitem = mapi_ab_openentry($ab, $entryid);
0 ignored issues
show
The function mapi_ab_openentry 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

2227
			$abitem = /** @scrutinizer ignore-call */ mapi_ab_openentry($ab, $entryid);
Loading history...
2228
2229
			if (!$abitem) {
2230
				return '';
2231
			}
2232
		}
2233
		catch (MAPIException $e) {
2234
			return '';
2235
		}
2236
2237
		$props = mapi_getprops($abitem, [PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS]);
0 ignored issues
show
The function mapi_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

2237
		$props = /** @scrutinizer ignore-call */ mapi_getprops($abitem, [PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS]);
Loading history...
2238
2239
		if ($props[PR_ADDRTYPE] == 'SMTP') {
2240
			return $props[PR_EMAIL_ADDRESS];
2241
		}
2242
2243
		return $props[PR_SMTP_ADDRESS];
2244
	}
2245
2246
	/**
2247
	 * Gets the properties associated with the owner of the passed store:
2248
	 * PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_ADDRTYPE, PR_ENTRYID, PR_SEARCH_KEY.
2249
	 *
2250
	 * @param mixed $store                  message store
2251
	 * @param bool  $fallbackToLoggedInUser If true then return properties of logged in user instead of mailbox owner.
2252
	 *                                      Not used when passed store is public store.
2253
	 *                                      For public store we are always returning logged in user's info.
2254
	 *
2255
	 * @return array|false properties of logged in user in an array in sequence of display_name, email address, address type, entryid and search key
2256
	 *
2257
	 * @psalm-return false|list{mixed, mixed, mixed, mixed, mixed}
2258
	 */
2259
	public function getOwnerAddress($store, $fallbackToLoggedInUser = true) {
2260
		if (!$this->session) {
2261
			return false;
2262
		}
2263
2264
		$storeProps = mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID, PR_USER_ENTRYID]);
0 ignored issues
show
The function mapi_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

2264
		$storeProps = /** @scrutinizer ignore-call */ mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID, PR_USER_ENTRYID]);
Loading history...
2265
2266
		$ownerEntryId = false;
2267
		if (isset($storeProps[PR_USER_ENTRYID]) && $storeProps[PR_USER_ENTRYID]) {
2268
			$ownerEntryId = $storeProps[PR_USER_ENTRYID];
2269
		}
2270
2271
		if (isset($storeProps[PR_MAILBOX_OWNER_ENTRYID]) && $storeProps[PR_MAILBOX_OWNER_ENTRYID] && !$fallbackToLoggedInUser) {
2272
			$ownerEntryId = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
2273
		}
2274
2275
		if ($ownerEntryId) {
2276
			$ab = mapi_openaddressbook($this->session);
0 ignored issues
show
The function mapi_openaddressbook 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

2276
			$ab = /** @scrutinizer ignore-call */ mapi_openaddressbook($this->session);
Loading history...
2277
2278
			$zarafaUser = mapi_ab_openentry($ab, $ownerEntryId);
0 ignored issues
show
The function mapi_ab_openentry 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

2278
			$zarafaUser = /** @scrutinizer ignore-call */ mapi_ab_openentry($ab, $ownerEntryId);
Loading history...
2279
			if (!$zarafaUser) {
2280
				return false;
2281
			}
2282
2283
			$ownerProps = mapi_getprops($zarafaUser, [PR_ADDRTYPE, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_SEARCH_KEY]);
2284
2285
			$addrType = $ownerProps[PR_ADDRTYPE];
2286
			$name = $ownerProps[PR_DISPLAY_NAME];
2287
			$emailAddr = $ownerProps[PR_EMAIL_ADDRESS];
2288
			$searchKey = $ownerProps[PR_SEARCH_KEY];
2289
			$entryId = $ownerEntryId;
2290
2291
			return [$name, $emailAddr, $addrType, $entryId, $searchKey];
2292
		}
2293
2294
		return false;
2295
	}
2296
2297
	// Opens this session's default message store
2298
	public function openDefaultStore() {
2299
		$entryid = '';
2300
2301
		$storestable = mapi_getmsgstorestable($this->session);
0 ignored issues
show
The function mapi_getmsgstorestable 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

2301
		$storestable = /** @scrutinizer ignore-call */ mapi_getmsgstorestable($this->session);
Loading history...
2302
		$rows = mapi_table_queryallrows($storestable, [PR_ENTRYID, PR_DEFAULT_STORE]);
0 ignored issues
show
The function mapi_table_queryallrows 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

2302
		$rows = /** @scrutinizer ignore-call */ mapi_table_queryallrows($storestable, [PR_ENTRYID, PR_DEFAULT_STORE]);
Loading history...
2303
2304
		foreach ($rows as $row) {
2305
			if (isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE]) {
2306
				$entryid = $row[PR_ENTRYID];
2307
				break;
2308
			}
2309
		}
2310
2311
		if (!$entryid) {
2312
			return false;
2313
		}
2314
2315
		return mapi_openmsgstore($this->session, $entryid);
0 ignored issues
show
The function mapi_openmsgstore 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

2315
		return /** @scrutinizer ignore-call */ mapi_openmsgstore($this->session, $entryid);
Loading history...
2316
	}
2317
2318
	/**
2319
	 * Function which adds organizer to recipient list which is passed.
2320
	 * This function also checks if it has organizer.
2321
	 *
2322
	 * @param array $messageProps message properties
2323
	 * @param array $recipients   recipients list of message
2324
	 * @param bool  $isException  true if we are processing recipient of exception
2325
	 */
2326
	public function addOrganizer($messageProps, &$recipients, $isException = false): void {
2327
		$hasOrganizer = false;
2328
		// Check if meeting already has an organizer.
2329
		foreach ($recipients as $key => $recipient) {
2330
			if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
2331
				$hasOrganizer = true;
2332
			}
2333
			elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) {
2334
				// Recipients for an occurrence
2335
				$recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
2336
			}
2337
		}
2338
2339
		if (!$hasOrganizer) {
2340
			// Create organizer.
2341
			$organizer = [];
2342
			$organizer[PR_ENTRYID] = $organizer[PR_RECIPIENT_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID];
2343
			$organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
2344
			$organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
2345
			$organizer[PR_RECIPIENT_TYPE] = MAPI_TO;
2346
			$organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
2347
			$organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
2348
			$organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
2349
			$organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
2350
			$organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY];
2351
			$organizer[PR_SMTP_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_SMTP_ADDRESS] ?? $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
2352
2353
			// Add organizer to recipients list.
2354
			array_unshift($recipients, $organizer);
2355
		}
2356
	}
2357
2358
	/**
2359
	 * Function which removes an exception/occurrence from recurrencing meeting
2360
	 * when a meeting cancellation of an occurrence is processed.
2361
	 *
2362
	 * @param mixed    $basedate basedate of an occurrence
2363
	 * @param mixed    $message  recurring item from which occurrence has to be deleted
2364
	 * @param resource $store    MAPI_MSG_Store which contains the item
2365
	 */
2366
	public function doRemoveExceptionFromCalendar($basedate, $message, $store): void {
2367
		$recurr = new Recurrence($store, $message);
2368
		$recurr->createException([], $basedate, true);
2369
		mapi_savechanges($message);
0 ignored issues
show
The function mapi_savechanges 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

2369
		/** @scrutinizer ignore-call */ 
2370
  mapi_savechanges($message);
Loading history...
2370
	}
2371
2372
	/**
2373
	 * Function which returns basedate of an changed occurrence from globalID of meeting request.
2374
	 *
2375
	 * @param string $goid globalID
2376
	 *
2377
	 * @return false|int true if basedate is found else false it not found
2378
	 */
2379
	public function getBasedateFromGlobalID($goid) {
2380
		$hexguid = bin2hex($goid);
2381
		$hexbase = substr($hexguid, 32, 8);
2382
		$day = (int) hexdec(substr($hexbase, 6, 2));
2383
		$month = (int) hexdec(substr($hexbase, 4, 2));
2384
		$year = (int) hexdec(substr($hexbase, 0, 4));
2385
2386
		if ($day && $month && $year) {
2387
			return gmmktime(0, 0, 0, $month, $day, $year);
2388
		}
2389
2390
		return false;
2391
	}
2392
2393
	/**
2394
	 * Function which sets basedate in globalID of changed occurrence which is to be sent.
2395
	 *
2396
	 * @param string $goid     globalID
2397
	 * @param mixed  $basedate of changed occurrence
2398
	 *
2399
	 * @return false|string globalID with basedate in it
2400
	 */
2401
	public function setBasedateInGlobalID($goid, $basedate = false) {
2402
		$hexguid = bin2hex($goid);
2403
		$year = $basedate ? sprintf('%04s', dechex((int) gmdate('Y', $basedate))) : '0000';
2404
		$month = $basedate ? sprintf('%02s', dechex((int) gmdate('m', $basedate))) : '00';
2405
		$day = $basedate ? sprintf('%02s', dechex((int) gmdate('d', $basedate))) : '00';
2406
2407
		return hex2bin(strtoupper(substr($hexguid, 0, 32) . $year . $month . $day . substr($hexguid, 40)));
2408
	}
2409
2410
	/**
2411
	 * Function which replaces attachments with copy_from in copy_to.
2412
	 *
2413
	 * @param mixed $copyFrom       MAPI_message from which attachments are to be copied
2414
	 * @param mixed $copyTo         MAPI_message to which attachment are to be copied
2415
	 * @param bool  $copyExceptions if true then all exceptions should also be sent as attachments
2416
	 */
2417
	public function replaceAttachments($copyFrom, $copyTo, $copyExceptions = true): void {
2418
		/* remove all old attachments */
2419
		$attachmentTableTo = mapi_message_getattachmenttable($copyTo);
0 ignored issues
show
The function mapi_message_getattachmenttable 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

2419
		$attachmentTableTo = /** @scrutinizer ignore-call */ mapi_message_getattachmenttable($copyTo);
Loading history...
2420
		if ($attachmentTableTo) {
2421
			$attachments = mapi_table_queryallrows($attachmentTableTo, [PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME]);
0 ignored issues
show
The function mapi_table_queryallrows 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

2421
			$attachments = /** @scrutinizer ignore-call */ mapi_table_queryallrows($attachmentTableTo, [PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME]);
Loading history...
2422
2423
			foreach ($attachments as $attachProps) {
2424
				/* remove exceptions too? */
2425
				if (!$copyExceptions && $attachProps[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG && isset($attachProps[PR_EXCEPTION_STARTTIME])) {
2426
					continue;
2427
				}
2428
				mapi_message_deleteattach($copyTo, $attachProps[PR_ATTACH_NUM]);
0 ignored issues
show
The function mapi_message_deleteattach 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

2428
				/** @scrutinizer ignore-call */ 
2429
    mapi_message_deleteattach($copyTo, $attachProps[PR_ATTACH_NUM]);
Loading history...
2429
			}
2430
		}
2431
2432
		/* copy new attachments */
2433
		$attachmentTableFrom = mapi_message_getattachmenttable($copyFrom);
2434
		if ($attachmentTableFrom) {
2435
			$attachments = mapi_table_queryallrows($attachmentTableFrom, [PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME]);
2436
2437
			foreach ($attachments as $attachProps) {
2438
				if (!$copyExceptions && $attachProps[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG && isset($attachProps[PR_EXCEPTION_STARTTIME])) {
2439
					continue;
2440
				}
2441
2442
				$attachOld = mapi_message_openattach($copyFrom, (int) $attachProps[PR_ATTACH_NUM]);
0 ignored issues
show
The function mapi_message_openattach 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

2442
				$attachOld = /** @scrutinizer ignore-call */ mapi_message_openattach($copyFrom, (int) $attachProps[PR_ATTACH_NUM]);
Loading history...
2443
				$attachNewResourceMsg = mapi_message_createattach($copyTo);
0 ignored issues
show
The function mapi_message_createattach 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

2443
				$attachNewResourceMsg = /** @scrutinizer ignore-call */ mapi_message_createattach($copyTo);
Loading history...
2444
				mapi_copyto($attachOld, [], [], $attachNewResourceMsg, 0);
0 ignored issues
show
The function mapi_copyto 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

2444
				/** @scrutinizer ignore-call */ 
2445
    mapi_copyto($attachOld, [], [], $attachNewResourceMsg, 0);
Loading history...
2445
				mapi_savechanges($attachNewResourceMsg);
0 ignored issues
show
The function mapi_savechanges 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

2445
				/** @scrutinizer ignore-call */ 
2446
    mapi_savechanges($attachNewResourceMsg);
Loading history...
2446
			}
2447
		}
2448
	}
2449
2450
	/**
2451
	 * Function which replaces recipients in copyTo with recipients from copyFrom.
2452
	 *
2453
	 * @param mixed $copyFrom   MAPI_message from which recipients are to be copied
2454
	 * @param mixed $copyTo     MAPI_message to which recipients are to be copied
2455
	 * @param bool  $isDelegate indicates whether delegate is processing
2456
	 *                          so don't copy delegate information to recipient table
2457
	 */
2458
	public function replaceRecipients($copyFrom, $copyTo, $isDelegate = false): void {
2459
		$recipientTable = mapi_message_getrecipienttable($copyFrom);
0 ignored issues
show
The function mapi_message_getrecipienttable 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

2459
		$recipientTable = /** @scrutinizer ignore-call */ mapi_message_getrecipienttable($copyFrom);
Loading history...
2460
2461
		// If delegate, then do not add the delegate in recipients
2462
		if ($isDelegate) {
2463
			$delegate = mapi_getprops($copyFrom, [PR_RECEIVED_BY_EMAIL_ADDRESS]);
0 ignored issues
show
The function mapi_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

2463
			$delegate = /** @scrutinizer ignore-call */ mapi_getprops($copyFrom, [PR_RECEIVED_BY_EMAIL_ADDRESS]);
Loading history...
2464
			$res = [
2465
				RES_PROPERTY,
2466
				[
2467
					RELOP => RELOP_NE,
2468
					ULPROPTAG => PR_EMAIL_ADDRESS,
2469
					VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]],
2470
				],
2471
			];
2472
			$recipients = mapi_table_queryallrows($recipientTable, $this->recipprops, $res);
0 ignored issues
show
The function mapi_table_queryallrows 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

2472
			$recipients = /** @scrutinizer ignore-call */ mapi_table_queryallrows($recipientTable, $this->recipprops, $res);
Loading history...
2473
		}
2474
		else {
2475
			$recipients = mapi_table_queryallrows($recipientTable, $this->recipprops);
2476
		}
2477
2478
		$copyToRecipientTable = mapi_message_getrecipienttable($copyTo);
2479
		$copyToRecipientRows = mapi_table_queryallrows($copyToRecipientTable, [PR_ROWID]);
2480
2481
		mapi_message_modifyrecipients($copyTo, MODRECIP_REMOVE, $copyToRecipientRows);
0 ignored issues
show
The function mapi_message_modifyrecipients 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

2481
		/** @scrutinizer ignore-call */ 
2482
  mapi_message_modifyrecipients($copyTo, MODRECIP_REMOVE, $copyToRecipientRows);
Loading history...
2482
		mapi_message_modifyrecipients($copyTo, MODRECIP_ADD, $recipients);
2483
	}
2484
2485
	/**
2486
	 * Function creates meeting item in resource's calendar.
2487
	 *
2488
	 * @param resource $message  MAPI_message which is to create in resource's calendar
2489
	 * @param bool     $cancel   cancel meeting
2490
	 * @param mixed    $prefix   prefix for subject of meeting
2491
	 * @param mixed    $basedate
2492
	 *
2493
	 * @return (mixed|resource)[][]
2494
	 *
2495
	 * @psalm-return list<array{store: resource, folder: mixed, msg: mixed}>
2496
	 */
2497
	public function bookResources($message, $cancel, $prefix, $basedate = false): array {
2498
		if (!$this->enableDirectBooking) {
2499
			return [];
2500
		}
2501
2502
		// Get the properties of the message
2503
		$messageprops = mapi_getprops($message);
0 ignored issues
show
The function mapi_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

2503
		$messageprops = /** @scrutinizer ignore-call */ mapi_getprops($message);
Loading history...
2504
2505
		$calFolder = '';
2506
2507
		if ($basedate) {
2508
			$recurrItemProps = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], $this->proptags['timezone_data'], $this->proptags['timezone'], PR_OWNER_APPT_ID]);
2509
2510
			$messageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid']], $basedate);
2511
			$messageprops[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']];
2512
2513
			// Delete properties which are not needed.
2514
			$deleteProps = [$this->proptags['basedate'], PR_DISPLAY_NAME, PR_ATTACHMENT_FLAGS, PR_ATTACHMENT_HIDDEN, PR_ATTACHMENT_LINKID, PR_ATTACH_FLAGS, PR_ATTACH_METHOD];
2515
			foreach ($deleteProps as $propID) {
2516
				if (isset($messageprops[$propID])) {
2517
					unset($messageprops[$propID]);
2518
				}
2519
			}
2520
2521
			if (isset($messageprops[$this->proptags['recurring']])) {
2522
				$messageprops[$this->proptags['recurring']] = false;
2523
			}
2524
2525
			// Set Outlook properties
2526
			$messageprops[$this->proptags['clipstart']] = $messageprops[$this->proptags['startdate']];
2527
			$messageprops[$this->proptags['clipend']] = $messageprops[$this->proptags['duedate']];
2528
			$messageprops[$this->proptags['timezone_data']] = $recurrItemProps[$this->proptags['timezone_data']];
2529
			$messageprops[$this->proptags['timezone']] = $recurrItemProps[$this->proptags['timezone']];
2530
			$messageprops[$this->proptags['attendee_critical_change']] = time();
2531
			$messageprops[$this->proptags['owner_critical_change']] = time();
2532
		}
2533
2534
		// Get resource recipients
2535
		$getResourcesRestriction = [
2536
			RES_PROPERTY,
2537
			[
2538
				RELOP => RELOP_EQ,	// Equals recipient type 3: Resource
2539
				ULPROPTAG => PR_RECIPIENT_TYPE,
2540
				VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2541
			],
2542
		];
2543
		$recipienttable = mapi_message_getrecipienttable($message);
0 ignored issues
show
The function mapi_message_getrecipienttable 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

2543
		$recipienttable = /** @scrutinizer ignore-call */ mapi_message_getrecipienttable($message);
Loading history...
2544
		$resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
0 ignored issues
show
The function mapi_table_queryallrows 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

2544
		$resourceRecipients = /** @scrutinizer ignore-call */ mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
Loading history...
2545
2546
		$this->errorSetResource = false;
2547
		$resourceRecipData = [];
2548
2549
		// Put appointment into store resource users
2550
		$i = 0;
2551
		$len = count($resourceRecipients);
2552
		while (!$this->errorSetResource && $i < $len) {
2553
			$userStore = $this->openCustomUserStore($resourceRecipients[$i][PR_ENTRYID]);
2554
2555
			// Open root folder
2556
			$userRoot = mapi_msgstore_openentry($userStore, null);
0 ignored issues
show
The function mapi_msgstore_openentry 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

2556
			$userRoot = /** @scrutinizer ignore-call */ mapi_msgstore_openentry($userStore, null);
Loading history...
2557
2558
			// Get calendar entryID
2559
			$userRootProps = mapi_getprops($userRoot, [PR_STORE_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_FREEBUSY_ENTRYIDS]);
2560
2561
			// Open Calendar folder
2562
			$accessToFolder = false;
2563
2564
			try {
2565
				// @FIXME this checks delegate has access to resource's calendar folder
2566
				// but it should use boss' credentials
2567
2568
				$accessToFolder = $this->checkCalendarWriteAccess($this->store);
2569
				if ($accessToFolder) {
2570
					$calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
2571
				}
2572
			}
2573
			catch (MAPIException $e) {
2574
				$e->setHandled();
2575
				$this->errorSetResource = 1; // No access
2576
			}
2577
2578
			if ($accessToFolder) {
2579
				/**
2580
				 * Get the LocalFreebusy message that contains the properties that
2581
				 * are set to accept or decline resource meeting requests.
2582
				 */
2583
				$localFreebusyMsg = FreeBusy::getLocalFreeBusyMessage($userStore);
2584
				if ($localFreebusyMsg) {
2585
					$props = mapi_getprops($localFreebusyMsg, [PR_SCHDINFO_AUTO_ACCEPT_APPTS, PR_SCHDINFO_DISALLOW_RECURRING_APPTS, PR_SCHDINFO_DISALLOW_OVERLAPPING_APPTS]);
2586
2587
					$acceptMeetingRequests = isset($props[PR_SCHDINFO_AUTO_ACCEPT_APPTS]) ? $props[PR_SCHDINFO_AUTO_ACCEPT_APPTS] : false;
2588
					$declineRecurringMeetingRequests = isset($props[PR_SCHDINFO_DISALLOW_RECURRING_APPTS]) ? $props[PR_SCHDINFO_DISALLOW_RECURRING_APPTS] : false;
2589
					$declineConflictingMeetingRequests = isset($props[PR_SCHDINFO_DISALLOW_OVERLAPPING_APPTS]) ? $props[PR_SCHDINFO_DISALLOW_OVERLAPPING_APPTS] : false;
2590
2591
					if (!$acceptMeetingRequests) {
2592
						/*
2593
						 * When a resource has not been set to automatically accept meeting requests,
2594
						 * the meeting request has to be sent to him rather than being put directly into
2595
						 * his calendar. No error should be returned.
2596
						 */
2597
						// $errorSetResource = 2;
2598
						$this->nonAcceptingResources[] = $resourceRecipients[$i];
2599
					}
2600
					else {
2601
						if ($declineRecurringMeetingRequests && !$cancel) {
2602
							// Check if appointment is recurring
2603
							if ($messageprops[$this->proptags['recurring']]) {
2604
								$this->errorSetResource = 3;
2605
							}
2606
						}
2607
						if ($declineConflictingMeetingRequests && !$cancel) {
2608
							// Check for conflicting items
2609
							if ($calFolder && $this->isMeetingConflicting($message, $userStore, $calFolder)) {
2610
								$this->errorSetResource = 4; // Conflict
2611
							}
2612
						}
2613
					}
2614
				}
2615
			}
2616
2617
			if (!$this->errorSetResource && $accessToFolder) {
2618
				/**
2619
				 * First search on GlobalID(0x3)
2620
				 * 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.
2621
				 * If (normal meeting) then GlobalID(0x3) and CleanGlobalID(0x23) are same, so doesn't matter if search is based on GlobalID.
2622
				 */
2623
				$rows = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
2624
2625
				/*
2626
				 * If no entry is found then
2627
				 * 1) Resource doesn't have meeting in Calendar. Seriously!!
2628
				 * OR
2629
				 * 2) We were looking for occurrence item but Resource has whole series
2630
				 */
2631
				if (empty($rows)) {
2632
					/**
2633
					 * Now search on CleanGlobalID(0x23) WHY???
2634
					 * Because we are looking recurring item.
2635
					 *
2636
					 * Possible results of this search
2637
					 * 1) If Resource was booked for more than one occurrences then this search will return all those occurrence because search is perform on CleanGlobalID
2638
					 * 2) If Resource was booked for whole series then it should return series.
2639
					 */
2640
					$rows = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
2641
2642
					$newResourceMsg = false;
2643
					if (!empty($rows)) {
2644
						// Since we are looking for recurring item, open every result and check for 'recurring' property.
2645
						foreach ($rows as $row) {
2646
							$ResourceMsg = mapi_msgstore_openentry($userStore, $row);
2647
							$ResourceMsgProps = mapi_getprops($ResourceMsg, [$this->proptags['recurring']]);
2648
2649
							if (isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2650
								$newResourceMsg = $ResourceMsg;
2651
								break;
2652
							}
2653
						}
2654
					}
2655
2656
					// Still no results found. I giveup, create new message.
2657
					if (!$newResourceMsg) {
2658
						$newResourceMsg = mapi_folder_createmessage($calFolder);
0 ignored issues
show
The function mapi_folder_createmessage 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

2658
						$newResourceMsg = /** @scrutinizer ignore-call */ mapi_folder_createmessage($calFolder);
Loading history...
2659
					}
2660
				}
2661
				else {
2662
					$newResourceMsg = mapi_msgstore_openentry($userStore, $rows[0]);
2663
				}
2664
2665
				// Prefix the subject if needed
2666
				if ($prefix && isset($messageprops[PR_SUBJECT])) {
2667
					$messageprops[PR_SUBJECT] = $prefix . $messageprops[PR_SUBJECT];
2668
				}
2669
2670
				// Set status to cancelled if needed
2671
				$messageprops[$this->proptags['busystatus']] = fbBusy; // The default status (Busy)
2672
				if ($cancel) {
2673
					$messageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // The meeting has been canceled
2674
					$messageprops[$this->proptags['busystatus']] = fbFree; // Free
2675
				}
2676
				else {
2677
					$messageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
2678
				}
2679
				$messageprops[$this->proptags['responsestatus']] = olResponseAccepted; // The resource automatically accepts the appointment
2680
2681
				$messageprops[PR_MESSAGE_CLASS] = 'IPM.Appointment';
2682
2683
				// Remove the PR_ICON_INDEX as it is not needed in the sent message.
2684
				$messageprops[PR_ICON_INDEX] = null;
2685
				$messageprops[PR_RESPONSE_REQUESTED] = true;
2686
2687
				// get the store of organizer, in case of delegates it will be delegate store
2688
				$defaultStore = $this->openDefaultStore();
2689
2690
				$storeProps = mapi_getprops($this->store, [PR_ENTRYID]);
2691
				$defaultStoreProps = mapi_getprops($defaultStore, [PR_ENTRYID]);
2692
2693
				// @FIXME use entryid comparison functions here
2694
				if ($storeProps[PR_ENTRYID] !== $defaultStoreProps[PR_ENTRYID]) {
2695
					// get delegate information
2696
					$addrInfo = $this->getOwnerAddress($defaultStore, false);
2697
2698
					if (!empty($addrInfo)) {
2699
						list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrInfo;
2700
2701
						$messageprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
2702
						$messageprops[PR_SENDER_NAME] = $ownername;
2703
						$messageprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
2704
						$messageprops[PR_SENDER_ENTRYID] = $ownerentryid;
2705
						$messageprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
2706
					}
2707
2708
					// get delegator information
2709
					$addrInfo = $this->getOwnerAddress($this->store, false);
2710
2711
					if (!empty($addrInfo)) {
2712
						list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrInfo;
2713
2714
						$messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
2715
						$messageprops[PR_SENT_REPRESENTING_NAME] = $ownername;
2716
						$messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
2717
						$messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
2718
						$messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
2719
					}
2720
				}
2721
				else {
2722
					// get organizer information
2723
					$addrInfo = $this->getOwnerAddress($this->store);
2724
2725
					if (!empty($addrInfo)) {
2726
						list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrInfo;
2727
2728
						$messageprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
2729
						$messageprops[PR_SENDER_NAME] = $ownername;
2730
						$messageprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
2731
						$messageprops[PR_SENDER_ENTRYID] = $ownerentryid;
2732
						$messageprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
2733
2734
						$messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
2735
						$messageprops[PR_SENT_REPRESENTING_NAME] = $ownername;
2736
						$messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
2737
						$messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
2738
						$messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
2739
					}
2740
				}
2741
2742
				$messageprops[$this->proptags['replytime']] = time();
2743
2744
				if ($basedate && isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2745
					$recurr = new Recurrence($userStore, $newResourceMsg);
2746
2747
					// Copy recipients list
2748
					$reciptable = mapi_message_getrecipienttable($message);
2749
					$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2750
2751
					// add owner to recipient table
2752
					$this->addOrganizer($messageprops, $recips, true);
2753
2754
					// Update occurrence
2755
					if ($recurr->isException($basedate)) {
2756
						$recurr->modifyException($messageprops, $basedate, $recips);
2757
					}
2758
					else {
2759
						$recurr->createException($messageprops, $basedate, false, $recips);
2760
					}
2761
				}
2762
				else {
2763
					mapi_setprops($newResourceMsg, $messageprops);
0 ignored issues
show
The function mapi_setprops 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

2763
					/** @scrutinizer ignore-call */ 
2764
     mapi_setprops($newResourceMsg, $messageprops);
Loading history...
2764
2765
					// Copy attachments
2766
					$this->replaceAttachments($message, $newResourceMsg);
2767
2768
					// Copy all recipients too
2769
					$this->replaceRecipients($message, $newResourceMsg);
2770
2771
					// Now add organizer also to recipient table
2772
					$recips = [];
2773
					$this->addOrganizer($messageprops, $recips);
2774
2775
					mapi_message_modifyrecipients($newResourceMsg, MODRECIP_ADD, $recips);
0 ignored issues
show
The function mapi_message_modifyrecipients 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

2775
					/** @scrutinizer ignore-call */ 
2776
     mapi_message_modifyrecipients($newResourceMsg, MODRECIP_ADD, $recips);
Loading history...
2776
				}
2777
2778
				mapi_savechanges($newResourceMsg);
0 ignored issues
show
The function mapi_savechanges 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

2778
				/** @scrutinizer ignore-call */ 
2779
    mapi_savechanges($newResourceMsg);
Loading history...
2779
2780
				$resourceRecipData[] = [
2781
					'store' => $userStore,
2782
					'folder' => $calFolder,
2783
					'msg' => $newResourceMsg,
2784
				];
2785
				$this->includesResources = true;
2786
			}
2787
			else {
2788
				/*
2789
				 * If no other errors occurred and you have no access to the
2790
				 * folder of the resource, throw an error=1.
2791
				 */
2792
				if (!$this->errorSetResource) {
2793
					$this->errorSetResource = 1;
2794
				}
2795
2796
				for ($j = 0, $len = count($resourceRecipData); $j < $len; ++$j) {
2797
					// Get the EntryID
2798
					$props = mapi_message_getprops($resourceRecipData[$j]['msg']);
2799
2800
					mapi_folder_deletemessages($resourceRecipData[$j]['folder'], [$props[PR_ENTRYID]], DELETE_HARD_DELETE);
0 ignored issues
show
The function mapi_folder_deletemessages 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

2800
					/** @scrutinizer ignore-call */ 
2801
     mapi_folder_deletemessages($resourceRecipData[$j]['folder'], [$props[PR_ENTRYID]], DELETE_HARD_DELETE);
Loading history...
2801
				}
2802
				$this->recipientDisplayname = $resourceRecipients[$i][PR_DISPLAY_NAME];
2803
			}
2804
			++$i;
2805
		}
2806
2807
		/*
2808
		 * Set the BCC-recipients (resources) tackstatus to accepted.
2809
		 */
2810
		// Get resource recipients
2811
		$getResourcesRestriction = [
2812
			RES_PROPERTY,
2813
			[
2814
				RELOP => RELOP_EQ,	// Equals recipient type 3: Resource
2815
				ULPROPTAG => PR_RECIPIENT_TYPE,
2816
				VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2817
			],
2818
		];
2819
		$recipienttable = mapi_message_getrecipienttable($message);
2820
		$resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
2821
		if (!empty($resourceRecipients)) {
2822
			// Set Tracking status of resource recipients to olResponseAccepted (3)
2823
			for ($i = 0, $len = count($resourceRecipients); $i < $len; ++$i) {
2824
				$resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusAccepted;
2825
				$resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS_TIME] = time();
2826
			}
2827
			mapi_message_modifyrecipients($message, MODRECIP_MODIFY, $resourceRecipients);
2828
		}
2829
2830
		return $resourceRecipData;
2831
	}
2832
2833
	/**
2834
	 * Function which save an exception into recurring item.
2835
	 *
2836
	 * @param resource $recurringItem  reference to MAPI_message of recurring item
2837
	 * @param resource $occurrenceItem reference to MAPI_message of occurrence
2838
	 * @param string   $basedate       basedate of occurrence
2839
	 * @param bool     $move           if true then occurrence item is deleted
2840
	 * @param bool     $tentative      true if user has tentatively accepted it or false if user has accepted it
2841
	 * @param bool     $userAction     true if user has manually responded to meeting request
2842
	 * @param resource $store          user store
2843
	 * @param bool     $isDelegate     true if delegate is processing this meeting request
2844
	 */
2845
	public function acceptException(&$recurringItem, &$occurrenceItem, $basedate, $move, $tentative, $userAction, $store, $isDelegate = false): void {
2846
		$recurr = new Recurrence($store, $recurringItem);
2847
2848
		// Copy properties from meeting request
2849
		$exception_props = mapi_getprops($occurrenceItem);
0 ignored issues
show
The function mapi_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

2849
		$exception_props = /** @scrutinizer ignore-call */ mapi_getprops($occurrenceItem);
Loading history...
2850
2851
		// Copy recipients list
2852
		$reciptable = mapi_message_getrecipienttable($occurrenceItem);
0 ignored issues
show
The function mapi_message_getrecipienttable 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

2852
		$reciptable = /** @scrutinizer ignore-call */ mapi_message_getrecipienttable($occurrenceItem);
Loading history...
2853
		// If delegate, then do not add the delegate in recipients
2854
		if ($isDelegate) {
2855
			$delegate = mapi_getprops($this->message, [PR_RECEIVED_BY_EMAIL_ADDRESS]);
2856
			$res = [
2857
				RES_PROPERTY,
2858
				[
2859
					RELOP => RELOP_NE,
2860
					ULPROPTAG => PR_EMAIL_ADDRESS,
2861
					VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]],
2862
				],
2863
			];
2864
			$recips = mapi_table_queryallrows($reciptable, $this->recipprops, $res);
0 ignored issues
show
The function mapi_table_queryallrows 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

2864
			$recips = /** @scrutinizer ignore-call */ mapi_table_queryallrows($reciptable, $this->recipprops, $res);
Loading history...
2865
		}
2866
		else {
2867
			$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2868
		}
2869
2870
		// add owner to recipient table
2871
		$this->addOrganizer($exception_props, $recips, true);
2872
2873
		// add delegator to meetings
2874
		if ($isDelegate) {
2875
			$this->addDelegator($exception_props, $recips);
2876
		}
2877
2878
		$exception_props[$this->proptags['meetingstatus']] = olMeetingReceived;
2879
		$exception_props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
2880
2881
		if (isset($exception_props[$this->proptags['intendedbusystatus']])) {
2882
			if ($tentative && $exception_props[$this->proptags['intendedbusystatus']] !== fbFree) {
2883
				$exception_props[$this->proptags['busystatus']] = fbTentative;
2884
			}
2885
			else {
2886
				$exception_props[$this->proptags['busystatus']] = $exception_props[$this->proptags['intendedbusystatus']];
2887
			}
2888
		// we already have intendedbusystatus value in $exception_props so no need to copy it
2889
		}
2890
		else {
2891
			$exception_props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
2892
		}
2893
2894
		if ($userAction) {
2895
			$addrInfo = $this->getOwnerAddress($this->store);
2896
2897
			// if user has responded then set replytime and name
2898
			$exception_props[$this->proptags['replytime']] = time();
2899
			if (!empty($addrInfo)) {
2900
				$exception_props[$this->proptags['apptreplyname']] = $addrInfo[0];
2901
			}
2902
		}
2903
2904
		if ($recurr->isException($basedate)) {
2905
			$recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
2906
		}
2907
		else {
2908
			$recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
2909
		}
2910
2911
		// Move the occurrenceItem to the waste basket
2912
		if ($move) {
2913
			$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
2914
			$sourcefolder = mapi_msgstore_openentry($store, $exception_props[PR_PARENT_ENTRYID]);
0 ignored issues
show
The function mapi_msgstore_openentry 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

2914
			$sourcefolder = /** @scrutinizer ignore-call */ mapi_msgstore_openentry($store, $exception_props[PR_PARENT_ENTRYID]);
Loading history...
2915
			mapi_folder_copymessages($sourcefolder, [$exception_props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
0 ignored issues
show
The function mapi_folder_copymessages 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

2915
			/** @scrutinizer ignore-call */ 
2916
   mapi_folder_copymessages($sourcefolder, [$exception_props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
Loading history...
2916
		}
2917
2918
		mapi_savechanges($recurringItem);
0 ignored issues
show
The function mapi_savechanges 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

2918
		/** @scrutinizer ignore-call */ 
2919
  mapi_savechanges($recurringItem);
Loading history...
2919
	}
2920
2921
	/**
2922
	 * Function which merges an exception mapi message to recurring message.
2923
	 * This will be used when we receive recurring meeting request and we already have an exception message
2924
	 * of same meeting in calendar and we need to remove that exception message and add it to attachment table
2925
	 * of recurring meeting.
2926
	 *
2927
	 * @param resource $recurringItem  reference to MAPI_message of recurring item
2928
	 * @param resource $occurrenceItem reference to MAPI_message of occurrence
2929
	 * @param mixed    $basedate       basedate of occurrence
2930
	 * @param resource $store          user store
2931
	 */
2932
	public function mergeException(&$recurringItem, &$occurrenceItem, $basedate, $store): void {
2933
		$recurr = new Recurrence($store, $recurringItem);
2934
2935
		// Copy properties from meeting request
2936
		$exception_props = mapi_getprops($occurrenceItem);
0 ignored issues
show
The function mapi_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

2936
		$exception_props = /** @scrutinizer ignore-call */ mapi_getprops($occurrenceItem);
Loading history...
2937
2938
		// Get recipient list from message and add it to exception attachment
2939
		$reciptable = mapi_message_getrecipienttable($occurrenceItem);
0 ignored issues
show
The function mapi_message_getrecipienttable 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

2939
		$reciptable = /** @scrutinizer ignore-call */ mapi_message_getrecipienttable($occurrenceItem);
Loading history...
2940
		$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
0 ignored issues
show
The function mapi_table_queryallrows 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

2940
		$recips = /** @scrutinizer ignore-call */ mapi_table_queryallrows($reciptable, $this->recipprops);
Loading history...
2941
2942
		if ($recurr->isException($basedate)) {
2943
			$recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
2944
		}
2945
		else {
2946
			$recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
2947
		}
2948
2949
		// Move the occurrenceItem to the waste basket
2950
		$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
2951
		$sourcefolder = mapi_msgstore_openentry($store, $exception_props[PR_PARENT_ENTRYID]);
0 ignored issues
show
The function mapi_msgstore_openentry 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

2951
		$sourcefolder = /** @scrutinizer ignore-call */ mapi_msgstore_openentry($store, $exception_props[PR_PARENT_ENTRYID]);
Loading history...
2952
		mapi_folder_copymessages($sourcefolder, [$exception_props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
0 ignored issues
show
The function mapi_folder_copymessages 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

2952
		/** @scrutinizer ignore-call */ 
2953
  mapi_folder_copymessages($sourcefolder, [$exception_props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
Loading history...
2953
2954
		mapi_savechanges($recurringItem);
0 ignored issues
show
The function mapi_savechanges 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

2954
		/** @scrutinizer ignore-call */ 
2955
  mapi_savechanges($recurringItem);
Loading history...
2955
	}
2956
2957
	/**
2958
	 * Function which submits meeting request based on arguments passed to it.
2959
	 *
2960
	 * @param resource $message        MAPI_message whose meeting request is to be sent
2961
	 * @param bool     $cancel         if true send request, else send cancellation
2962
	 * @param mixed    $prefix         subject prefix
2963
	 * @param mixed    $basedate       basedate for an occurrence
2964
	 * @param mixed    $recurObject    recurrence object of mr
2965
	 * @param bool     $copyExceptions When sending update mail for recurring item then we don't send exceptions in attachments
2966
	 * @param mixed    $modifiedRecips
2967
	 * @param mixed    $deletedRecips
2968
	 */
2969
	public function submitMeetingRequest($message, $cancel, $prefix, $basedate = false, $recurObject = false, $copyExceptions = true, $modifiedRecips = false, $deletedRecips = false): void {
2970
		$newmessageprops = $messageprops = mapi_getprops($this->message);
0 ignored issues
show
The function mapi_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

2970
		$newmessageprops = $messageprops = /** @scrutinizer ignore-call */ mapi_getprops($this->message);
Loading history...
2971
		$new = $this->createOutgoingMessage();
2972
2973
		// Copy the entire message into the new meeting request message
2974
		if ($basedate) {
2975
			// messageprops contains properties of whole recurring series
2976
			// and newmessageprops contains properties of exception item
2977
			$newmessageprops = mapi_getprops($message);
2978
2979
			// Ensure that the correct basedate is set in the new message
2980
			$newmessageprops[$this->proptags['basedate']] = $basedate;
2981
2982
			// Set isRecurring to false, because this is an exception
2983
			$newmessageprops[$this->proptags['recurring']] = false;
2984
2985
			// set LID_IS_EXCEPTION to true
2986
			$newmessageprops[$this->proptags['is_exception']] = true;
2987
2988
			// Set to high importance
2989
			if ($cancel) {
2990
				$newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;
2991
			}
2992
2993
			// Set startdate and enddate of exception
2994
			if ($cancel && $recurObject) {
2995
				$newmessageprops[$this->proptags['startdate']] = $recurObject->getOccurrenceStart($basedate);
2996
				$newmessageprops[$this->proptags['duedate']] = $recurObject->getOccurrenceEnd($basedate);
2997
			}
2998
2999
			// Set basedate in guid (0x3)
3000
			$newmessageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
3001
			$newmessageprops[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
3002
			$newmessageprops[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
3003
3004
			// Get deleted recipiets from exception msg
3005
			$restriction = [
3006
				RES_AND,
3007
				[
3008
					[
3009
						RES_BITMASK,
3010
						[
3011
							ULTYPE => BMR_NEZ,
3012
							ULPROPTAG => PR_RECIPIENT_FLAGS,
3013
							ULMASK => recipExceptionalDeleted,
3014
						],
3015
					],
3016
					[
3017
						RES_BITMASK,
3018
						[
3019
							ULTYPE => BMR_EQZ,
3020
							ULPROPTAG => PR_RECIPIENT_FLAGS,
3021
							ULMASK => recipOrganizer,
3022
						],
3023
					],
3024
				],
3025
			];
3026
3027
			// In direct-booking mode, we don't need to send cancellations to resources
3028
			if ($this->enableDirectBooking) {
3029
				$restriction[1][] = [
3030
					RES_PROPERTY,
3031
					[
3032
						RELOP => RELOP_NE,	// Does not equal recipient type: MAPI_BCC (Resource)
3033
						ULPROPTAG => PR_RECIPIENT_TYPE,
3034
						VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
3035
					],
3036
				];
3037
			}
3038
3039
			$recipienttable = mapi_message_getrecipienttable($message);
0 ignored issues
show
The function mapi_message_getrecipienttable 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

3039
			$recipienttable = /** @scrutinizer ignore-call */ mapi_message_getrecipienttable($message);
Loading history...
3040
			$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $restriction);
0 ignored issues
show
The function mapi_table_queryallrows 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

3040
			$recipients = /** @scrutinizer ignore-call */ mapi_table_queryallrows($recipienttable, $this->recipprops, $restriction);
Loading history...
3041
3042
			if (!$deletedRecips) {
3043
				$deletedRecips = array_merge([], $recipients);
3044
			}
3045
			else {
3046
				$deletedRecips = array_merge($deletedRecips, $recipients);
3047
			}
3048
		}
3049
3050
		// Remove the PR_ICON_INDEX as it is not needed in the sent message.
3051
		$newmessageprops[PR_ICON_INDEX] = null;
3052
		$newmessageprops[PR_RESPONSE_REQUESTED] = true;
3053
3054
		// PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
3055
		$newmessageprops[PR_START_DATE] = $newmessageprops[$this->proptags['startdate']];
3056
		$newmessageprops[PR_END_DATE] = $newmessageprops[$this->proptags['duedate']];
3057
3058
		// Set updatecounter/AppointmentSequenceNumber
3059
		// get the value of latest updatecounter for the whole series and use it
3060
		$newmessageprops[$this->proptags['updatecounter']] = $messageprops[$this->proptags['last_updatecounter']];
3061
3062
		$meetingTimeInfo = $this->getMeetingTimeInfo();
3063
3064
		if ($meetingTimeInfo) {
3065
			// Needs to unset PR_HTML and PR_RTF_COMPRESSED props
3066
			// because while canceling meeting requests with edit text
3067
			// will override the PR_BODY because body value is not consistent with
3068
			// PR_HTML and PR_RTF_COMPRESSED value so in this case PR_RTF_COMPRESSED will
3069
			// get priority which override the PR_BODY value.
3070
			unset($newmessageprops[PR_HTML], $newmessageprops[PR_RTF_COMPRESSED]);
3071
3072
			$newmessageprops[PR_BODY] = $meetingTimeInfo;
3073
		}
3074
3075
		// Send all recurrence info in mail, if this is a recurrence meeting.
3076
		if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']]) {
3077
			if (!empty($messageprops[$this->proptags['recurring_pattern']])) {
3078
				$newmessageprops[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
3079
			}
3080
			$newmessageprops[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
3081
			$newmessageprops[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
3082
			$newmessageprops[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
3083
3084
			if ($recurObject) {
3085
				$this->generateRecurDates($recurObject, $messageprops, $newmessageprops);
3086
			}
3087
		}
3088
3089
		if (isset($newmessageprops[$this->proptags['counter_proposal']])) {
3090
			unset($newmessageprops[$this->proptags['counter_proposal']]);
3091
		}
3092
3093
		// Prefix the subject if needed
3094
		if ($prefix && isset($newmessageprops[PR_SUBJECT])) {
3095
			$newmessageprops[PR_SUBJECT] = $prefix . $newmessageprops[PR_SUBJECT];
3096
		}
3097
3098
		if (isset($newmessageprops[$this->proptags['categories']]) &&
3099
			!empty($newmessageprops[$this->proptags['categories']])) {
3100
			unset($newmessageprops[$this->proptags['categories']]);
3101
		}
3102
		mapi_setprops($new, $newmessageprops);
0 ignored issues
show
The function mapi_setprops 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

3102
		/** @scrutinizer ignore-call */ 
3103
  mapi_setprops($new, $newmessageprops);
Loading history...
3103
3104
		// Copy attachments
3105
		$this->replaceAttachments($message, $new, $copyExceptions);
3106
3107
		// Retrieve only those recipient who should receive this meeting request.
3108
		$stripResourcesRestriction = [
3109
			RES_AND,
3110
			[
3111
				[
3112
					RES_BITMASK,
3113
					[
3114
						ULTYPE => BMR_EQZ,
3115
						ULPROPTAG => PR_RECIPIENT_FLAGS,
3116
						ULMASK => recipExceptionalDeleted,
3117
					],
3118
				],
3119
				[
3120
					RES_BITMASK,
3121
					[
3122
						ULTYPE => BMR_EQZ,
3123
						ULPROPTAG => PR_RECIPIENT_FLAGS,
3124
						ULMASK => recipOrganizer,
3125
					],
3126
				],
3127
			],
3128
		];
3129
3130
		// In direct-booking mode, resources do not receive a meeting request
3131
		if ($this->enableDirectBooking) {
3132
			$stripResourcesRestriction[1][] = [
3133
				RES_PROPERTY,
3134
				[
3135
					RELOP => RELOP_NE,	// Does not equal recipient type: MAPI_BCC (Resource)
3136
					ULPROPTAG => PR_RECIPIENT_TYPE,
3137
					VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
3138
				],
3139
			];
3140
		}
3141
3142
		// If no recipients were explicitly provided, we will send the update to all
3143
		// recipients from the meeting.
3144
		if ($modifiedRecips === false) {
3145
			$recipienttable = mapi_message_getrecipienttable($message);
3146
			$modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
3147
3148
			if ($basedate && empty($modifiedRecips)) {
3149
				// Retrieve full list
3150
				$recipienttable = mapi_message_getrecipienttable($this->message);
3151
				$modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops);
3152
3153
				// Save recipients in exceptions
3154
				mapi_message_modifyrecipients($message, MODRECIP_ADD, $modifiedRecips);
0 ignored issues
show
The function mapi_message_modifyrecipients 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

3154
				/** @scrutinizer ignore-call */ 
3155
    mapi_message_modifyrecipients($message, MODRECIP_ADD, $modifiedRecips);
Loading history...
3155
3156
				// Now retrieve only those recipient who should receive this meeting request.
3157
				$modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
3158
			}
3159
		}
3160
3161
		// @TODO: handle nonAcceptingResources
3162
		/*
3163
		 * Add resource recipients that did not automatically accept the meeting request.
3164
		 * (note: meaning that they did not decline the meeting request)
3165
		 */ /*
3166
		for($i=0;$i<count($this->nonAcceptingResources);$i++){
3167
			$recipients[] = $this->nonAcceptingResources[$i];
3168
		}*/
3169
3170
		if (!empty($modifiedRecips)) {
3171
			// Strip out the sender/'owner' recipient
3172
			mapi_message_modifyrecipients($new, MODRECIP_ADD, $modifiedRecips);
3173
3174
			// Set some properties that are different in the sent request than
3175
			// in the item in our calendar
3176
3177
			// we should store busystatus value to intendedbusystatus property, because busystatus for outgoing meeting request
3178
			// should always be fbTentative
3179
			$newmessageprops[$this->proptags['intendedbusystatus']] = isset($newmessageprops[$this->proptags['busystatus']]) ? $newmessageprops[$this->proptags['busystatus']] : $messageprops[$this->proptags['busystatus']];
3180
			$newmessageprops[$this->proptags['busystatus']] = fbTentative; // The default status when not accepted
3181
			$newmessageprops[$this->proptags['responsestatus']] = olResponseNotResponded; // The recipient has not responded yet
3182
			$newmessageprops[$this->proptags['attendee_critical_change']] = time();
3183
			$newmessageprops[$this->proptags['owner_critical_change']] = time();
3184
			$newmessageprops[$this->proptags['meetingtype']] = mtgRequest;
3185
3186
			if ($cancel) {
3187
				$newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled';
3188
				$newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
3189
				$newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
3190
			}
3191
			else {
3192
				$newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Request';
3193
				$newmessageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
3194
			}
3195
3196
			mapi_setprops($new, $newmessageprops);
3197
			mapi_savechanges($new);
0 ignored issues
show
The function mapi_savechanges 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

3197
			/** @scrutinizer ignore-call */ 
3198
   mapi_savechanges($new);
Loading history...
3198
3199
			// Submit message to non-resource recipients
3200
			mapi_message_submitmessage($new);
0 ignored issues
show
The function mapi_message_submitmessage 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

3200
			/** @scrutinizer ignore-call */ 
3201
   mapi_message_submitmessage($new);
Loading history...
3201
		}
3202
3203
		// Search through the deleted recipients, and see if any of them is also
3204
		// listed as a recipient to whom we have sent an update. As we don't
3205
		// want to send a cancellation message to recipients who will also receive
3206
		// an meeting update, we have to filter those recipients out.
3207
		if ($deletedRecips) {
3208
			$tmp = [];
3209
3210
			foreach ($deletedRecips as $delRecip) {
3211
				$found = false;
3212
3213
				// Search if the deleted recipient can be found inside
3214
				// the updated recipients as well.
3215
				foreach ($modifiedRecips as $recip) {
3216
					if ($this->compareABEntryIDs($recip[PR_ENTRYID], $delRecip[PR_ENTRYID])) {
3217
						$found = true;
3218
						break;
3219
					}
3220
				}
3221
3222
				// If the recipient was not found, it truly is deleted,
3223
				// and we can safely send a cancellation message
3224
				if (!$found) {
3225
					$tmp[] = $delRecip;
3226
				}
3227
			}
3228
3229
			$deletedRecips = $tmp;
3230
		}
3231
3232
		// Send cancellation to deleted attendees
3233
		if ($deletedRecips) {
3234
			$new = $this->createOutgoingMessage();
3235
3236
			mapi_message_modifyrecipients($new, MODRECIP_ADD, $deletedRecips);
3237
3238
			$newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled';
3239
			$newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
3240
			$newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
3241
			$newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;	// HIGH Importance
3242
			if (isset($newmessageprops[PR_SUBJECT])) {
3243
				$newmessageprops[PR_SUBJECT] = dgettext('zarafa', 'Canceled') . ': ' . $newmessageprops[PR_SUBJECT];
3244
			}
3245
3246
			mapi_setprops($new, $newmessageprops);
3247
			mapi_savechanges($new);
3248
3249
			// Submit message to non-resource recipients
3250
			mapi_message_submitmessage($new);
3251
		}
3252
3253
		// Set properties on meeting object in calendar
3254
		// Set requestsent to 'true' (turns on 'tracking', etc)
3255
		$props = [];
3256
		$props[$this->proptags['meetingstatus']] = olMeeting;
3257
		$props[$this->proptags['responsestatus']] = olResponseOrganized;
3258
		// Only set the 'requestsent' property if it wasn't set previously yet,
3259
		// this ensures we will not accidentally set it from true to false.
3260
		if (!isset($messageprops[$this->proptags['requestsent']]) || $messageprops[$this->proptags['requestsent']] !== true) {
3261
			$props[$this->proptags['requestsent']] = !empty($modifiedRecips) || ($this->includesResources && !$this->errorSetResource);
3262
		}
3263
		$props[$this->proptags['attendee_critical_change']] = time();
3264
		$props[$this->proptags['owner_critical_change']] = time();
3265
		$props[$this->proptags['meetingtype']] = mtgRequest;
3266
		// save the new updatecounter to exception/recurring series/normal meeting
3267
		$props[$this->proptags['updatecounter']] = $newmessageprops[$this->proptags['updatecounter']];
3268
3269
		// PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
3270
		$props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
3271
		$props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
3272
3273
		mapi_setprops($message, $props);
3274
3275
		// saving of these properties on calendar item should be handled by caller function
3276
		// based on sending meeting request was successful or not
3277
	}
3278
3279
	/**
3280
	 * OL2007 uses these 4 properties to specify occurrence that should be updated.
3281
	 * ical generates RECURRENCE-ID property based on exception's basedate (PidLidExceptionReplaceTime),
3282
	 * but OL07 doesn't send this property, so ical will generate RECURRENCE-ID property based on date
3283
	 * from GlobalObjId and time from StartRecurTime property, so we are sending basedate property and
3284
	 * also additionally we are sending these properties.
3285
	 * Ref: MS-OXCICAL 2.2.1.20.20 Property: RECURRENCE-ID.
3286
	 *
3287
	 * @param object $recurObject     instance of recurrence class for this message
3288
	 * @param array  $messageprops    properties of meeting object that is going to be sent
3289
	 * @param array  $newmessageprops properties of meeting request/response that is going to be sent
3290
	 */
3291
	public function generateRecurDates($recurObject, $messageprops, &$newmessageprops): void {
3292
		if ($messageprops[$this->proptags['startdate']] && $messageprops[$this->proptags['duedate']]) {
3293
			$startDate = date('Y:n:j:G:i:s', $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['startdate']]));
3294
			$endDate = date('Y:n:j:G:i:s', $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['duedate']]));
3295
3296
			$startDate = explode(':', $startDate);
3297
			$endDate = explode(':', $endDate);
3298
3299
			// [0] => year, [1] => month, [2] => day, [3] => hour, [4] => minutes, [5] => seconds
3300
			// RecurStartDate = year * 512 + month_number * 32 + day_number
3301
			$newmessageprops[$this->proptags['start_recur_date']] = (((int) $startDate[0]) * 512) + (((int) $startDate[1]) * 32) + ((int) $startDate[2]);
3302
			// RecurStartTime = hour * 4096 + minutes * 64 + seconds
3303
			$newmessageprops[$this->proptags['start_recur_time']] = (((int) $startDate[3]) * 4096) + (((int) $startDate[4]) * 64) + ((int) $startDate[5]);
3304
3305
			$newmessageprops[$this->proptags['end_recur_date']] = (((int) $endDate[0]) * 512) + (((int) $endDate[1]) * 32) + ((int) $endDate[2]);
3306
			$newmessageprops[$this->proptags['end_recur_time']] = (((int) $endDate[3]) * 4096) + (((int) $endDate[4]) * 64) + ((int) $endDate[5]);
3307
		}
3308
	}
3309
3310
	/**
3311
	 * Function will create a new outgoing message that will be used to send meeting mail.
3312
	 *
3313
	 * @param mixed $store (optional) store that is used when creating response, if delegate is creating outgoing mail
3314
	 *                     then this would point to delegate store
3315
	 *
3316
	 * @return resource outgoing mail that is created and can be used for sending it
3317
	 */
3318
	public function createOutgoingMessage($store = false) {
3319
		// get logged in user's store that will be used to send mail, for delegate this will be
3320
		// delegate store
3321
		$userStore = $this->openDefaultStore();
3322
3323
		$sentprops = [];
3324
		$outbox = $this->openDefaultOutbox($userStore);
3325
3326
		$outgoing = mapi_folder_createmessage($outbox);
0 ignored issues
show
The function mapi_folder_createmessage 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

3326
		$outgoing = /** @scrutinizer ignore-call */ mapi_folder_createmessage($outbox);
Loading history...
3327
3328
		// check if $store is set and it is not equal to $defaultStore (means its the delegation case)
3329
		if ($store !== false) {
3330
			$storeProps = mapi_getprops($store, [PR_ENTRYID]);
0 ignored issues
show
The function mapi_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

3330
			$storeProps = /** @scrutinizer ignore-call */ mapi_getprops($store, [PR_ENTRYID]);
Loading history...
3331
			$userStoreProps = mapi_getprops($userStore, [PR_ENTRYID]);
3332
3333
			// @FIXME use entryid comparison functions here
3334
			if ($storeProps[PR_ENTRYID] !== $userStoreProps[PR_ENTRYID]) {
3335
				// get the delegator properties and set it into outgoing mail
3336
				$delegatorDetails = $this->getOwnerAddress($store, false);
3337
3338
				if (!empty($delegatorDetails)) {
3339
					list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $delegatorDetails;
3340
					$sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
3341
					$sentprops[PR_SENT_REPRESENTING_NAME] = $ownername;
3342
					$sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
3343
					$sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
3344
					$sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
3345
				}
3346
3347
				// get the delegate properties and set it into outgoing mail
3348
				$delegateDetails = $this->getOwnerAddress($userStore, false);
3349
3350
				if (!empty($delegateDetails)) {
3351
					list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $delegateDetails;
3352
					$sentprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
3353
					$sentprops[PR_SENDER_NAME] = $ownername;
3354
					$sentprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
3355
					$sentprops[PR_SENDER_ENTRYID] = $ownerentryid;
3356
					$sentprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
3357
				}
3358
			}
3359
		}
3360
		else {
3361
			// normal user is sending mail, so both set of properties will be same
3362
			$userDetails = $this->getOwnerAddress($userStore);
3363
3364
			if (!empty($userDetails)) {
3365
				list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $userDetails;
3366
				$sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
3367
				$sentprops[PR_SENT_REPRESENTING_NAME] = $ownername;
3368
				$sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
3369
				$sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
3370
				$sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
3371
3372
				$sentprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
3373
				$sentprops[PR_SENDER_NAME] = $ownername;
3374
				$sentprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
3375
				$sentprops[PR_SENDER_ENTRYID] = $ownerentryid;
3376
				$sentprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
3377
			}
3378
		}
3379
3380
		$sentprops[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID($userStore);
3381
3382
		mapi_setprops($outgoing, $sentprops);
0 ignored issues
show
The function mapi_setprops 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

3382
		/** @scrutinizer ignore-call */ 
3383
  mapi_setprops($outgoing, $sentprops);
Loading history...
3383
3384
		return $outgoing;
3385
	}
3386
3387
	/**
3388
	 * Function which checks that meeting in attendee's calendar is already updated
3389
	 * and we are checking an old meeting request. This function also will update property
3390
	 * meetingtype to indicate that its out of date meeting request.
3391
	 *
3392
	 * @return bool true if meeting request is outofdate else false if it is new
3393
	 */
3394
	public function isMeetingOutOfDate() {
3395
		$result = false;
3396
3397
		$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
The function mapi_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

3397
		$props = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], $this->proptags['updatecounter'], $this->proptags['meetingtype'], $this->proptags['owner_critical_change']]);
Loading history...
3398
3399
		if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS])) {
3400
			return $result;
3401
		}
3402
3403
		if (isset($props[$this->proptags['meetingtype']]) && ($props[$this->proptags['meetingtype']] & mtgOutOfDate) == mtgOutOfDate) {
3404
			return true;
3405
		}
3406
3407
		// get the basedate to check for exception
3408
		$basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]);
3409
3410
		$calendarItem = $this->getCorrespondentCalendarItem(true);
3411
3412
		// if basedate is provided and we could not find the item then it could be that we are checking
3413
		// an exception so get the exception and check it
3414
		if ($basedate !== false && $calendarItem !== false) {
3415
			$exception = $this->getExceptionItem($calendarItem, $basedate);
3416
3417
			if ($exception !== false) {
3418
				// we are able to find the exception compare with it
3419
				$calendarItem = $exception;
3420
			}
3421
			// we are not able to find exception, could mean that a significant change has occurred on series
3422
			// and it deleted all exceptions, so compare with series
3423
			// $calendarItem already contains reference to series
3424
		}
3425
3426
		if ($calendarItem !== false) {
3427
			$calendarItemProps = mapi_getprops($calendarItem, [
3428
				$this->proptags['owner_critical_change'],
3429
				$this->proptags['updatecounter'],
3430
			]);
3431
3432
			$updateCounter = (isset($calendarItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]);
3433
3434
			$criticalChange = (isset($calendarItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $calendarItemProps[$this->proptags['owner_critical_change']]);
3435
3436
			if ($updateCounter || $criticalChange) {
3437
				// meeting request is out of date, set properties to indicate this
3438
				mapi_setprops($this->message, [$this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033]);
0 ignored issues
show
The function mapi_setprops 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

3438
				/** @scrutinizer ignore-call */ 
3439
    mapi_setprops($this->message, [$this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033]);
Loading history...
3439
				mapi_savechanges($this->message);
0 ignored issues
show
The function mapi_savechanges 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

3439
				/** @scrutinizer ignore-call */ 
3440
    mapi_savechanges($this->message);
Loading history...
3440
3441
				$result = true;
3442
			}
3443
		}
3444
3445
		return $result;
3446
	}
3447
3448
	/**
3449
	 * Function which checks that if we have received a meeting response for an updated meeting in organizer's calendar.
3450
	 *
3451
	 * @param mixed $basedate basedate of the exception if we want to compare with exception
3452
	 *
3453
	 * @return bool true if meeting request is updated later
3454
	 */
3455
	public function isMeetingUpdated($basedate = false) {
3456
		$result = false;
3457
3458
		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['updatecounter']]);
0 ignored issues
show
The function mapi_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

3458
		$props = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['updatecounter']]);
Loading history...
3459
3460
		if (!$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS])) {
3461
			return $result;
3462
		}
3463
3464
		$calendarItem = $this->getCorrespondentCalendarItem(true);
3465
3466
		if ($calendarItem !== false) {
3467
			// basedate is provided so open exception
3468
			if ($basedate !== false) {
3469
				$exception = $this->getExceptionItem($calendarItem, $basedate);
3470
3471
				if ($exception !== false) {
3472
					// we are able to find the exception compare with it
3473
					$calendarItem = $exception;
3474
				}
3475
				// we are not able to find exception, could mean that a significant change has occurred on series
3476
				// and it deleted all exceptions, so compare with series
3477
				// $calendarItem already contains reference to series
3478
			}
3479
3480
			if ($calendarItem !== false) {
3481
				$calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['updatecounter']]);
3482
3483
				/*
3484
				 * if(message_counter < appointment_counter) meeting object is newer then meeting response (meeting is updated)
3485
				 * if(message_counter >= appointment_counter) meeting is not updated, do normal processing
3486
				 */
3487
				if (isset($calendarItemProps[$this->proptags['updatecounter']], $props[$this->proptags['updatecounter']])) {
3488
					if ($props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]) {
3489
						$result = true;
3490
					}
3491
				}
3492
			}
3493
		}
3494
3495
		return $result;
3496
	}
3497
3498
	/**
3499
	 * Checks if there has been any significant changes on appointment/meeting item.
3500
	 * Significant changes be:
3501
	 * 1) startdate has been changed
3502
	 * 2) duedate has been changed OR
3503
	 * 3) recurrence pattern has been created, modified or removed.
3504
	 *
3505
	 * @param mixed $oldProps
3506
	 * @param mixed $basedate
3507
	 * @param mixed $isRecurrenceChanged for change in recurrence pattern.
3508
	 *                                   true means Recurrence pattern has been changed,
3509
	 *                                   so clear all attendees response
3510
	 */
3511
	public function checkSignificantChanges($oldProps, $basedate, $isRecurrenceChanged = false) {
3512
		$message = null;
3513
		$attach = null;
3514
3515
		// If basedate is specified then we need to open exception message to clear recipient responses
3516
		if ($basedate) {
3517
			$recurrence = new Recurrence($this->store, $this->message);
3518
			if ($recurrence->isException($basedate)) {
3519
				$attach = $recurrence->getExceptionAttachment($basedate);
3520
				if ($attach) {
3521
					$message = mapi_attach_openobj($attach, MAPI_MODIFY);
0 ignored issues
show
The function mapi_attach_openobj 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

3521
					$message = /** @scrutinizer ignore-call */ mapi_attach_openobj($attach, MAPI_MODIFY);
Loading history...
3522
				}
3523
			}
3524
		}
3525
		else {
3526
			// use normal message or recurring series message
3527
			$message = $this->message;
3528
		}
3529
3530
		if (!$message) {
3531
			return;
3532
		}
3533
3534
		$newProps = mapi_getprops($message, [$this->proptags['startdate'], $this->proptags['duedate'], $this->proptags['updatecounter']]);
0 ignored issues
show
The function mapi_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

3534
		$newProps = /** @scrutinizer ignore-call */ mapi_getprops($message, [$this->proptags['startdate'], $this->proptags['duedate'], $this->proptags['updatecounter']]);
Loading history...
3535
3536
		// Check whether message is updated or not.
3537
		if (isset($newProps[$this->proptags['updatecounter']]) && $newProps[$this->proptags['updatecounter']] == 0) {
3538
			return;
3539
		}
3540
3541
		if (($newProps[$this->proptags['startdate']] != $oldProps[$this->proptags['startdate']]) ||
3542
				($newProps[$this->proptags['duedate']] != $oldProps[$this->proptags['duedate']]) ||
3543
				$isRecurrenceChanged) {
3544
			$this->clearRecipientResponse($message);
3545
3546
			mapi_setprops($message, [$this->proptags['owner_critical_change'] => time()]);
0 ignored issues
show
The function mapi_setprops 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

3546
			/** @scrutinizer ignore-call */ 
3547
   mapi_setprops($message, [$this->proptags['owner_critical_change'] => time()]);
Loading history...
3547
3548
			mapi_savechanges($message);
0 ignored issues
show
The function mapi_savechanges 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

3548
			/** @scrutinizer ignore-call */ 
3549
   mapi_savechanges($message);
Loading history...
3549
			if ($attach) { // Also save attachment Object.
3550
				mapi_savechanges($attach);
3551
			}
3552
		}
3553
	}
3554
3555
	/**
3556
	 * Clear responses of all attendees who have replied in past.
3557
	 *
3558
	 * @param resource $message on which responses should be cleared
3559
	 */
3560
	public function clearRecipientResponse($message): void {
3561
		$recipTable = mapi_message_getrecipienttable($message);
0 ignored issues
show
The function mapi_message_getrecipienttable 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

3561
		$recipTable = /** @scrutinizer ignore-call */ mapi_message_getrecipienttable($message);
Loading history...
3562
		$recipsRows = mapi_table_queryallrows($recipTable, $this->recipprops);
0 ignored issues
show
The function mapi_table_queryallrows 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

3562
		$recipsRows = /** @scrutinizer ignore-call */ mapi_table_queryallrows($recipTable, $this->recipprops);
Loading history...
3563
3564
		foreach ($recipsRows as $recipient) {
3565
			if (($recipient[PR_RECIPIENT_FLAGS] & recipOrganizer) != recipOrganizer) {
3566
				// Recipient is attendee, set the trackstatus to 'Not Responded'
3567
				$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3568
			}
3569
			else {
3570
				// Recipient is organizer, this is not possible, but for safety
3571
				// it is best to clear the trackstatus for him as well by setting
3572
				// the trackstatus to 'Organized'.
3573
				$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3574
			}
3575
			mapi_message_modifyrecipients($message, MODRECIP_MODIFY, [$recipient]);
0 ignored issues
show
The function mapi_message_modifyrecipients 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

3575
			/** @scrutinizer ignore-call */ 
3576
   mapi_message_modifyrecipients($message, MODRECIP_MODIFY, [$recipient]);
Loading history...
3576
		}
3577
	}
3578
3579
	/**
3580
	 * Function returns correspondent calendar item attached with the meeting request/response/cancellation.
3581
	 * This will only check for actual MAPIMessages in calendar folder, so if a meeting request is
3582
	 * for exception then this function will return recurring series for that meeting request
3583
	 * after that you need to use getExceptionItem function to get exception item that will be
3584
	 * fetched from the attachment table of recurring series MAPIMessage.
3585
	 *
3586
	 * @param bool $open boolean to indicate the function should return entryid or MAPIMessage. Defaults to true.
3587
	 *
3588
	 * @return bool|resource resource of calendar item
3589
	 */
3590
	public function getCorrespondentCalendarItem($open = true) {
3591
		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_ENTRYID]);
0 ignored issues
show
The function mapi_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

3591
		$props = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_ENTRYID]);
Loading history...
3592
3593
		if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS]) && !$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS]) && !$this->isMeetingCancellation($props[PR_MESSAGE_CLASS])) {
3594
			// can work only with meeting requests/responses/cancellations
3595
			return false;
3596
		}
3597
3598
		// there is no goid - no items can be found - aborting
3599
		if (empty($props[$this->proptags['goid']])) {
3600
			return false;
3601
		}
3602
		$globalId = $props[$this->proptags['goid']];
3603
3604
		// If Delegate is processing Meeting Request/Response for Delegator then retrieve Delegator's store and calendar.
3605
		if (isset($props[PR_RCVD_REPRESENTING_ENTRYID])) {
3606
			$delegatorStore = $this->getDelegatorStore($props[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
3607
3608
			$store = $delegatorStore['store'];
3609
			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
3610
		}
3611
		else {
3612
			$store = $this->store;
3613
			$calFolder = $this->openDefaultCalendar();
3614
		}
3615
3616
		$basedate = $this->getBasedateFromGlobalID($globalId);
3617
3618
		/**
3619
		 * First search for any appointments which correspond to the $globalId,
3620
		 * this can be the entire series (if the Meeting Request refers to the
3621
		 * entire series), or an particular Occurrence (if the meeting Request
3622
		 * contains a basedate).
3623
		 *
3624
		 * If we cannot find a corresponding item, and the $globalId contains
3625
		 * a $basedate, it might imply that a new exception will have to be
3626
		 * created for a series which is present in the calendar, we can look
3627
		 * that one up by searching for the $cleanGlobalId.
3628
		 */
3629
		$entryids = $this->findCalendarItems($globalId, $calFolder);
3630
		if ($basedate !== false && empty($entryids)) {
3631
			// only search if a goid2 is available
3632
			if (!empty($props[$this->proptags['goid2']])) {
3633
				$cleanGlobalId = $props[$this->proptags['goid2']];
3634
				$entryids = $this->findCalendarItems($cleanGlobalId, $calFolder, true);
3635
			}
3636
		}
3637
3638
		// there should be only one item returned
3639
		if (!empty($entryids) && count($entryids) === 1) {
3640
			// return only entryid
3641
			if ($open === false) {
3642
				return $entryids[0];
3643
			}
3644
3645
			// open calendar item and return it
3646
			if ($store) {
3647
				return mapi_msgstore_openentry($store, $entryids[0]);
0 ignored issues
show
The function mapi_msgstore_openentry 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

3647
				return /** @scrutinizer ignore-call */ mapi_msgstore_openentry($store, $entryids[0]);
Loading history...
3648
			}
3649
		}
3650
3651
		// no items found in calendar
3652
		return false;
3653
	}
3654
3655
	/**
3656
	 * Function returns exception item based on the basedate passed.
3657
	 *
3658
	 * @param mixed $recurringMessage Resource of Recurring meeting from calendar
3659
	 * @param mixed $basedate         basedate of exception that needs to be returned
3660
	 * @param mixed $store            store that contains the recurring calendar item
3661
	 *
3662
	 * @return entryid or MAPIMessage resource of exception item
3663
	 */
3664
	public function getExceptionItem($recurringMessage, $basedate, $store = false) {
3665
		$occurItem = false;
3666
3667
		$props = mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID, $this->proptags['recurring']]);
0 ignored issues
show
The function mapi_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

3667
		$props = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID, $this->proptags['recurring']]);
Loading history...
3668
3669
		// check if the passed item is recurring series
3670
		if (isset($props[$this->proptags['recurring']]) && $props[$this->proptags['recurring']] !== false) {
3671
			return false;
3672
		}
3673
3674
		if ($store === false) {
3675
			// If Delegate is processing Meeting Request/Response for Delegator then retrieve Delegator's store and calendar.
3676
			if (isset($props[PR_RCVD_REPRESENTING_ENTRYID])) {
3677
				$delegatorStore = $this->getDelegatorStore($props[PR_RCVD_REPRESENTING_ENTRYID]);
3678
				$store = $delegatorStore['store'];
3679
			}
3680
			else {
3681
				$store = $this->store;
3682
			}
3683
		}
3684
3685
		$recurr = new Recurrence($store, $recurringMessage);
3686
		$attach = $recurr->getExceptionAttachment($basedate);
3687
		if ($attach) {
3688
			$occurItem = mapi_attach_openobj($attach);
0 ignored issues
show
The function mapi_attach_openobj 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

3688
			$occurItem = /** @scrutinizer ignore-call */ mapi_attach_openobj($attach);
Loading history...
3689
		}
3690
3691
		return $occurItem;
3692
	}
3693
3694
	/**
3695
	 * Function which checks whether received meeting request is either conflicting with other appointments or not.
3696
	 *
3697
	 * @param false|resource $message
3698
	 * @param false|resource $userStore
3699
	 * @param mixed          $calFolder calendar folder for conflict checking
3700
	 *
3701
	 * @return bool|int
3702
	 *
3703
	 * @psalm-return bool|int<1, max>
3704
	 */
3705
	public function isMeetingConflicting($message = false, $userStore = false, $calFolder = false) {
3706
		$returnValue = false;
3707
		$noOfInstances = 0;
3708
3709
		if ($message === false) {
3710
			$message = $this->message;
3711
		}
3712
3713
		$messageProps = mapi_getprops(
0 ignored issues
show
The function mapi_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

3713
		$messageProps = /** @scrutinizer ignore-call */ mapi_getprops(
Loading history...
3714
			$message,
3715
			[
3716
				PR_MESSAGE_CLASS,
3717
				$this->proptags['goid'],
3718
				$this->proptags['goid2'],
3719
				$this->proptags['startdate'],
3720
				$this->proptags['duedate'],
3721
				$this->proptags['recurring'],
3722
				$this->proptags['clipstart'],
3723
				$this->proptags['clipend'],
3724
				PR_RCVD_REPRESENTING_ENTRYID,
3725
				$this->proptags['basedate'],
3726
				PR_RCVD_REPRESENTING_NAME,
3727
			]
3728
		);
3729
3730
		if ($userStore === false) {
3731
			$userStore = $this->store;
3732
3733
			// check if delegate is processing the response
3734
			if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) {
3735
				$delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
3736
3737
				$userStore = $delegatorStore['store'];
3738
				$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
3739
			}
3740
		}
3741
3742
		if ($calFolder === false) {
3743
			$calFolder = $this->openDefaultCalendar($userStore);
3744
		}
3745
3746
		if ($calFolder) {
3747
			// Meeting request is recurring, so get all occurrence and check for each occurrence whether it conflicts with other appointments in Calendar.
3748
			if (isset($messageProps[$this->proptags['recurring']]) && $messageProps[$this->proptags['recurring']] === true) {
3749
				// Apply recurrence class and retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date')
3750
				$recurr = new Recurrence($userStore, $message);
3751
				$items = $recurr->getItems($messageProps[$this->proptags['clipstart']], $messageProps[$this->proptags['clipend']] * (24 * 24 * 60), 30);
3752
3753
				foreach ($items as $item) {
3754
					// Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
3755
					$calendarItems = $recurr->getCalendarItems($userStore, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus']]);
3756
3757
					foreach ($calendarItems as $calendarItem) {
3758
						if ($calendarItem[$this->proptags['busystatus']] !== fbFree) {
3759
							/*
3760
							 * Only meeting requests have globalID, normal appointments do not have globalID
3761
							 * so if any normal appointment if found then it is assumed to be conflict.
3762
							 */
3763
							if (isset($calendarItem[$this->proptags['goid']])) {
3764
								if ($calendarItem[$this->proptags['goid']] !== $messageProps[$this->proptags['goid']]) {
3765
									++$noOfInstances;
3766
									break;
3767
								}
3768
							}
3769
							else {
3770
								++$noOfInstances;
3771
								break;
3772
							}
3773
						}
3774
					}
3775
				}
3776
3777
				if ($noOfInstances > 0) {
3778
					$returnValue = $noOfInstances;
3779
				}
3780
			}
3781
			else {
3782
				// Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
3783
				$items = getCalendarItems($userStore, $calFolder, $messageProps[$this->proptags['startdate']], $messageProps[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus']]);
3784
3785
				if (isset($messageProps[$this->proptags['basedate']]) && !empty($messageProps[$this->proptags['basedate']])) {
3786
					$basedate = $messageProps[$this->proptags['basedate']];
3787
					// Get the goid2 from recurring MR which further used to
3788
					// check the resource conflicts item.
3789
					$recurrItemProps = mapi_getprops($this->message, [$this->proptags['goid2']]);
3790
					$messageProps[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid2']], $basedate);
3791
					$messageProps[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']];
3792
				}
3793
3794
				foreach ($items as $item) {
3795
					if ($item[$this->proptags['busystatus']] !== fbFree) {
3796
						if (isset($item[$this->proptags['goid']])) {
3797
							if (($item[$this->proptags['goid']] !== $messageProps[$this->proptags['goid']]) &&
3798
								($item[$this->proptags['goid']] !== $messageProps[$this->proptags['goid2']])) {
3799
								$returnValue = true;
3800
								break;
3801
							}
3802
						}
3803
						else {
3804
							$returnValue = true;
3805
							break;
3806
						}
3807
					}
3808
				}
3809
			}
3810
		}
3811
3812
		return $returnValue;
3813
	}
3814
3815
	/**
3816
	 * Function which adds organizer to recipient list which is passed.
3817
	 * This function also checks if it has organizer.
3818
	 *
3819
	 * @param array $messageProps message properties
3820
	 * @param array $recipients   recipients list of message
3821
	 */
3822
	public function addDelegator($messageProps, &$recipients): void {
3823
		$hasDelegator = false;
3824
		// Check if meeting already has an organizer.
3825
		foreach ($recipients as $key => $recipient) {
3826
			if (isset($messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) && $recipient[PR_EMAIL_ADDRESS] == $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) {
3827
				$hasDelegator = true;
3828
			}
3829
		}
3830
3831
		if (!$hasDelegator) {
3832
			// Create delegator.
3833
			$delegator = [];
3834
			$delegator[PR_ENTRYID] = $messageProps[PR_RCVD_REPRESENTING_ENTRYID];
3835
			$delegator[PR_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
3836
			$delegator[PR_EMAIL_ADDRESS] = $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS];
3837
			$delegator[PR_RECIPIENT_TYPE] = MAPI_TO;
3838
			$delegator[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
3839
			$delegator[PR_ADDRTYPE] = empty($messageProps[PR_RCVD_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_RCVD_REPRESENTING_ADDRTYPE];
3840
			$delegator[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3841
			$delegator[PR_RECIPIENT_FLAGS] = recipSendable;
3842
			$delegator[PR_SEARCH_KEY] = $messageProps[PR_RCVD_REPRESENTING_SEARCH_KEY];
3843
3844
			// Add organizer to recipients list.
3845
			array_unshift($recipients, $delegator);
3846
		}
3847
	}
3848
3849
	/**
3850
	 * Function will return delegator's store and calendar folder for processing meetings.
3851
	 *
3852
	 * @param string $receivedRepresentingEntryId entryid of the delegator user
3853
	 * @param array  $foldersToOpen               contains list of folder types that should be returned in result
3854
	 *
3855
	 * @return resource[] contains store of the delegator and resource of folders if $foldersToOpen is not empty
3856
	 *
3857
	 * @psalm-return array<resource>
3858
	 */
3859
	public function getDelegatorStore($receivedRepresentingEntryId, $foldersToOpen = []): array {
3860
		$returnData = [];
3861
3862
		$delegatorStore = $this->openCustomUserStore($receivedRepresentingEntryId);
3863
		$returnData['store'] = $delegatorStore;
3864
3865
		if (!empty($foldersToOpen)) {
3866
			for ($index = 0, $len = count($foldersToOpen); $index < $len; ++$index) {
3867
				$folderType = $foldersToOpen[$index];
3868
3869
				// first try with default folders
3870
				$folder = $this->openDefaultFolder($folderType, $delegatorStore);
3871
3872
				// if folder not found then try with base folders
3873
				if ($folder === false) {
3874
					$folder = $this->openBaseFolder($folderType, $delegatorStore);
3875
				}
3876
3877
				if ($folder === false) {
3878
					// we are still not able to get the folder so give up
3879
					continue;
3880
				}
3881
3882
				$returnData[$folderType] = $folder;
3883
			}
3884
		}
3885
3886
		return $returnData;
3887
	}
3888
3889
	/**
3890
	 * Function returns extra info about meeting timing along with message body
3891
	 * which will be included in body while sending meeting request/response.
3892
	 *
3893
	 * @return false|string $meetingTimeInfo info about meeting timing along with message body
3894
	 */
3895
	public function getMeetingTimeInfo() {
3896
		return $this->meetingTimeInfo;
3897
	}
3898
3899
	/**
3900
	 * Function sets extra info about meeting timing along with message body
3901
	 * which will be included in body while sending meeting request/response.
3902
	 *
3903
	 * @param string $meetingTimeInfo info about meeting timing along with message body
3904
	 */
3905
	public function setMeetingTimeInfo($meetingTimeInfo): void {
3906
		$this->meetingTimeInfo = $meetingTimeInfo;
3907
	}
3908
3909
	/**
3910
	 * Helper function which is use to get local categories of all occurrence.
3911
	 *
3912
	 * @param mixed $calendarItem meeting request item
3913
	 * @param mixed $store        store containing calendar folder
3914
	 * @param mixed $calFolder    calendar folder
3915
	 *
3916
	 * @return array $localCategories which contain array of basedate along with categories
3917
	 */
3918
	public function getLocalCategories($calendarItem, $store, $calFolder) {
3919
		$calendarItemProps = mapi_getprops($calendarItem);
0 ignored issues
show
The function mapi_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

3919
		$calendarItemProps = /** @scrutinizer ignore-call */ mapi_getprops($calendarItem);
Loading history...
3920
		$recurrence = new Recurrence($store, $calendarItem);
3921
3922
		// Retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date')
3923
		$items = $recurrence->getItems($calendarItemProps[$this->proptags['clipstart']], $calendarItemProps[$this->proptags['clipend']] * (24 * 24 * 60), 30);
3924
		$localCategories = [];
3925
3926
		foreach ($items as $item) {
3927
			$recurrenceItems = $recurrence->getCalendarItems($store, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus'], $this->proptags['categories']]);
3928
			foreach ($recurrenceItems as $recurrenceItem) {
3929
				// Check if occurrence is exception then get the local categories of that occurrence.
3930
				if (isset($recurrenceItem[$this->proptags['goid']]) && $recurrenceItem[$this->proptags['goid']] == $calendarItemProps[$this->proptags['goid']]) {
3931
					$exceptionAttach = $recurrence->getExceptionAttachment($recurrenceItem['basedate']);
3932
3933
					if ($exceptionAttach) {
3934
						$exception = mapi_attach_openobj($exceptionAttach, 0);
0 ignored issues
show
The function mapi_attach_openobj 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

3934
						$exception = /** @scrutinizer ignore-call */ mapi_attach_openobj($exceptionAttach, 0);
Loading history...
3935
						$exceptionProps = mapi_getprops($exception, [$this->proptags['categories']]);
3936
						if (isset($exceptionProps[$this->proptags['categories']])) {
3937
							$localCategories[$recurrenceItem['basedate']] = $exceptionProps[$this->proptags['categories']];
3938
						}
3939
					}
3940
				}
3941
			}
3942
		}
3943
3944
		return $localCategories;
3945
	}
3946
3947
	/**
3948
	 * Helper function which is use to apply local categories on respective occurrences.
3949
	 *
3950
	 * @param mixed $calendarItem    meeting request item
3951
	 * @param mixed $store           store containing calendar folder
3952
	 * @param array $localCategories array contains basedate and array of categories
3953
	 */
3954
	public function applyLocalCategories($calendarItem, $store, $localCategories): void {
3955
		$calendarItemProps = mapi_getprops($calendarItem, [PR_PARENT_ENTRYID, PR_ENTRYID]);
0 ignored issues
show
The function mapi_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

3955
		$calendarItemProps = /** @scrutinizer ignore-call */ mapi_getprops($calendarItem, [PR_PARENT_ENTRYID, PR_ENTRYID]);
Loading history...
3956
		$message = mapi_msgstore_openentry($store, $calendarItemProps[PR_ENTRYID]);
0 ignored issues
show
The function mapi_msgstore_openentry 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

3956
		$message = /** @scrutinizer ignore-call */ mapi_msgstore_openentry($store, $calendarItemProps[PR_ENTRYID]);
Loading history...
3957
		$recurrence = new Recurrence($store, $message);
3958
3959
		// Check for all occurrence if it is exception then modify the exception by setting up categories,
3960
		// Otherwise create new exception with categories.
3961
		foreach ($localCategories as $key => $value) {
3962
			if ($recurrence->isException($key)) {
3963
				$recurrence->modifyException([$this->proptags['categories'] => $value], $key);
3964
			}
3965
			else {
3966
				$recurrence->createException([$this->proptags['categories'] => $value], $key, false);
3967
			}
3968
			mapi_savechanges($message);
0 ignored issues
show
The function mapi_savechanges 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

3968
			/** @scrutinizer ignore-call */ 
3969
   mapi_savechanges($message);
Loading history...
3969
		}
3970
	}
3971
}
3972