Passed
Push — master ( bc722f...03cdb2 )
by
unknown
03:21 queued 12s
created

Meetingrequest::getFreeBusyInfo()   B

Complexity

Conditions 7
Paths 3

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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

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

347
			$this->processResponse($userStore, /** @scrutinizer ignore-type */ $calendarItem, $basedate, $messageprops);
Loading history...
348
		}
349
	}
350
351
	/**
352
	 * Process every incoming MeetingRequest response.This updates the appointment
353
	 * in your calendar to show whether the user has accepted or declined.
354
	 *
355
	 * @param resource $store        contains the userStore in which the meeting is created
356
	 * @param resource $calendarItem resource of the calendar item for which this response has arrived
357
	 * @param mixed    $basedate     if present the create an exception
358
	 * @param array    $messageprops contains message properties
359
	 */
360
	public function processResponse($store, $calendarItem, $basedate, $messageprops) {
361
		$senderentryid = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
362
		$messageclass = $messageprops[PR_MESSAGE_CLASS];
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
363
		$deliverytime = $messageprops[PR_MESSAGE_DELIVERY_TIME];
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_DELIVERY_TIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
364
365
		// Open the calendar item, find the sender in the recipient table and update all the recipients of the calendar item that match
366
		// the email address of the response.
367
		$calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['recurring'], PR_STORE_ENTRYID, PR_PARENT_ENTRYID, PR_ENTRYID, $this->proptags['updatecounter']]);
0 ignored issues
show
Bug introduced by
The constant PR_STORE_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_PARENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
368
369
		// check if meeting response is already processed
370
		if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
0 ignored issues
show
Bug introduced by
The constant PR_PROCESSED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
371
			// meeting is already processed
372
			return;
373
		}
374
		mapi_setprops($this->message, [PR_PROCESSED => true]);
375
		mapi_savechanges($this->message);
376
377
		// if meeting is updated in organizer's calendar then we don't need to process
378
		// old response
379
		if ($this->isMeetingUpdated($basedate)) {
380
			return;
381
		}
382
383
		// If basedate is found, then create/modify exception msg and do processing
384
		if ($basedate && isset($calendarItemProps[$this->proptags['recurring']]) && $calendarItemProps[$this->proptags['recurring']] === true) {
385
			$recurr = new Recurrence($store, $calendarItem);
386
387
			// Copy properties from meeting request
388
			$exception_props = mapi_getprops($this->message, [
389
				PR_OWNER_APPT_ID,
0 ignored issues
show
Bug introduced by
The constant PR_OWNER_APPT_ID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
390
				$this->proptags['proposed_start_whole'],
391
				$this->proptags['proposed_end_whole'],
392
				$this->proptags['proposed_duration'],
393
				$this->proptags['counter_proposal'],
394
			]);
395
396
			// Create/modify exception
397
			if ($recurr->isException($basedate)) {
398
				$recurr->modifyException($exception_props, $basedate);
399
			}
400
			else {
401
				// When we are creating an exception we need copy recipients from main recurring item
402
				$recipTable = mapi_message_getrecipienttable($calendarItem);
403
				$recips = mapi_table_queryallrows($recipTable, $this->recipprops);
404
405
				// Retrieve actual start/due dates from calendar item.
406
				$exception_props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
407
				$exception_props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
408
409
				$recurr->createException($exception_props, $basedate, false, $recips);
410
			}
411
412
			mapi_savechanges($calendarItem);
413
414
			$attach = $recurr->getExceptionAttachment($basedate);
415
			if ($attach) {
416
				$recurringItem = $calendarItem;
417
				$calendarItem = mapi_attach_openobj($attach, MAPI_MODIFY);
418
			}
419
			else {
420
				return false;
421
			}
422
		}
423
424
		// Get the recipients of the calendar item
425
		$reciptable = mapi_message_getrecipienttable($calendarItem);
426
		$recipients = mapi_table_queryallrows($reciptable, $this->recipprops);
427
428
		// FIXME we should look at the updatecounter property and compare it
429
		// to the counter in the recipient to see if this update is actually
430
		// newer than the status in the calendar item
431
		$found = false;
432
433
		$totalrecips = 0;
434
		$acceptedrecips = 0;
435
		foreach ($recipients as $recipient) {
436
			++$totalrecips;
437
			if (isset($recipient[PR_ENTRYID]) && $this->compareABEntryIDs($recipient[PR_ENTRYID], $senderentryid)) {
438
				$found = true;
439
440
				/*
441
				 * If value of attendee_critical_change on meeting response mail is less than PR_RECIPIENT_TRACKSTATUS_TIME
442
				 * on the corresponding recipientRow of meeting then we ignore this response mail.
443
				 */
444
				if (isset($recipient[PR_RECIPIENT_TRACKSTATUS_TIME]) && ($messageprops[$this->proptags['attendee_critical_change']] < $recipient[PR_RECIPIENT_TRACKSTATUS_TIME])) {
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TRACKSTATUS_TIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
445
					continue;
446
				}
447
448
				// The email address matches, update the row
449
				$recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TRACKSTATUS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
450
				if (isset($messageprops[$this->proptags['attendee_critical_change']])) {
451
					$recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $messageprops[$this->proptags['attendee_critical_change']];
452
				}
453
454
				// If this is a counter proposal, set the proposal properties in the recipient row
455
				if (isset($messageprops[$this->proptags['counter_proposal']]) && $messageprops[$this->proptags['counter_proposal']]) {
456
					$recipient[PR_RECIPIENT_PROPOSEDSTARTTIME] = $messageprops[$this->proptags['proposed_start_whole']];
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_PROPOSEDSTARTTIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
457
					$recipient[PR_RECIPIENT_PROPOSEDENDTIME] = $messageprops[$this->proptags['proposed_end_whole']];
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_PROPOSEDENDTIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
458
					$recipient[PR_RECIPIENT_PROPOSED] = $messageprops[$this->proptags['counter_proposal']];
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_PROPOSED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
459
				}
460
461
				// Update the recipient information
462
				mapi_message_modifyrecipients($calendarItem, MODRECIP_REMOVE, [$recipient]);
463
				mapi_message_modifyrecipients($calendarItem, MODRECIP_ADD, [$recipient]);
464
			}
465
			if (isset($recipient[PR_RECIPIENT_TRACKSTATUS]) && $recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted) {
466
				++$acceptedrecips;
467
			}
468
		}
469
470
		// If the recipient was not found in the original calendar item,
471
		// then add the recpient as a new optional recipient
472
		if (!$found) {
473
			$recipient = [];
474
			$recipient[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
475
			$recipient[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
476
			$recipient[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
477
			$recipient[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
0 ignored issues
show
Bug introduced by
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENT_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
478
			$recipient[PR_RECIPIENT_TYPE] = MAPI_CC;
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
479
			$recipient[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
480
			$recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
481
			$recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $deliverytime;
482
483
			// If this is a counter proposal, set the proposal properties in the recipient row
484
			if (isset($messageprops[$this->proptags['counter_proposal']])) {
485
				$recipient[PR_RECIPIENT_PROPOSEDSTARTTIME] = $messageprops[$this->proptags['proposed_start_whole']];
486
				$recipient[PR_RECIPIENT_PROPOSEDENDTIME] = $messageprops[$this->proptags['proposed_end_whole']];
487
				$recipient[PR_RECIPIENT_PROPOSED] = $messageprops[$this->proptags['counter_proposal']];
488
			}
489
490
			mapi_message_modifyrecipients($calendarItem, MODRECIP_ADD, [$recipient]);
491
			++$totalrecips;
492
			if ($recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted) {
493
				++$acceptedrecips;
494
			}
495
		}
496
497
		// TODO: Update counter proposal number property on message
498
		/*
499
		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.
500
		*/
501
		// If this is a counter proposal, set the counter proposal indicator boolean
502
		if (isset($messageprops[$this->proptags['counter_proposal']])) {
503
			$props = [];
504
			if ($messageprops[$this->proptags['counter_proposal']]) {
505
				$props[$this->proptags['counter_proposal']] = true;
506
			}
507
			else {
508
				$props[$this->proptags['counter_proposal']] = false;
509
			}
510
511
			mapi_setprops($calendarItem, $props);
512
		}
513
514
		mapi_savechanges($calendarItem);
515
		if (isset($attach)) {
516
			mapi_savechanges($attach);
517
			mapi_savechanges($recurringItem);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $recurringItem does not seem to be defined for all execution paths leading up to this point.
Loading history...
518
		}
519
	}
520
521
	/**
522
	 * Process an incoming meeting request cancellation. This updates the
523
	 * appointment in your calendar to show that the meeting has been cancelled.
524
	 */
525
	public function processMeetingCancellation() {
526
		if (!$this->isMeetingCancellation()) {
527
			return;
528
		}
529
530
		if ($this->isLocalOrganiser()) {
531
			return;
532
		}
533
534
		if (!$this->isInCalendar()) {
535
			return;
536
		}
537
538
		$listProperties = $this->proptags;
539
		$listProperties['subject'] = PR_SUBJECT;
0 ignored issues
show
Bug introduced by
The constant PR_SUBJECT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
540
		$listProperties['sent_representing_name'] = PR_SENT_REPRESENTING_NAME;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
541
		$listProperties['sent_representing_address_type'] = PR_SENT_REPRESENTING_ADDRTYPE;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
542
		$listProperties['sent_representing_email_address'] = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
543
		$listProperties['sent_representing_entryid'] = PR_SENT_REPRESENTING_ENTRYID;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
544
		$listProperties['sent_representing_search_key'] = PR_SENT_REPRESENTING_SEARCH_KEY;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
545
		$listProperties['rcvd_representing_name'] = PR_RCVD_REPRESENTING_NAME;
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
546
		$listProperties['rcvd_representing_address_type'] = PR_RCVD_REPRESENTING_ADDRTYPE;
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
547
		$listProperties['rcvd_representing_email_address'] = PR_RCVD_REPRESENTING_EMAIL_ADDRESS;
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
548
		$listProperties['rcvd_representing_entryid'] = PR_RCVD_REPRESENTING_ENTRYID;
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
549
		$listProperties['rcvd_representing_search_key'] = PR_RCVD_REPRESENTING_SEARCH_KEY;
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
550
		$messageProps = mapi_getprops($this->message, $listProperties);
551
552
		$goid = $messageProps[$this->proptags['goid']];	// GlobalID (0x3)
553
		if (!isset($goid)) {
554
			return;
555
		}
556
557
		// get delegator store, if delegate is processing this cancellation
558
		if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) {
559
			$delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_APPOINTMENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
560
561
			$store = $delegatorStore['store'];
562
		}
563
		else {
564
			$store = $this->store;
565
		}
566
567
		// check for calendar access
568
		if ($this->checkCalendarWriteAccess($store) !== true) {
569
			// Throw an exception that we don't have write permissions on calendar folder,
570
			// allow caller to fill the error message
571
			throw new MAPIException(null, MAPI_E_NO_ACCESS);
0 ignored issues
show
Bug introduced by
The constant MAPI_E_NO_ACCESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
572
		}
573
574
		$calendarItem = $this->getCorrespondentCalendarItem(true);
575
		$basedate = $this->getBasedateFromGlobalID($goid);
576
577
		if ($calendarItem !== false) {
578
			// if basedate is provided and we could not find the item then it could be that we are processing
579
			// an exception so get the exception and process it
580
			if ($basedate) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $basedate of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
581
				$calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['recurring']]);
582
				if ($calendarItemProps[$this->proptags['recurring']] === true) {
583
					$recurr = new Recurrence($store, $calendarItem);
0 ignored issues
show
Bug introduced by
It seems like $calendarItem can also be of type true; however, parameter $message of Recurrence::__construct() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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

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

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

1227
			$this->createResponse(olResponseDeclined, [], $body, $store, /** @scrutinizer ignore-type */ $basedate, $calFolder);
Loading history...
1228
		}
1229
1230
		if ($basedate) {
1231
			// use CleanGlobalObjid (0x23)
1232
			$calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
1233
1234
			if (is_array($calendaritems)) {
0 ignored issues
show
introduced by
The condition is_array($calendaritems) is always true.
Loading history...
1235
				foreach ($calendaritems as $entryid) {
1236
					// Open each calendar item and set the properties of the cancellation object
1237
					$calendaritem = mapi_msgstore_openentry($store, $entryid);
1238
1239
					// Recurring item is found, now delete exception
1240
					if ($calendaritem) {
1241
						$this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store);
1242
						$result = true;
1243
					}
1244
				}
1245
			}
1246
1247
			if ($this->isMeetingRequest()) {
1248
				$calendaritem = false;
1249
			}
1250
		}
1251
1252
		if (!$calendaritem) {
1253
			$calendar = $this->openDefaultCalendar($store);
1254
1255
			if (!empty($entryids)) {
1256
				mapi_folder_deletemessages($calendar, $entryids);
1257
			}
1258
1259
			// All we have to do to decline, is to move the item to the waste basket
1260
			$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
1261
			$sourcefolder = $this->openParentFolder();
1262
1263
			$messageprops = mapi_getprops($this->message, [PR_ENTRYID]);
1264
1265
			// Release the message
1266
			$this->message = null;
1267
1268
			// Move the message to the waste basket
1269
			mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1270
1271
			$result = true;
1272
		}
1273
1274
		return $result;
1275
	}
1276
1277
	/**
1278
	 * Removes a meeting request from the calendar when the user presses the
1279
	 * 'remove from calendar' button in response to a meeting cancellation.
1280
	 *
1281
	 * @param mixed $basedate if specified contains starttime of day of an occurrence
1282
	 */
1283
	public function doRemoveFromCalendar($basedate) {
1284
		if ($this->isLocalOrganiser()) {
1285
			return false;
1286
		}
1287
1288
		$messageprops = mapi_getprops($this->message, [PR_ENTRYID, $this->proptags['goid'], PR_RCVD_REPRESENTING_ENTRYID, PR_MESSAGE_CLASS]);
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_RCVD_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1289
1290
		$goid = $messageprops[$this->proptags['goid']];
1291
1292
		if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) {
1293
			$delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_APPOINTMENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1294
1295
			$store = $delegatorStore['store'];
1296
			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
1297
		}
1298
		else {
1299
			$store = $this->store;
1300
			$calFolder = $this->openDefaultCalendar();
1301
		}
1302
1303
		// check for calendar access before deleting the calendar item
1304
		if ($this->checkCalendarWriteAccess($store) !== true) {
1305
			// Throw an exception that we don't have write permissions on calendar folder,
1306
			// allow caller to fill the error message
1307
			throw new MAPIException(null, MAPI_E_NO_ACCESS);
0 ignored issues
show
Bug introduced by
The constant MAPI_E_NO_ACCESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1308
		}
1309
1310
		$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
1311
		// get the source folder of the meeting message
1312
		$sourcefolder = $this->openParentFolder();
1313
1314
		// Check if the message is a meeting request in the inbox or a calendaritem by checking the message class
1315
		if ($this->isMeetingCancellation($messageprops[PR_MESSAGE_CLASS])) {
1316
			// get the basedate to check for exception
1317
			$basedate = $this->getBasedateFromGlobalID($goid);
1318
1319
			$calendarItem = $this->getCorrespondentCalendarItem(true);
1320
1321
			if ($calendarItem !== false) {
1322
				// basedate is provided so open exception
1323
				if ($basedate) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $basedate of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1324
					$exception = $this->getExceptionItem($calendarItem, $basedate);
1325
1326
					if ($exception !== false) {
0 ignored issues
show
introduced by
The condition $exception !== false is always true.
Loading history...
1327
						// exception found, remove it from calendar
1328
						$this->doRemoveExceptionFromCalendar($basedate, $calendarItem, $store);
0 ignored issues
show
Bug introduced by
It seems like $calendarItem can also be of type true; however, parameter $message of Meetingrequest::doRemoveExceptionFromCalendar() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

1328
						$this->doRemoveExceptionFromCalendar($basedate, /** @scrutinizer ignore-type */ $calendarItem, $store);
Loading history...
1329
					}
1330
				}
1331
				else {
1332
					// remove normal / recurring series from calendar
1333
					$entryids = mapi_getprops($calendarItem, [PR_ENTRYID]);
1334
1335
					$entryids = [$entryids[PR_ENTRYID]];
1336
1337
					mapi_folder_copymessages($calFolder, $entryids, $wastebasket, MESSAGE_MOVE);
1338
				}
1339
			}
1340
1341
			// Release the message, because we are going to move it to wastebasket
1342
			$this->message = null;
1343
1344
			// Move the cancellation mail to wastebasket
1345
			mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1346
		}
1347
		else {
1348
			// Here only properties are set on calendaritem, because user is responding from calendar.
1349
			if ($basedate) {
1350
				// remove the occurrence
1351
				$this->doRemoveExceptionFromCalendar($basedate, $this->message, $store);
1352
			}
1353
			else {
1354
				// remove normal/recurring meeting item.
1355
				// Move the message to the waste basket
1356
				mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1357
			}
1358
		}
1359
	}
1360
1361
	/**
1362
	 * Function can be used to cancel any existing meeting and send cancellation mails to attendees.
1363
	 * Should only be called from meeting object from calendar.
1364
	 *
1365
	 * @param mixed $basedate (optional) basedate of occurrence which should be cancelled
1366
	 * @FIXME cancellation mail is also sent to attendee which has declined the meeting
1367
	 * @FIXME don't send canellation mail when cancelling meeting from past
1368
	 */
1369
	public function doCancelInvitation($basedate = false) {
1370
		if (!$this->isLocalOrganiser()) {
1371
			return;
1372
		}
1373
1374
		// check write access for delegate
1375
		if ($this->checkCalendarWriteAccess($this->store) !== true) {
1376
			// Throw an exception that we don't have write permissions on calendar folder,
1377
			// error message will be filled by module
1378
			throw new MAPIException(null, MAPI_E_NO_ACCESS);
0 ignored issues
show
Bug introduced by
The constant MAPI_E_NO_ACCESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1379
		}
1380
1381
		$messageProps = mapi_getprops($this->message, [PR_ENTRYID, $this->proptags['recurring']]);
1382
1383
		if (isset($messageProps[$this->proptags['recurring']]) && $messageProps[$this->proptags['recurring']] === true) {
1384
			// cancellation of recurring series or one occurrence
1385
			$recurrence = new Recurrence($this->store, $this->message);
1386
1387
			// if basedate is specified then we are cancelling only one occurrence, so create exception for that occurrence
1388
			if ($basedate) {
1389
				$recurrence->createException([], $basedate, true);
1390
			}
1391
1392
			// update the meeting request
1393
			$this->updateMeetingRequest();
1394
1395
			// send cancellation mails
1396
			$this->sendMeetingRequest(true, dgettext('zarafa', 'Canceled') . ': ', $basedate);
1397
1398
			// save changes in the message
1399
			mapi_savechanges($this->message);
1400
		}
1401
		else {
1402
			// cancellation of normal meeting request
1403
			// Send the cancellation
1404
			$this->updateMeetingRequest();
1405
			$this->sendMeetingRequest(true, dgettext('zarafa', 'Canceled') . ': ');
1406
1407
			// save changes in the message
1408
			mapi_savechanges($this->message);
1409
		}
1410
1411
		// if basedate is specified then we have already created exception of it so nothing should be done now
1412
		// but when cancelling normal / recurring meeting request we need to remove meeting from calendar
1413
		if ($basedate === false) {
1414
			// get the wastebasket folder, for delegate this will give wastebasket of delegate
1415
			$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
1416
1417
			// get the source folder of the meeting message
1418
			$sourcefolder = $this->openParentFolder();
1419
1420
			// Move the message to the deleted items
1421
			mapi_folder_copymessages($sourcefolder, [$messageProps[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1422
		}
1423
	}
1424
1425
	/**
1426
	 * Convert epoch to MAPI FileTime, number of 100-nanosecond units since
1427
	 * the start of January 1, 1601.
1428
	 * https://msdn.microsoft.com/en-us/library/office/cc765906.aspx.
1429
	 *
1430
	 * @param int the current epoch
0 ignored issues
show
Bug introduced by
The type the was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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

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

1528
				$recurr = new Recurrence(/** @scrutinizer ignore-type */ $this->openDefaultStore(), $this->message);
Loading history...
1529
1530
				// First send meetingrequest for recurring item
1531
				$this->submitMeetingRequest($this->message, $cancel, $prefix, false, $recurr, false, $modifiedRecips, $deletedRecips);
1532
1533
				// Then send all meeting request for all exceptions
1534
				$exceptions = $recurr->getAllExceptions();
1535
				if ($exceptions) {
1536
					foreach ($exceptions as $exceptionBasedate) {
1537
						$attach = $recurr->getExceptionAttachment($exceptionBasedate);
1538
1539
						if ($attach) {
1540
							$occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
1541
							$this->submitMeetingRequest($occurrenceItem, $cancel, false, $exceptionBasedate, $recurr, false, $modifiedRecips, $deletedRecips);
1542
							mapi_savechanges($attach);
1543
						}
1544
					}
1545
				}
1546
			}
1547
		}
1548
		else {
1549
			// Basedate found, an exception is to be sent
1550
			if ($basedate) {
1551
				$recurr = new Recurrence($this->openDefaultStore(), $this->message);
1552
1553
				if ($cancel) {
1554
					// @TODO: remove occurrence from Resource's Calendar if resource was booked for whole series
1555
					$this->submitMeetingRequest($this->message, $cancel, $prefix, $basedate, $recurr, false);
1556
				}
1557
				else {
1558
					$attach = $recurr->getExceptionAttachment($basedate);
1559
1560
					if ($attach) {
1561
						$occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
1562
1563
						// Book resource for this occurrence
1564
						$resourceRecipData = $this->bookResources($occurrenceItem, $cancel, $prefix, $basedate);
0 ignored issues
show
Unused Code introduced by
The assignment to $resourceRecipData is dead and can be removed.
Loading history...
1565
1566
						if (!$this->errorSetResource) {
1567
							// Save all previous changes
1568
							mapi_savechanges($this->message);
1569
1570
							$this->submitMeetingRequest($occurrenceItem, $cancel, $prefix, $basedate, $recurr, true, $modifiedRecips, $deletedRecips);
1571
							mapi_savechanges($occurrenceItem);
1572
							mapi_savechanges($attach);
1573
						}
1574
					}
1575
				}
1576
			}
1577
			else {
1578
				// This is normal meeting
1579
				$resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
1580
1581
				if (!$this->errorSetResource) {
1582
					$this->submitMeetingRequest($this->message, $cancel, $prefix, false, false, false, $modifiedRecips, $deletedRecips);
1583
				}
1584
			}
1585
		}
1586
1587
		if (isset($this->errorSetResource) && $this->errorSetResource) {
1588
			return [
1589
				'error' => $this->errorSetResource,
1590
				'displayname' => $this->recipientDisplayname,
1591
			];
1592
		}
1593
1594
		return true;
1595
	}
1596
1597
	/**
1598
	 * Updates the message after an update has been performed (for example,
1599
	 * changing the time of the meeting). This must be called before re-sending
1600
	 * the meeting request. You can also call this function instead of 'setMeetingRequest()'
1601
	 * as it will automatically call setMeetingRequest on this object if it is the first
1602
	 * call to this function.
1603
	 *
1604
	 * @param mixed $basedate
1605
	 */
1606
	public function updateMeetingRequest($basedate = false) {
1607
		$messageprops = mapi_getprops($this->message, [$this->proptags['last_updatecounter'], $this->proptags['goid']]);
1608
1609
		if (!isset($messageprops[$this->proptags['goid']])) {
1610
			$this->setMeetingRequest($basedate);
1611
		}
1612
		else {
1613
			$counter = $messageprops[$this->proptags['last_updatecounter']] + 1;
1614
1615
			// increment value of last_updatecounter, last_updatecounter will be common for recurring series
1616
			// so even if you sending an exception only you need to update the last_updatecounter in the recurring series message
1617
			// this way we can make sure that every time we will be using a uniwue number for every operation
1618
			mapi_setprops($this->message, [$this->proptags['last_updatecounter'] => $counter]);
1619
		}
1620
	}
1621
1622
	/**
1623
	 * Returns TRUE if we are the organiser of the meeting. Can be used with any type of meeting object.
1624
	 */
1625
	public function isLocalOrganiser() {
1626
		$props = mapi_getprops($this->message, [$this->proptags['goid'], PR_MESSAGE_CLASS]);
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1627
1628
		if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS]) && !$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS]) && !$this->isMeetingCancellation($props[PR_MESSAGE_CLASS])) {
1629
			// we are checking with calendar item
1630
			$calendarItem = $this->message;
1631
		}
1632
		else {
1633
			// we are checking with meeting request / response / cancellation mail
1634
			// get calendar items
1635
			$calendarItem = $this->getCorrespondentCalendarItem(true);
1636
		}
1637
1638
		// even if we have received request/response for exception/occurrence then also
1639
		// we can check recurring series for organizer, no need to check with exception/occurrence
1640
1641
		if ($calendarItem !== false) {
1642
			$messageProps = mapi_getprops($calendarItem, [$this->proptags['responsestatus']]);
1643
1644
			if (isset($messageProps[$this->proptags['responsestatus']]) && $messageProps[$this->proptags['responsestatus']] === olResponseOrganized) {
1645
				return true;
1646
			}
1647
		}
1648
1649
		return false;
1650
	}
1651
1652
	/*
1653
	 * Support functions - INTERNAL ONLY
1654
	 ***************************************************************************************************
1655
	 */
1656
1657
	/**
1658
	 * Return the tracking status of a recipient based on the IPM class (passed).
1659
	 *
1660
	 * @param mixed $class
1661
	 */
1662
	public function getTrackStatus($class) {
1663
		$status = olRecipientTrackStatusNone;
1664
1665
		switch ($class) {
1666
			case 'IPM.Schedule.Meeting.Resp.Pos':
1667
				$status = olRecipientTrackStatusAccepted;
1668
				break;
1669
1670
			case 'IPM.Schedule.Meeting.Resp.Tent':
1671
				$status = olRecipientTrackStatusTentative;
1672
				break;
1673
1674
			case 'IPM.Schedule.Meeting.Resp.Neg':
1675
				$status = olRecipientTrackStatusDeclined;
1676
				break;
1677
		}
1678
1679
		return $status;
1680
	}
1681
1682
	/**
1683
	 * Function returns MAPIFolder resource of the folder that currently holds this meeting/meeting request
1684
	 * object.
1685
	 */
1686
	public function openParentFolder() {
1687
		$messageprops = mapi_getprops($this->message, [PR_PARENT_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_PARENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1688
1689
		return mapi_msgstore_openentry($this->store, $messageprops[PR_PARENT_ENTRYID]);
1690
	}
1691
1692
	/**
1693
	 * Function will return resource of the default calendar folder of store.
1694
	 *
1695
	 * @param mixed $store {optional} user store whose default calendar should be opened
1696
	 *
1697
	 * @return resource default calendar folder of store
1698
	 */
1699
	public function openDefaultCalendar($store = false) {
1700
		return $this->openDefaultFolder(PR_IPM_APPOINTMENT_ENTRYID, $store);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_APPOINTMENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1701
	}
1702
1703
	/**
1704
	 * Function will return resource of the default outbox folder of store.
1705
	 *
1706
	 * @param mixed $store {optional} user store whose default outbox should be opened
1707
	 *
1708
	 * @return resource default outbox folder of store
1709
	 */
1710
	public function openDefaultOutbox($store = false) {
1711
		return $this->openBaseFolder(PR_IPM_OUTBOX_ENTRYID, $store);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_OUTBOX_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1712
	}
1713
1714
	/**
1715
	 * Function will return resource of the default wastebasket folder of store.
1716
	 *
1717
	 * @param mixed $store {optional} user store whose default wastebasket should be opened
1718
	 *
1719
	 * @return resource default wastebasket folder of store
1720
	 */
1721
	public function openDefaultWastebasket($store = false) {
1722
		return $this->openBaseFolder(PR_IPM_WASTEBASKET_ENTRYID, $store);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_WASTEBASKET_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1723
	}
1724
1725
	/**
1726
	 * Function will return resource of the default calendar folder of store.
1727
	 *
1728
	 * @param mixed $store {optional} user store whose default calendar should be opened
1729
	 *
1730
	 * @return resource default calendar folder of store
1731
	 */
1732
	public function getDefaultWastebasketEntryID($store = false) {
1733
		return $this->getBaseEntryID(PR_IPM_WASTEBASKET_ENTRYID, $store);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getBaseEnt...BASKET_ENTRYID, $store) returns the type boolean|string which is incompatible with the documented return type resource.
Loading history...
Bug introduced by
The constant PR_IPM_WASTEBASKET_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1734
	}
1735
1736
	/**
1737
	 * Function will return resource of the default sent mail folder of store.
1738
	 *
1739
	 * @param mixed $store {optional} user store whose default sent mail should be opened
1740
	 *
1741
	 * @return resource default sent mail folder of store
1742
	 */
1743
	public function getDefaultSentmailEntryID($store = false) {
1744
		return $this->getBaseEntryID(PR_IPM_SENTMAIL_ENTRYID, $store);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_SENTMAIL_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug Best Practice introduced by
The expression return $this->getBaseEnt...NTMAIL_ENTRYID, $store) returns the type boolean|string which is incompatible with the documented return type resource.
Loading history...
1745
	}
1746
1747
	/**
1748
	 * Function will return entryid of any default folder of store. This method is useful when you want
1749
	 * to get entryid of folder which is stored as properties of inbox folder
1750
	 * (PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_JOURNAL_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_TASK_ENTRYID).
1751
	 *
1752
	 * @param int   $prop  proptag of the folder for which we want to get entryid
1753
	 * @param mixed $store {optional} user store from which we need to get entryid of default folder
1754
	 *
1755
	 * @return string|bool entryid of folder pointed by $prop
1756
	 */
1757
	public function getDefaultFolderEntryID($prop, $store = false) {
1758
		try {
1759
			$inbox = mapi_msgstore_getreceivefolder($store ? $store : $this->store);
1760
			$inboxprops = mapi_getprops($inbox, [$prop]);
1761
			if (isset($inboxprops[$prop])) {
1762
				return $inboxprops[$prop];
1763
			}
1764
		}
1765
		catch (MAPIException $e) {
1766
			// public store doesn't support this method
1767
			if ($e->getCode() == MAPI_E_NO_SUPPORT) {
0 ignored issues
show
Bug introduced by
The constant MAPI_E_NO_SUPPORT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1768
				// don't propagate this error to parent handlers, if store doesn't support it
1769
				$e->setHandled();
1770
			}
1771
		}
1772
1773
		return false;
1774
	}
1775
1776
	/**
1777
	 * Function will return resource of any default folder of store.
1778
	 *
1779
	 * @param int   $prop  proptag of the folder that we want to open
1780
	 * @param mixed $store {optional} user store from which we need to open default folder
1781
	 *
1782
	 * @return resource default folder of store
1783
	 */
1784
	public function openDefaultFolder($prop, $store = false) {
1785
		$folder = false;
1786
		$entryid = $this->getDefaultFolderEntryID($prop, $store);
1787
1788
		if ($entryid !== false) {
1789
			$folder = mapi_msgstore_openentry($store ? $store : $this->store, $entryid);
1790
		}
1791
1792
		return $folder;
1793
	}
1794
1795
	/**
1796
	 * Function will return entryid of default folder from store. This method is useful when you want
1797
	 * to get entryid of folder which is stored as store properties
1798
	 * (PR_IPM_FAVORITES_ENTRYID, PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID).
1799
	 *
1800
	 * @param int   $prop  proptag of the folder whose entryid we want to get
1801
	 * @param mixed $store {optional} user store from which we need to get entryid of default folder
1802
	 *
1803
	 * @return string|bool entryid of default folder from store
1804
	 */
1805
	public function getBaseEntryID($prop, $store = false) {
1806
		$storeprops = mapi_getprops($store ? $store : $this->store, [$prop]);
1807
		if (!isset($storeprops[$prop])) {
1808
			return false;
1809
		}
1810
1811
		return $storeprops[$prop];
1812
	}
1813
1814
	/**
1815
	 * Function will return resource of any default folder of store.
1816
	 *
1817
	 * @param int   $prop  proptag of the folder that we want to open
1818
	 * @param mixed $store {optional} user store from which we need to open default folder
1819
	 *
1820
	 * @return resource default folder of store
1821
	 */
1822
	public function openBaseFolder($prop, $store = false) {
1823
		$folder = false;
1824
		$entryid = $this->getBaseEntryID($prop, $store);
1825
1826
		if ($entryid !== false) {
1827
			$folder = mapi_msgstore_openentry($store ? $store : $this->store, $entryid);
1828
		}
1829
1830
		return $folder;
1831
	}
1832
1833
	/**
1834
	 * Function checks whether user has access over the specified folder or not.
1835
	 *
1836
	 * @param string $entryid entryid The entryid of the folder to check
1837
	 * @param mixed  $store   (optional) store from which folder should be opened
1838
	 *
1839
	 * @return bool true if user has an access over the folder, false if not
1840
	 */
1841
	public function checkFolderWriteAccess($entryid, $store = false) {
1842
		$accessToFolder = false;
1843
1844
		if (!empty($entryid)) {
1845
			if ($store === false) {
1846
				$store = $this->store;
1847
			}
1848
1849
			try {
1850
				$folder = mapi_msgstore_openentry($store, $entryid);
1851
				$folderProps = mapi_getprops($folder, [PR_ACCESS]);
0 ignored issues
show
Bug introduced by
The constant PR_ACCESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1852
				if (($folderProps[PR_ACCESS] & MAPI_ACCESS_CREATE_CONTENTS) === MAPI_ACCESS_CREATE_CONTENTS) {
1853
					$accessToFolder = true;
1854
				}
1855
			}
1856
			catch (MAPIException $e) {
1857
				// we don't have rights to open folder, so return false
1858
				if ($e->getCode() == MAPI_E_NO_ACCESS) {
0 ignored issues
show
Bug introduced by
The constant MAPI_E_NO_ACCESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1859
					return $accessToFolder;
1860
				}
1861
1862
				// rethrow other errors
1863
				throw $e;
1864
			}
1865
		}
1866
1867
		return $accessToFolder;
1868
	}
1869
1870
	/**
1871
	 * Function checks whether user has access over the specified folder or not.
1872
	 *
1873
	 * @param mixed $store
1874
	 *
1875
	 * @return bool true if user has an access over the folder, false if not
1876
	 */
1877
	public function checkCalendarWriteAccess($store = false) {
1878
		if ($store === false) {
1879
			// If this meeting request is received by a delegate then open delegator's store.
1880
			$messageProps = mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1881
			if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) {
1882
				$delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID]);
1883
1884
				$store = $delegatorStore['store'];
1885
			}
1886
			else {
1887
				$store = $this->store;
1888
			}
1889
		}
1890
1891
		// If the store is a public folder, the calendar folder is the PARENT_ENTRYID of the calendar item
1892
		$provider = mapi_getprops($store, [PR_MDB_PROVIDER]);
0 ignored issues
show
Bug introduced by
The constant PR_MDB_PROVIDER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1893
		if (isset($provider[PR_MDB_PROVIDER]) && $provider[PR_MDB_PROVIDER] === ZARAFA_STORE_PUBLIC_GUID) {
1894
			$entryid = mapi_getprops($this->message, [PR_PARENT_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_PARENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1895
			$entryid = $entryid[PR_PARENT_ENTRYID];
1896
		}
1897
		else {
1898
			$entryid = $this->getDefaultFolderEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_APPOINTMENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1899
			if ($entryid === false) {
1900
				$entryid = $this->getBaseEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store);
1901
			}
1902
1903
			if ($entryid === false) {
1904
				return false;
1905
			}
1906
		}
1907
1908
		return $this->checkFolderWriteAccess($entryid, $store);
1909
	}
1910
1911
	/**
1912
	 * Function will resolve the user and open its store.
1913
	 *
1914
	 * @param string $ownerentryid the entryid of the user
1915
	 *
1916
	 * @return resource store of the user
1917
	 */
1918
	public function openCustomUserStore($ownerentryid) {
1919
		$ab = mapi_openaddressbook($this->session);
1920
1921
		try {
1922
			$mailuser = mapi_ab_openentry($ab, $ownerentryid);
1923
		}
1924
		catch (MAPIException $e) {
1925
			return;
1926
		}
1927
1928
		$mailuserprops = mapi_getprops($mailuser, [PR_EMAIL_ADDRESS]);
0 ignored issues
show
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1929
		$storeid = mapi_msgstore_createentryid($this->store, $mailuserprops[PR_EMAIL_ADDRESS]);
1930
1931
		return mapi_openmsgstore($this->session, $storeid);
1932
	}
1933
1934
	/**
1935
	 * Function which sends response to organizer when attendee accepts, declines or proposes new time to a received meeting request.
1936
	 *
1937
	 * @param int   $status              response status of attendee
1938
	 * @param array $proposeNewTimeProps properties of attendee's proposal
1939
	 * @param mixed $body
1940
	 * @param mixed $store
1941
	 * @param int   $basedate            date of occurrence which attendee has responded
1942
	 * @param mixed $calFolder
1943
	 */
1944
	public function createResponse($status, $proposeNewTimeProps = [], $body = false, $store, $basedate = false, $calFolder) {
1945
		$messageprops = mapi_getprops($this->message, [
1946
			PR_SENT_REPRESENTING_ENTRYID,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1947
			PR_SENT_REPRESENTING_EMAIL_ADDRESS,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1948
			PR_SENT_REPRESENTING_ADDRTYPE,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1949
			PR_SENT_REPRESENTING_NAME,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1950
			PR_SENT_REPRESENTING_SEARCH_KEY,
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1951
			$this->proptags['goid'],
1952
			$this->proptags['goid2'],
1953
			$this->proptags['location'],
1954
			$this->proptags['startdate'],
1955
			$this->proptags['duedate'],
1956
			$this->proptags['recurring'],
1957
			$this->proptags['recurring_pattern'],
1958
			$this->proptags['recurrence_data'],
1959
			$this->proptags['timezone_data'],
1960
			$this->proptags['timezone'],
1961
			$this->proptags['updatecounter'],
1962
			PR_SUBJECT,
0 ignored issues
show
Bug introduced by
The constant PR_SUBJECT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1963
			PR_MESSAGE_CLASS,
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1964
			PR_OWNER_APPT_ID,
0 ignored issues
show
Bug introduced by
The constant PR_OWNER_APPT_ID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1965
			$this->proptags['is_exception'],
1966
		]);
1967
1968
		$props = [];
1969
1970
		if ($basedate !== false && !$this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS])) {
1971
			// we are creating response from a recurring calendar item object
1972
			// We found basedate,so opened occurrence and get properties.
1973
			$recurr = new Recurrence($store, $this->message);
1974
			$exception = $recurr->getExceptionAttachment($basedate);
1975
1976
			if ($exception) {
1977
				// Exception found, Now retrieve properties
1978
				$imessage = mapi_attach_openobj($exception, 0);
1979
				$imsgprops = mapi_getprops($imessage);
1980
1981
				// If location is provided, copy it to the response
1982
				if (isset($imsgprops[$this->proptags['location']])) {
1983
					$messageprops[$this->proptags['location']] = $imsgprops[$this->proptags['location']];
1984
				}
1985
1986
				// Update $messageprops with timings of occurrence
1987
				$messageprops[$this->proptags['startdate']] = $imsgprops[$this->proptags['startdate']];
1988
				$messageprops[$this->proptags['duedate']] = $imsgprops[$this->proptags['duedate']];
1989
1990
				// Meeting related properties
1991
				$props[$this->proptags['meetingstatus']] = $imsgprops[$this->proptags['meetingstatus']];
1992
				$props[$this->proptags['responsestatus']] = $imsgprops[$this->proptags['responsestatus']];
1993
				$props[PR_SUBJECT] = $imsgprops[PR_SUBJECT];
1994
			}
1995
			else {
1996
				// Exceptions is deleted.
1997
				// Update $messageprops with timings of occurrence
1998
				$messageprops[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
1999
				$messageprops[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
2000
2001
				$props[$this->proptags['meetingstatus']] = olNonMeeting;
2002
				$props[$this->proptags['responsestatus']] = olResponseNone;
2003
			}
2004
2005
			$props[$this->proptags['recurring']] = false;
2006
			$props[$this->proptags['is_exception']] = true;
2007
		}
2008
		else {
2009
			// we are creating a response from meeting request mail (it could be recurring or non-recurring)
2010
			// Send all recurrence info in response, if this is a recurrence meeting.
2011
			$isRecurring = isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']];
2012
			$isException = isset($messageprops[$this->proptags['is_exception']]) && $messageprops[$this->proptags['is_exception']];
2013
			if ($isRecurring || $isException) {
2014
				if ($isRecurring) {
2015
					$props[$this->proptags['recurring']] = $messageprops[$this->proptags['recurring']];
2016
				}
2017
				if ($isException) {
2018
					$props[$this->proptags['is_exception']] = $messageprops[$this->proptags['is_exception']];
2019
				}
2020
				$calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
2021
2022
				$calendaritem = mapi_msgstore_openentry($store, $calendaritems[0]);
2023
				$recurr = new Recurrence($store, $calendaritem);
2024
			}
2025
		}
2026
2027
		// we are sending a response for recurring meeting request (or exception), so set some required properties
2028
		if (isset($recurr) && $recurr) {
2029
			if (!empty($messageprops[$this->proptags['recurring_pattern']])) {
2030
				$props[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
2031
			}
2032
2033
			if (!empty($messageprops[$this->proptags['recurrence_data']])) {
2034
				$props[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
2035
			}
2036
2037
			$props[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
2038
			$props[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
2039
2040
			$this->generateRecurDates($recurr, $messageprops, $props);
2041
		}
2042
2043
		// Create a response message
2044
		$recip = [];
2045
		$recip[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
2046
		$recip[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
0 ignored issues
show
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2047
		$recip[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
0 ignored issues
show
Bug introduced by
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2048
		$recip[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2049
		$recip[PR_RECIPIENT_TYPE] = MAPI_TO;
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2050
		$recip[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
0 ignored issues
show
Bug introduced by
The constant PR_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2051
2052
		$subjectprefix = '';
2053
		switch ($status) {
2054
			case olResponseAccepted:
2055
				$classpostfix = 'Pos';
2056
				$subjectprefix = dgettext('zarafa', 'Accepted');
2057
				break;
2058
2059
			case olResponseDeclined:
2060
				$classpostfix = 'Neg';
2061
				$subjectprefix = dgettext('zarafa', 'Declined');
2062
				break;
2063
2064
			case olResponseTentative:
2065
				$classpostfix = 'Tent';
2066
				$subjectprefix = dgettext('zarafa', 'Tentatively accepted');
2067
				break;
2068
		}
2069
2070
		if (!empty($proposeNewTimeProps)) {
2071
			// if attendee has proposed new time then change subject prefix
2072
			$subjectprefix = dgettext('zarafa', 'New Time Proposed');
2073
		}
2074
2075
		$props[PR_SUBJECT] = $subjectprefix . ': ' . $messageprops[PR_SUBJECT];
2076
2077
		$props[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Resp.' . $classpostfix;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $classpostfix does not seem to be defined for all execution paths leading up to this point.
Loading history...
2078
		if (isset($messageprops[PR_OWNER_APPT_ID])) {
2079
			$props[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
2080
		}
2081
2082
		// Set GlobalId AND CleanGlobalId, if exception then also set basedate into GlobalId(0x3).
2083
		$props[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
2084
		$props[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
2085
		$props[$this->proptags['updatecounter']] = isset($messageprops[$this->proptags['updatecounter']]) ? $messageprops[$this->proptags['updatecounter']] : 0;
2086
2087
		if (!empty($proposeNewTimeProps)) {
2088
			// merge proposal properties to message properties which will be sent to organizer
2089
			$props = $proposeNewTimeProps + $props;
2090
		}
2091
2092
		// Set body message in Appointment
2093
		if (isset($body)) {
2094
			$props[PR_BODY] = $this->getMeetingTimeInfo() ? $this->getMeetingTimeInfo() : $body;
0 ignored issues
show
Bug introduced by
The constant PR_BODY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2095
		}
2096
2097
		// PR_START_DATE/PR_END_DATE is used in the UI in Outlook on the response message
2098
		$props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
0 ignored issues
show
Bug introduced by
The constant PR_START_DATE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2099
		$props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
0 ignored issues
show
Bug introduced by
The constant PR_END_DATE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2100
2101
		// Set startdate and duedate in response mail.
2102
		$props[$this->proptags['startdate']] = $messageprops[$this->proptags['startdate']];
2103
		$props[$this->proptags['duedate']] = $messageprops[$this->proptags['duedate']];
2104
2105
		// responselocation is used in the UI in Outlook on the response message
2106
		if (isset($messageprops[$this->proptags['location']])) {
2107
			$props[$this->proptags['responselocation']] = $messageprops[$this->proptags['location']];
2108
			$props[$this->proptags['location']] = $messageprops[$this->proptags['location']];
2109
		}
2110
2111
		$message = $this->createOutgoingMessage($store);
2112
2113
		mapi_setprops($message, $props);
2114
		mapi_message_modifyrecipients($message, MODRECIP_ADD, [$recip]);
2115
		mapi_savechanges($message);
2116
		mapi_message_submitmessage($message);
2117
	}
2118
2119
	/**
2120
	 * Function which finds items in calendar based on globalId and cleanGlobalId.
2121
	 *
2122
	 * @param string $goid             GlobalID(0x3) of item
2123
	 * @param mixed  $calendar         MAPI_folder of user (optional)
2124
	 * @param bool   $useCleanGlobalId if true then search should be performed on cleanGlobalId(0x23) else globalId(0x3)
2125
	 *
2126
	 * @return array
2127
	 */
2128
	public function findCalendarItems($goid, $calendar = false, $useCleanGlobalId = false) {
2129
		if ($calendar === false) {
2130
			// Open the Calendar
2131
			$calendar = $this->openDefaultCalendar();
2132
		}
2133
2134
		// Find the item by restricting all items to the correct ID
2135
		$restrict = [
2136
			RES_PROPERTY,
2137
			[
2138
				RELOP => RELOP_EQ,
2139
				ULPROPTAG => ($useCleanGlobalId === true ? $this->proptags['goid2'] : $this->proptags['goid']),
2140
				VALUE => $goid,
2141
			],
2142
		];
2143
2144
		$calendarcontents = mapi_folder_getcontentstable($calendar);
2145
2146
		$rows = mapi_table_queryallrows($calendarcontents, [PR_ENTRYID], $restrict);
2147
2148
		if (empty($rows)) {
2149
			return;
2150
		}
2151
2152
		$calendaritems = [];
2153
2154
		// In principle, there should only be one row, but we'll handle them all just in case
2155
		foreach ($rows as $row) {
2156
			$calendaritems[] = $row[PR_ENTRYID];
2157
		}
2158
2159
		return $calendaritems;
2160
	}
2161
2162
	// Returns TRUE if both entryid's are equal. Equality is defined by both entryid's pointing at the
2163
	// same SMTP address when converted to SMTP
2164
	public function compareABEntryIDs($entryid1, $entryid2) {
2165
		// If the session was not passed, just do a 'normal' compare.
2166
		if (!$this->session) {
2167
			return $entryid1 == $entryid2;
2168
		}
2169
2170
		$smtp1 = $this->getSMTPAddress($entryid1);
2171
		$smtp2 = $this->getSMTPAddress($entryid2);
2172
2173
		if ($smtp1 == $smtp2) {
2174
			return true;
2175
		}
2176
2177
		return false;
2178
	}
2179
2180
	// Gets the SMTP address of the passed addressbook entryid
2181
	public function getSMTPAddress($entryid) {
2182
		if (!$this->session) {
2183
			return false;
2184
		}
2185
2186
		try {
2187
			$ab = mapi_openaddressbook($this->session);
2188
			$abitem = mapi_ab_openentry($ab, $entryid);
2189
2190
			if (!$abitem) {
2191
				return '';
2192
			}
2193
		}
2194
		catch (MAPIException $e) {
2195
			return '';
2196
		}
2197
2198
		$props = mapi_getprops($abitem, [PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS]);
0 ignored issues
show
Bug introduced by
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SMTP_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2199
2200
		if ($props[PR_ADDRTYPE] == 'SMTP') {
2201
			return $props[PR_EMAIL_ADDRESS];
2202
		}
2203
2204
		return $props[PR_SMTP_ADDRESS];
2205
	}
2206
2207
	/**
2208
	 * Gets the properties associated with the owner of the passed store:
2209
	 * PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_ADDRTYPE, PR_ENTRYID, PR_SEARCH_KEY.
2210
	 *
2211
	 * @param mixed $store message store
2212
	 * @param bool  $fallbackToLoggedInUser If true then return properties of logged in user instead of mailbox owner.
2213
	 *                                      Not used when passed store is public store.
2214
	 *                                      For public store we are always returning logged in user's info.
2215
	 *
2216
	 * @return array|bool properties of logged in user in an array in sequence of display_name, email address, address type,
2217
	 *                    entryid and search key
2218
	 */
2219
	public function getOwnerAddress($store, $fallbackToLoggedInUser = true) {
2220
		if (!$this->session) {
2221
			return false;
2222
		}
2223
2224
		$storeProps = mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID, PR_USER_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_MAILBOX_OWNER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_USER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2225
2226
		$ownerEntryId = false;
2227
		if (isset($storeProps[PR_USER_ENTRYID]) && $storeProps[PR_USER_ENTRYID]) {
2228
			$ownerEntryId = $storeProps[PR_USER_ENTRYID];
2229
		}
2230
2231
		if (isset($storeProps[PR_MAILBOX_OWNER_ENTRYID]) && $storeProps[PR_MAILBOX_OWNER_ENTRYID] && !$fallbackToLoggedInUser) {
2232
			$ownerEntryId = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
2233
		}
2234
2235
		if ($ownerEntryId) {
2236
			$ab = mapi_openaddressbook($this->session);
2237
2238
			$zarafaUser = mapi_ab_openentry($ab, $ownerEntryId);
2239
			if (!$zarafaUser) {
2240
				return false;
2241
			}
2242
2243
			$ownerProps = mapi_getprops($zarafaUser, [PR_ADDRTYPE, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_SEARCH_KEY]);
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2244
2245
			$addrType = $ownerProps[PR_ADDRTYPE];
2246
			$name = $ownerProps[PR_DISPLAY_NAME];
2247
			$emailAddr = $ownerProps[PR_EMAIL_ADDRESS];
2248
			$searchKey = $ownerProps[PR_SEARCH_KEY];
2249
			$entryId = $ownerEntryId;
2250
2251
			return [$name, $emailAddr, $addrType, $entryId, $searchKey];
2252
		}
2253
2254
		return false;
2255
	}
2256
2257
	// Opens this session's default message store
2258
	public function openDefaultStore() {
2259
		$storestable = mapi_getmsgstorestable($this->session);
2260
		$rows = mapi_table_queryallrows($storestable, [PR_ENTRYID, PR_DEFAULT_STORE]);
0 ignored issues
show
Bug introduced by
The constant PR_DEFAULT_STORE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2261
2262
		foreach ($rows as $row) {
2263
			if (isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE]) {
2264
				$entryid = $row[PR_ENTRYID];
2265
				break;
2266
			}
2267
		}
2268
2269
		if (!$entryid) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $entryid does not seem to be defined for all execution paths leading up to this point.
Loading history...
2270
			return false;
2271
		}
2272
2273
		return mapi_openmsgstore($this->session, $entryid);
2274
	}
2275
2276
	/**
2277
	 *  Function which adds organizer to recipient list which is passed.
2278
	 *  This function also checks if it has organizer.
2279
	 *
2280
	 * @param array $messageProps message properties
2281
	 * @param array $recipients   recipients list of message
2282
	 * @param bool  $isException  true if we are processing recipient of exception
2283
	 */
2284
	public function addOrganizer($messageProps, &$recipients, $isException = false) {
2285
		$hasOrganizer = false;
2286
		// Check if meeting already has an organizer.
2287
		foreach ($recipients as $key => $recipient) {
2288
			if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2289
				$hasOrganizer = true;
2290
			}
2291
			elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) {
2292
				// Recipients for an occurrence
2293
				$recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
2294
			}
2295
		}
2296
2297
		if (!$hasOrganizer) {
2298
			// Create organizer.
2299
			$organizer = [];
2300
			$organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID];
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2301
			$organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2302
			$organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2303
			$organizer[PR_RECIPIENT_TYPE] = MAPI_TO;
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2304
			$organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2305
			$organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
0 ignored issues
show
Bug introduced by
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENT_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2306
			$organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TRACKSTATUS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2307
			$organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
2308
			$organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY];
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2309
2310
			// Add organizer to recipients list.
2311
			array_unshift($recipients, $organizer);
2312
		}
2313
	}
2314
2315
	/**
2316
	 * Function which removes an exception/occurrence from recurrencing meeting
2317
	 * when a meeting cancellation of an occurrence is processed.
2318
	 *
2319
	 * @param mixed    $basedate basedate of an occurrence
2320
	 * @param resource $message  recurring item from which occurrence has to be deleted
2321
	 * @param resource $store    MAPI_MSG_Store which contains the item
2322
	 */
2323
	public function doRemoveExceptionFromCalendar($basedate, $message, $store) {
2324
		$recurr = new Recurrence($store, $message);
2325
		$recurr->createException([], $basedate, true);
2326
		mapi_savechanges($message);
2327
	}
2328
2329
	/**
2330
	 * Function which returns basedate of an changed occurrence from globalID of meeting request.
2331
	 *
2332
	 * @param string $goid globalID
2333
	 *
2334
	 * @return int|bool true if basedate is found else false it not found
2335
	 */
2336
	public function getBasedateFromGlobalID($goid) {
2337
		$hexguid = bin2hex($goid);
2338
		$hexbase = substr($hexguid, 32, 8);
2339
		$day = (int) hexdec(substr($hexbase, 6, 2));
2340
		$month = (int) hexdec(substr($hexbase, 4, 2));
2341
		$year = (int) hexdec(substr($hexbase, 0, 4));
2342
2343
		if ($day && $month && $year) {
2344
			return gmmktime(0, 0, 0, $month, $day, $year);
2345
		}
2346
2347
		return false;
2348
	}
2349
2350
	/**
2351
	 * Function which sets basedate in globalID of changed occurrence which is to be sent.
2352
	 *
2353
	 * @param string $goid globalID
2354
	 * @param mixed  $basedate of changed occurrence
2355
	 *
2356
	 * @return string|bool globalID with basedate in it
2357
	 */
2358
	public function setBasedateInGlobalID($goid, $basedate = false) {
2359
		$hexguid = bin2hex($goid);
2360
		$year = $basedate ? sprintf('%04s', dechex((int) gmdate('Y', $basedate))) : '0000';
2361
		$month = $basedate ? sprintf('%02s', dechex((int) gmdate('m', $basedate))) : '00';
2362
		$day = $basedate ? sprintf('%02s', dechex((int) gmdate('d', $basedate))) : '00';
2363
2364
		return hex2bin(strtoupper(substr($hexguid, 0, 32) . $year . $month . $day . substr($hexguid, 40)));
2365
	}
2366
2367
	/**
2368
	 * Function which replaces attachments with copy_from in copy_to.
2369
	 *
2370
	 * @param mixed $copyFrom      MAPI_message from which attachments are to be copied
2371
	 * @param mixed $copyTo        MAPI_message to which attachment are to be copied
2372
	 * @param bool  $copyExceptions if true then all exceptions should also be sent as attachments
2373
	 */
2374
	public function replaceAttachments($copyFrom, $copyTo, $copyExceptions = true) {
2375
		/* remove all old attachments */
2376
		$attachmentTableTo = mapi_message_getattachmenttable($copyTo);
2377
		if ($attachmentTableTo) {
2378
			$attachments = mapi_table_queryallrows($attachmentTableTo, [PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME]);
0 ignored issues
show
Bug introduced by
The constant PR_EXCEPTION_STARTTIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_METHOD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_NUM was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2379
2380
			foreach ($attachments as $attachProps) {
2381
				/* remove exceptions too? */
2382
				if (!$copyExceptions && $attachProps[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG && isset($attachProps[PR_EXCEPTION_STARTTIME])) {
2383
					continue;
2384
				}
2385
				mapi_message_deleteattach($copyTo, $attachProps[PR_ATTACH_NUM]);
2386
			}
2387
		}
2388
2389
		/* copy new attachments */
2390
		$attachmentTableFrom = mapi_message_getattachmenttable($copyFrom);
2391
		if ($attachmentTableFrom) {
2392
			$attachments = mapi_table_queryallrows($attachmentTableFrom, [PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME]);
2393
2394
			foreach ($attachments as $attachProps) {
2395
				if (!$copyExceptions && $attachProps[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG && isset($attachProps[PR_EXCEPTION_STARTTIME])) {
2396
					continue;
2397
				}
2398
2399
				$attachOld = mapi_message_openattach($copyFrom, (int) $attachProps[PR_ATTACH_NUM]);
2400
				$attachNewResourceMsg = mapi_message_createattach($copyTo);
2401
				mapi_copyto($attachOld, [], [], $attachNewResourceMsg, 0);
2402
				mapi_savechanges($attachNewResourceMsg);
2403
			}
2404
		}
2405
	}
2406
2407
	/**
2408
	 * Function which replaces recipients in copyTo with recipients from copyFrom.
2409
	 *
2410
	 * @param mixed $copyFrom   MAPI_message from which recipients are to be copied
2411
	 * @param mixed $copyTo     MAPI_message to which recipients are to be copied
2412
	 * @param bool  $isDelegate indicates wheter delegate is processing
2413
	 *                          so don't copy delegate information to recipient table
2414
	 */
2415
	public function replaceRecipients($copyFrom, $copyTo, $isDelegate = false) {
2416
		$recipientTable = mapi_message_getrecipienttable($copyFrom);
2417
2418
		// If delegate, then do not add the delegate in recipients
2419
		if ($isDelegate) {
2420
			$delegate = mapi_getprops($copyFrom, [PR_RECEIVED_BY_EMAIL_ADDRESS]);
0 ignored issues
show
Bug introduced by
The constant PR_RECEIVED_BY_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2421
			$res = [
2422
				RES_PROPERTY,
2423
				[
2424
					RELOP => RELOP_NE,
2425
					ULPROPTAG => PR_EMAIL_ADDRESS,
0 ignored issues
show
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2426
					VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]],
2427
				],
2428
			];
2429
			$recipients = mapi_table_queryallrows($recipientTable, $this->recipprops, $res);
2430
		}
2431
		else {
2432
			$recipients = mapi_table_queryallrows($recipientTable, $this->recipprops);
2433
		}
2434
2435
		$copyToRecipientTable = mapi_message_getrecipienttable($copyTo);
2436
		$copyToRecipientRows = mapi_table_queryallrows($copyToRecipientTable, [PR_ROWID]);
0 ignored issues
show
Bug introduced by
The constant PR_ROWID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2437
2438
		mapi_message_modifyrecipients($copyTo, MODRECIP_REMOVE, $copyToRecipientRows);
2439
		mapi_message_modifyrecipients($copyTo, MODRECIP_ADD, $recipients);
2440
	}
2441
2442
	/**
2443
	 * Function creates meeting item in resource's calendar.
2444
	 *
2445
	 * @param resource $message  MAPI_message which is to create in resource's calendar
2446
	 * @param bool     $cancel   cancel meeting
2447
	 * @param mixed    $prefix   prefix for subject of meeting
2448
	 * @param mixed    $basedate
2449
	 */
2450
	public function bookResources($message, $cancel, $prefix, $basedate = false) {
2451
		if (!$this->enableDirectBooking) {
2452
			return [];
2453
		}
2454
2455
		// Get the properties of the message
2456
		$messageprops = mapi_getprops($message);
2457
2458
		if ($basedate) {
2459
			$recurrItemProps = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], $this->proptags['timezone_data'], $this->proptags['timezone'], PR_OWNER_APPT_ID]);
0 ignored issues
show
Bug introduced by
The constant PR_OWNER_APPT_ID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2460
2461
			$messageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid']], $basedate);
2462
			$messageprops[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']];
2463
2464
			// Delete properties which are not needed.
2465
			$deleteProps = [$this->proptags['basedate'], PR_DISPLAY_NAME, PR_ATTACHMENT_FLAGS, PR_ATTACHMENT_HIDDEN, PR_ATTACHMENT_LINKID, PR_ATTACH_FLAGS, PR_ATTACH_METHOD];
0 ignored issues
show
Bug introduced by
The constant PR_ATTACHMENT_LINKID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACHMENT_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_METHOD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACHMENT_HIDDEN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2466
			foreach ($deleteProps as $propID) {
2467
				if (isset($messageprops[$propID])) {
2468
					unset($messageprops[$propID]);
2469
				}
2470
			}
2471
2472
			if (isset($messageprops[$this->proptags['recurring']])) {
2473
				$messageprops[$this->proptags['recurring']] = false;
2474
			}
2475
2476
			// Set Outlook properties
2477
			$messageprops[$this->proptags['clipstart']] = $messageprops[$this->proptags['startdate']];
2478
			$messageprops[$this->proptags['clipend']] = $messageprops[$this->proptags['duedate']];
2479
			$messageprops[$this->proptags['timezone_data']] = $recurrItemProps[$this->proptags['timezone_data']];
2480
			$messageprops[$this->proptags['timezone']] = $recurrItemProps[$this->proptags['timezone']];
2481
			$messageprops[$this->proptags['attendee_critical_change']] = time();
2482
			$messageprops[$this->proptags['owner_critical_change']] = time();
2483
		}
2484
2485
		// Get resource recipients
2486
		$getResourcesRestriction = [
2487
			RES_PROPERTY,
2488
			[
2489
				RELOP => RELOP_EQ,	// Equals recipient type 3: Resource
2490
				ULPROPTAG => PR_RECIPIENT_TYPE,
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2491
				VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2492
			],
2493
		];
2494
		$recipienttable = mapi_message_getrecipienttable($message);
2495
		$resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
2496
2497
		$this->errorSetResource = false;
2498
		$resourceRecipData = [];
2499
2500
		// Put appointment into store resource users
2501
		$i = 0;
2502
		$len = count($resourceRecipients);
2503
		while (!$this->errorSetResource && $i < $len) {
2504
			$userStore = $this->openCustomUserStore($resourceRecipients[$i][PR_ENTRYID]);
2505
2506
			// Open root folder
2507
			$userRoot = mapi_msgstore_openentry($userStore, null);
2508
2509
			// Get calendar entryID
2510
			$userRootProps = mapi_getprops($userRoot, [PR_STORE_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_FREEBUSY_ENTRYIDS]);
0 ignored issues
show
Bug introduced by
The constant PR_STORE_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_FREEBUSY_ENTRYIDS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_IPM_APPOINTMENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2511
2512
			// Open Calendar folder
2513
			$accessToFolder = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $accessToFolder is dead and can be removed.
Loading history...
2514
2515
			try {
2516
				// @FIXME this checks delegate has access to resource's calendar folder
2517
				// but it should use boss' credentials
2518
2519
				$accessToFolder = $this->checkCalendarWriteAccess($this->store);
2520
				if ($accessToFolder) {
2521
					$calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
2522
				}
2523
			}
2524
			catch (MAPIException $e) {
2525
				$e->setHandled();
2526
				$this->errorSetResource = 1; // No access
2527
			}
2528
2529
			if ($accessToFolder) {
2530
				/**
2531
				 * Get the LocalFreebusy message that contains the properties that
2532
				 * are set to accept or decline resource meeting requests.
2533
				 */
2534
				$localFreebusyMsg = FreeBusy::getLocalFreeBusyMessage($userStore);
2535
				if ($localFreebusyMsg) {
2536
					$props = mapi_getprops($localFreebusyMsg, [PR_SCHDINFO_AUTO_ACCEPT_APPTS, PR_SCHDINFO_DISALLOW_RECURRING_APPTS, PR_SCHDINFO_DISALLOW_OVERLAPPING_APPTS]);
0 ignored issues
show
Bug introduced by
The constant PR_SCHDINFO_AUTO_ACCEPT_APPTS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SCHDINFO_DISALLOW_RECURRING_APPTS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SCHDINFO_DISALLOW_OVERLAPPING_APPTS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2537
2538
					$acceptMeetingRequests = isset($props[PR_SCHDINFO_AUTO_ACCEPT_APPTS]) ? $props[PR_SCHDINFO_AUTO_ACCEPT_APPTS] : false;
2539
					$declineRecurringMeetingRequests = isset($props[PR_SCHDINFO_DISALLOW_RECURRING_APPTS]) ? $props[PR_SCHDINFO_DISALLOW_RECURRING_APPTS] : false;
2540
					$declineConflictingMeetingRequests = isset($props[PR_SCHDINFO_DISALLOW_OVERLAPPING_APPTS]) ? $props[PR_SCHDINFO_DISALLOW_OVERLAPPING_APPTS] : false;
2541
2542
					if (!$acceptMeetingRequests) {
2543
						/*
2544
						 * When a resource has not been set to automatically accept meeting requests,
2545
						 * the meeting request has to be sent to him rather than being put directly into
2546
						 * his calendar. No error should be returned.
2547
						 */
2548
						// $errorSetResource = 2;
2549
						$this->nonAcceptingResources[] = $resourceRecipients[$i];
2550
					}
2551
					else {
2552
						if ($declineRecurringMeetingRequests && !$cancel) {
2553
							// Check if appointment is recurring
2554
							if ($messageprops[$this->proptags['recurring']]) {
2555
								$this->errorSetResource = 3;
2556
							}
2557
						}
2558
						if ($declineConflictingMeetingRequests && !$cancel) {
2559
							// Check for conflicting items
2560
							if ($calFolder && $this->isMeetingConflicting($message, $userStore, $calFolder)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $calFolder does not seem to be defined for all execution paths leading up to this point.
Loading history...
2561
								$this->errorSetResource = 4; // Conflict
2562
							}
2563
						}
2564
					}
2565
				}
2566
			}
2567
2568
			if (!$this->errorSetResource && $accessToFolder) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->errorSetResource of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2569
				/**
2570
				 * First search on GlobalID(0x3)
2571
				 * 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.
2572
				 * If (normal meeting) then GlobalID(0x3) and CleanGlobalID(0x23) are same, so doesn't matter if search is based on GlobalID.
2573
				 */
2574
				$rows = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
2575
2576
				/*
2577
				 * If no entry is found then
2578
				 * 1) Resource doesn't have meeting in Calendar. Seriously!!
2579
				 * OR
2580
				 * 2) We were looking for occurrence item but Resource has whole series
2581
				 */
2582
				if (empty($rows)) {
2583
					/**
2584
					 * Now search on CleanGlobalID(0x23) WHY???
2585
					 * Because we are looking recurring item.
2586
					 *
2587
					 * Possible results of this search
2588
					 * 1) If Resource was booked for more than one occurrences then this search will return all those occurrence because search is perform on CleanGlobalID
2589
					 * 2) If Resource was booked for whole series then it should return series.
2590
					 */
2591
					$rows = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
2592
2593
					$newResourceMsg = false;
2594
					if (!empty($rows)) {
2595
						// Since we are looking for recurring item, open every result and check for 'recurring' property.
2596
						foreach ($rows as $row) {
2597
							$ResourceMsg = mapi_msgstore_openentry($userStore, $row);
2598
							$ResourceMsgProps = mapi_getprops($ResourceMsg, [$this->proptags['recurring']]);
2599
2600
							if (isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2601
								$newResourceMsg = $ResourceMsg;
2602
								break;
2603
							}
2604
						}
2605
					}
2606
2607
					// Still no results found. I giveup, create new message.
2608
					if (!$newResourceMsg) {
2609
						$newResourceMsg = mapi_folder_createmessage($calFolder);
2610
					}
2611
				}
2612
				else {
2613
					$newResourceMsg = mapi_msgstore_openentry($userStore, $rows[0]);
2614
				}
2615
2616
				// Prefix the subject if needed
2617
				if ($prefix && isset($messageprops[PR_SUBJECT])) {
0 ignored issues
show
Bug introduced by
The constant PR_SUBJECT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2618
					$messageprops[PR_SUBJECT] = $prefix . $messageprops[PR_SUBJECT];
2619
				}
2620
2621
				// Set status to cancelled if needed
2622
				$messageprops[$this->proptags['busystatus']] = fbBusy; // The default status (Busy)
2623
				if ($cancel) {
2624
					$messageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // The meeting has been canceled
2625
					$messageprops[$this->proptags['busystatus']] = fbFree; // Free
2626
				}
2627
				else {
2628
					$messageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
2629
				}
2630
				$messageprops[$this->proptags['responsestatus']] = olResponseAccepted; // The resource automatically accepts the appointment
2631
2632
				$messageprops[PR_MESSAGE_CLASS] = 'IPM.Appointment';
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2633
2634
				// Remove the PR_ICON_INDEX as it is not needed in the sent message.
2635
				$messageprops[PR_ICON_INDEX] = null;
0 ignored issues
show
Bug introduced by
The constant PR_ICON_INDEX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2636
				$messageprops[PR_RESPONSE_REQUESTED] = true;
0 ignored issues
show
Bug introduced by
The constant PR_RESPONSE_REQUESTED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2637
2638
				// get the store of organizer, in case of delegates it will be delegate store
2639
				$defaultStore = $this->openDefaultStore();
2640
2641
				$storeProps = mapi_getprops($this->store, [PR_ENTRYID]);
2642
				$defaultStoreProps = mapi_getprops($defaultStore, [PR_ENTRYID]);
2643
2644
				// @FIXME use entryid comparison functions here
2645
				if ($storeProps[PR_ENTRYID] !== $defaultStoreProps[PR_ENTRYID]) {
2646
					// get delegate information
2647
					$addrInfo = $this->getOwnerAddress($defaultStore, false);
2648
2649
					if ($addrInfo) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $addrInfo of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2650
						list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrInfo;
2651
2652
						$messageprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2653
						$messageprops[PR_SENDER_NAME] = $ownername;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2654
						$messageprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2655
						$messageprops[PR_SENDER_ENTRYID] = $ownerentryid;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2656
						$messageprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2657
					}
2658
2659
					// get delegator information
2660
					$addrInfo = $this->getOwnerAddress($this->store, false);
2661
2662
					if ($addrInfo) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $addrInfo of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2663
						list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrInfo;
2664
2665
						$messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2666
						$messageprops[PR_SENT_REPRESENTING_NAME] = $ownername;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2667
						$messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2668
						$messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2669
						$messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2670
					}
2671
				}
2672
				else {
2673
					// get organizer information
2674
					$addrinfo = $this->getOwnerAddress($this->store);
2675
2676
					if ($addrinfo) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $addrinfo of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2677
						list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrinfo;
2678
2679
						$messageprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
2680
						$messageprops[PR_SENDER_NAME] = $ownername;
2681
						$messageprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
2682
						$messageprops[PR_SENDER_ENTRYID] = $ownerentryid;
2683
						$messageprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
2684
2685
						$messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
2686
						$messageprops[PR_SENT_REPRESENTING_NAME] = $ownername;
2687
						$messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
2688
						$messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
2689
						$messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
2690
					}
2691
				}
2692
2693
				$messageprops[$this->proptags['replytime']] = time();
2694
2695
				if ($basedate && isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2696
					$recurr = new Recurrence($userStore, $newResourceMsg);
2697
2698
					// Copy recipients list
2699
					$reciptable = mapi_message_getrecipienttable($message);
2700
					$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2701
2702
					// add owner to recipient table
2703
					$this->addOrganizer($messageprops, $recips, true);
2704
2705
					// Update occurrence
2706
					if ($recurr->isException($basedate)) {
2707
						$recurr->modifyException($messageprops, $basedate, $recips);
2708
					}
2709
					else {
2710
						$recurr->createException($messageprops, $basedate, false, $recips);
2711
					}
2712
				}
2713
				else {
2714
					mapi_setprops($newResourceMsg, $messageprops);
2715
2716
					// Copy attachments
2717
					$this->replaceAttachments($message, $newResourceMsg);
2718
2719
					// Copy all recipients too
2720
					$this->replaceRecipients($message, $newResourceMsg);
2721
2722
					// Now add organizer also to recipient table
2723
					$recips = [];
2724
					$this->addOrganizer($messageprops, $recips);
2725
2726
					mapi_message_modifyrecipients($newResourceMsg, MODRECIP_ADD, $recips);
2727
				}
2728
2729
				mapi_savechanges($newResourceMsg);
2730
2731
				$resourceRecipData[] = [
2732
					'store' => $userStore,
2733
					'folder' => $calFolder,
2734
					'msg' => $newResourceMsg,
2735
				];
2736
				$this->includesResources = true;
2737
			}
2738
			else {
2739
				/*
2740
				 * If no other errors occurred and you have no access to the
2741
				 * folder of the resource, throw an error=1.
2742
				 */
2743
				if (!$this->errorSetResource) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->errorSetResource of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2744
					$this->errorSetResource = 1;
2745
				}
2746
2747
				for ($j = 0, $len = count($resourceRecipData); $j < $len; ++$j) {
2748
					// Get the EntryID
2749
					$props = mapi_message_getprops($resourceRecipData[$j]['msg']);
0 ignored issues
show
Bug introduced by
The function mapi_message_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2749
					$props = /** @scrutinizer ignore-call */ mapi_message_getprops($resourceRecipData[$j]['msg']);
Loading history...
2750
2751
					mapi_folder_deletemessages($resourceRecipData[$j]['folder'], [$props[PR_ENTRYID]], DELETE_HARD_DELETE);
2752
				}
2753
				$this->recipientDisplayname = $resourceRecipients[$i][PR_DISPLAY_NAME];
2754
			}
2755
			++$i;
2756
		}
2757
2758
		/*
2759
		 * Set the BCC-recipients (resources) tackstatus to accepted.
2760
		 */
2761
		// Get resource recipients
2762
		$getResourcesRestriction = [
2763
			RES_PROPERTY,
2764
			[
2765
				RELOP => RELOP_EQ,	// Equals recipient type 3: Resource
2766
				ULPROPTAG => PR_RECIPIENT_TYPE,
2767
				VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2768
			],
2769
		];
2770
		$recipienttable = mapi_message_getrecipienttable($message);
2771
		$resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
2772
		if (!empty($resourceRecipients)) {
2773
			// Set Tracking status of resource recipients to olResponseAccepted (3)
2774
			for ($i = 0, $len = count($resourceRecipients); $i < $len; ++$i) {
2775
				$resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusAccepted;
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TRACKSTATUS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2776
				$resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS_TIME] = time();
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TRACKSTATUS_TIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2777
			}
2778
			mapi_message_modifyrecipients($message, MODRECIP_MODIFY, $resourceRecipients);
2779
		}
2780
2781
		return $resourceRecipData;
2782
	}
2783
2784
	/**
2785
	 * Function which save an exception into recurring item.
2786
	 *
2787
	 * @param resource $recurringItem  reference to MAPI_message of recurring item
2788
	 * @param resource $occurrenceItem reference to MAPI_message of occurrence
2789
	 * @param string   $basedate       basedate of occurrence
2790
	 * @param bool     $move           if true then occurrence item is deleted
2791
	 * @param bool     $tentative      true if user has tentatively accepted it or false if user has accepted it
2792
	 * @param bool     $userAction     true if user has manually responded to meeting request
2793
	 * @param resource $store          user store
2794
	 * @param bool     $isDelegate     true if delegate is processing this meeting request
2795
	 */
2796
	public function acceptException(&$recurringItem, &$occurrenceItem, $basedate, $move = false, $tentative, $userAction = false, $store, $isDelegate = false) {
2797
		$recurr = new Recurrence($store, $recurringItem);
2798
2799
		// Copy properties from meeting request
2800
		$exception_props = mapi_getprops($occurrenceItem);
2801
2802
		// Copy recipients list
2803
		$reciptable = mapi_message_getrecipienttable($occurrenceItem);
2804
		// If delegate, then do not add the delegate in recipients
2805
		if ($isDelegate) {
2806
			$delegate = mapi_getprops($this->message, [PR_RECEIVED_BY_EMAIL_ADDRESS]);
0 ignored issues
show
Bug introduced by
The constant PR_RECEIVED_BY_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2807
			$res = [
2808
				RES_PROPERTY,
2809
				[
2810
					RELOP => RELOP_NE,
2811
					ULPROPTAG => PR_EMAIL_ADDRESS,
0 ignored issues
show
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2812
					VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]],
2813
				],
2814
			];
2815
			$recips = mapi_table_queryallrows($reciptable, $this->recipprops, $res);
2816
		}
2817
		else {
2818
			$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2819
		}
2820
2821
		// add owner to recipient table
2822
		$this->addOrganizer($exception_props, $recips, true);
2823
2824
		// add delegator to meetings
2825
		if ($isDelegate) {
2826
			$this->addDelegator($exception_props, $recips);
2827
		}
2828
2829
		$exception_props[$this->proptags['meetingstatus']] = olMeetingReceived;
2830
		$exception_props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
2831
2832
		if (isset($exception_props[$this->proptags['intendedbusystatus']])) {
2833
			if ($tentative && $exception_props[$this->proptags['intendedbusystatus']] !== fbFree) {
2834
				$exception_props[$this->proptags['busystatus']] = fbTentative;
2835
			}
2836
			else {
2837
				$exception_props[$this->proptags['busystatus']] = $exception_props[$this->proptags['intendedbusystatus']];
2838
			}
2839
			// we already have intendedbusystatus value in $exception_props so no need to copy it
2840
		}
2841
		else {
2842
			$exception_props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
2843
		}
2844
2845
		if ($userAction) {
2846
			$addrInfo = $this->getOwnerAddress($this->store);
2847
2848
			// if user has responded then set replytime and name
2849
			$exception_props[$this->proptags['replytime']] = time();
2850
			if (!empty($addrInfo)) {
2851
				$exception_props[$this->proptags['apptreplyname']] = $addrInfo[0];
2852
			}
2853
		}
2854
2855
		if ($recurr->isException($basedate)) {
2856
			$recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
2857
		}
2858
		else {
2859
			$recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
2860
		}
2861
2862
		// Move the occurrenceItem to the waste basket
2863
		if ($move) {
2864
			$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
2865
			$sourcefolder = mapi_msgstore_openentry($store, $exception_props[PR_PARENT_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_PARENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2866
			mapi_folder_copymessages($sourcefolder, [$exception_props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
2867
		}
2868
2869
		mapi_savechanges($recurringItem);
2870
	}
2871
2872
	/**
2873
	 * Function which merges an exception mapi message to recurring message.
2874
	 * This will be used when we receive recurring meeting request and we already have an exception message
2875
	 * of same meeting in calendar and we need to remove that exception message and add it to attachment table
2876
	 * of recurring meeting.
2877
	 *
2878
	 * @param resource $recurringItem  reference to MAPI_message of recurring item
2879
	 * @param resource $occurrenceItem reference to MAPI_message of occurrence
2880
	 * @param mixed    $basedate       basedate of occurrence
2881
	 * @param resource $store          user store
2882
	 */
2883
	public function mergeException(&$recurringItem, &$occurrenceItem, $basedate, $store) {
2884
		$recurr = new Recurrence($store, $recurringItem);
2885
2886
		// Copy properties from meeting request
2887
		$exception_props = mapi_getprops($occurrenceItem);
2888
2889
		// Get recipient list from message and add it to exception attachment
2890
		$reciptable = mapi_message_getrecipienttable($occurrenceItem);
2891
		$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2892
2893
		if ($recurr->isException($basedate)) {
2894
			$recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
2895
		}
2896
		else {
2897
			$recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
2898
		}
2899
2900
		// Move the occurrenceItem to the waste basket
2901
		$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
2902
		$sourcefolder = mapi_msgstore_openentry($store, $exception_props[PR_PARENT_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_PARENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2903
		mapi_folder_copymessages($sourcefolder, [$exception_props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
2904
2905
		mapi_savechanges($recurringItem);
2906
	}
2907
2908
	/**
2909
	 * Function which submits meeting request based on arguments passed to it.
2910
	 *
2911
	 * @param resource $message        MAPI_message whose meeting request is to be sent
2912
	 * @param bool     $cancel         if true send request, else send cancellation
2913
	 * @param mixed    $prefix         subject prefix
2914
	 * @param mixed    $basedate       basedate for an occurrence
2915
	 * @param mixed    $recurObject    recurrence object of mr
2916
	 * @param bool     $copyExceptions When sending update mail for recurring item then we don't send exceptions in attachments
2917
	 * @param mixed    $modifiedRecips
2918
	 * @param mixed    $deletedRecips
2919
	 */
2920
	public function submitMeetingRequest($message, $cancel, $prefix, $basedate = false, $recurObject = false, $copyExceptions = true, $modifiedRecips = false, $deletedRecips = false) {
2921
		$newmessageprops = $messageprops = mapi_getprops($this->message);
2922
		$new = $this->createOutgoingMessage();
2923
2924
		// Copy the entire message into the new meeting request message
2925
		if ($basedate) {
2926
			// messageprops contains properties of whole recurring series
2927
			// and newmessageprops contains properties of exception item
2928
			$newmessageprops = mapi_getprops($message);
2929
2930
			// Ensure that the correct basedate is set in the new message
2931
			$newmessageprops[$this->proptags['basedate']] = $basedate;
2932
2933
			// Set isRecurring to false, because this is an exception
2934
			$newmessageprops[$this->proptags['recurring']] = false;
2935
2936
			// set LID_IS_EXCEPTION to true
2937
			$newmessageprops[$this->proptags['is_exception']] = true;
2938
2939
			// Set to high importance
2940
			if ($cancel) {
2941
				$newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;
0 ignored issues
show
Bug introduced by
The constant PR_IMPORTANCE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2942
			}
2943
2944
			// Set startdate and enddate of exception
2945
			if ($cancel && $recurObject) {
2946
				$newmessageprops[$this->proptags['startdate']] = $recurObject->getOccurrenceStart($basedate);
2947
				$newmessageprops[$this->proptags['duedate']] = $recurObject->getOccurrenceEnd($basedate);
2948
			}
2949
2950
			// Set basedate in guid (0x3)
2951
			$newmessageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
2952
			$newmessageprops[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
2953
			$newmessageprops[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
0 ignored issues
show
Bug introduced by
The constant PR_OWNER_APPT_ID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2954
2955
			// Get deleted recipiets from exception msg
2956
			$restriction = [
2957
				RES_AND,
2958
				[
2959
					[
2960
						RES_BITMASK,
2961
						[
2962
							ULTYPE => BMR_NEZ,
2963
							ULPROPTAG => PR_RECIPIENT_FLAGS,
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2964
							ULMASK => recipExceptionalDeleted,
2965
						],
2966
					],
2967
					[
2968
						RES_BITMASK,
2969
						[
2970
							ULTYPE => BMR_EQZ,
2971
							ULPROPTAG => PR_RECIPIENT_FLAGS,
2972
							ULMASK => recipOrganizer,
2973
						],
2974
					],
2975
				],
2976
			];
2977
2978
			// In direct-booking mode, we don't need to send cancellations to resources
2979
			if ($this->enableDirectBooking) {
2980
				$restriction[1][] = [
2981
					RES_PROPERTY,
2982
					[
2983
						RELOP => RELOP_NE,	// Does not equal recipient type: MAPI_BCC (Resource)
2984
						ULPROPTAG => PR_RECIPIENT_TYPE,
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2985
						VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2986
					],
2987
				];
2988
			}
2989
2990
			$recipienttable = mapi_message_getrecipienttable($message);
2991
			$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $restriction);
2992
2993
			if (!$deletedRecips) {
2994
				$deletedRecips = array_merge([], $recipients);
2995
			}
2996
			else {
2997
				$deletedRecips = array_merge($deletedRecips, $recipients);
2998
			}
2999
		}
3000
3001
		// Remove the PR_ICON_INDEX as it is not needed in the sent message.
3002
		$newmessageprops[PR_ICON_INDEX] = null;
0 ignored issues
show
Bug introduced by
The constant PR_ICON_INDEX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3003
		$newmessageprops[PR_RESPONSE_REQUESTED] = true;
0 ignored issues
show
Bug introduced by
The constant PR_RESPONSE_REQUESTED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3004
3005
		// PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
3006
		$newmessageprops[PR_START_DATE] = $newmessageprops[$this->proptags['startdate']];
0 ignored issues
show
Bug introduced by
The constant PR_START_DATE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3007
		$newmessageprops[PR_END_DATE] = $newmessageprops[$this->proptags['duedate']];
0 ignored issues
show
Bug introduced by
The constant PR_END_DATE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3008
3009
		// Set updatecounter/AppointmentSequenceNumber
3010
		// get the value of latest updatecounter for the whole series and use it
3011
		$newmessageprops[$this->proptags['updatecounter']] = $messageprops[$this->proptags['last_updatecounter']];
3012
3013
		$meetingTimeInfo = $this->getMeetingTimeInfo();
3014
3015
		if ($meetingTimeInfo) {
3016
			// Needs to unset PR_HTML and PR_RTF_COMPRESSED props
3017
			// because while canceling meeting requests with edit text
3018
			// will override the PR_BODY because body value is not consistent with
3019
			// PR_HTML and PR_RTF_COMPRESSED value so in this case PR_RTF_COMPRESSED will
3020
			// get priority which override the PR_BODY value.
3021
			unset($newmessageprops[PR_HTML], $newmessageprops[PR_RTF_COMPRESSED]);
0 ignored issues
show
Bug introduced by
The constant PR_HTML was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_RTF_COMPRESSED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3022
3023
			$newmessageprops[PR_BODY] = $meetingTimeInfo;
0 ignored issues
show
Bug introduced by
The constant PR_BODY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3024
		}
3025
3026
		// Send all recurrence info in mail, if this is a recurrence meeting.
3027
		if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']]) {
3028
			if (!empty($messageprops[$this->proptags['recurring_pattern']])) {
3029
				$newmessageprops[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
3030
			}
3031
			$newmessageprops[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
3032
			$newmessageprops[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
3033
			$newmessageprops[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
3034
3035
			if ($recurObject) {
3036
				$this->generateRecurDates($recurObject, $messageprops, $newmessageprops);
3037
			}
3038
		}
3039
3040
		if (isset($newmessageprops[$this->proptags['counter_proposal']])) {
3041
			unset($newmessageprops[$this->proptags['counter_proposal']]);
3042
		}
3043
3044
		// Prefix the subject if needed
3045
		if ($prefix && isset($newmessageprops[PR_SUBJECT])) {
0 ignored issues
show
Bug introduced by
The constant PR_SUBJECT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3046
			$newmessageprops[PR_SUBJECT] = $prefix . $newmessageprops[PR_SUBJECT];
3047
		}
3048
3049
		if (isset($newmessageprops[$this->proptags['categories']]) &&
3050
			!empty($newmessageprops[$this->proptags['categories']])) {
3051
			unset($newmessageprops[$this->proptags['categories']]);
3052
		}
3053
		mapi_setprops($new, $newmessageprops);
3054
3055
		// Copy attachments
3056
		$this->replaceAttachments($message, $new, $copyExceptions);
3057
3058
		// Retrieve only those recipient who should receive this meeting request.
3059
		$stripResourcesRestriction = [
3060
			RES_AND,
3061
			[
3062
				[
3063
					RES_BITMASK,
3064
					[
3065
						ULTYPE => BMR_EQZ,
3066
						ULPROPTAG => PR_RECIPIENT_FLAGS,
3067
						ULMASK => recipExceptionalDeleted,
3068
					],
3069
				],
3070
				[
3071
					RES_BITMASK,
3072
					[
3073
						ULTYPE => BMR_EQZ,
3074
						ULPROPTAG => PR_RECIPIENT_FLAGS,
3075
						ULMASK => recipOrganizer,
3076
					],
3077
				],
3078
			],
3079
		];
3080
3081
		// In direct-booking mode, resources do not receive a meeting request
3082
		if ($this->enableDirectBooking) {
3083
			$stripResourcesRestriction[1][] = [
3084
				RES_PROPERTY,
3085
				[
3086
					RELOP => RELOP_NE,	// Does not equal recipient type: MAPI_BCC (Resource)
3087
					ULPROPTAG => PR_RECIPIENT_TYPE,
3088
					VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
3089
				],
3090
			];
3091
		}
3092
3093
		// If no recipients were explicitly provided, we will send the update to all
3094
		// recipients from the meeting.
3095
		if ($modifiedRecips === false) {
3096
			$recipienttable = mapi_message_getrecipienttable($message);
3097
			$modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
3098
3099
			if ($basedate && empty($modifiedRecips)) {
3100
				// Retrieve full list
3101
				$recipienttable = mapi_message_getrecipienttable($this->message);
3102
				$modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops);
3103
3104
				// Save recipients in exceptions
3105
				mapi_message_modifyrecipients($message, MODRECIP_ADD, $modifiedRecips);
3106
3107
				// Now retrieve only those recipient who should receive this meeting request.
3108
				$modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
3109
			}
3110
		}
3111
3112
		// @TODO: handle nonAcceptingResources
3113
		/*
3114
		 * Add resource recipients that did not automatically accept the meeting request.
3115
		 * (note: meaning that they did not decline the meeting request)
3116
		 */ /*
3117
		for($i=0;$i<count($this->nonAcceptingResources);$i++){
3118
			$recipients[] = $this->nonAcceptingResources[$i];
3119
		}*/
3120
3121
		if (!empty($modifiedRecips)) {
3122
			// Strip out the sender/'owner' recipient
3123
			mapi_message_modifyrecipients($new, MODRECIP_ADD, $modifiedRecips);
3124
3125
			// Set some properties that are different in the sent request than
3126
			// in the item in our calendar
3127
3128
			// we should store busystatus value to intendedbusystatus property, because busystatus for outgoing meeting request
3129
			// should always be fbTentative
3130
			$newmessageprops[$this->proptags['intendedbusystatus']] = isset($newmessageprops[$this->proptags['busystatus']]) ? $newmessageprops[$this->proptags['busystatus']] : $messageprops[$this->proptags['busystatus']];
3131
			$newmessageprops[$this->proptags['busystatus']] = fbTentative; // The default status when not accepted
3132
			$newmessageprops[$this->proptags['responsestatus']] = olResponseNotResponded; // The recipient has not responded yet
3133
			$newmessageprops[$this->proptags['attendee_critical_change']] = time();
3134
			$newmessageprops[$this->proptags['owner_critical_change']] = time();
3135
			$newmessageprops[$this->proptags['meetingtype']] = mtgRequest;
3136
3137
			if ($cancel) {
3138
				$newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled';
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3139
				$newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
3140
				$newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
3141
			}
3142
			else {
3143
				$newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Request';
3144
				$newmessageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
3145
			}
3146
3147
			mapi_setprops($new, $newmessageprops);
3148
			mapi_savechanges($new);
3149
3150
			// Submit message to non-resource recipients
3151
			mapi_message_submitmessage($new);
3152
		}
3153
3154
		// Search through the deleted recipients, and see if any of them is also
3155
		// listed as a recipient to whom we have sent an update. As we don't
3156
		// want to send a cancellation message to recipients who will also receive
3157
		// an meeting update, we have to filter those recipients out.
3158
		if ($deletedRecips) {
3159
			$tmp = [];
3160
3161
			foreach ($deletedRecips as $delRecip) {
3162
				$found = false;
3163
3164
				// Search if the deleted recipient can be found inside
3165
				// the updated recipients as well.
3166
				foreach ($modifiedRecips as $recip) {
3167
					if ($this->compareABEntryIDs($recip[PR_ENTRYID], $delRecip[PR_ENTRYID])) {
3168
						$found = true;
3169
						break;
3170
					}
3171
				}
3172
3173
				// If the recipient was not found, it truly is deleted,
3174
				// and we can safely send a cancellation message
3175
				if (!$found) {
3176
					$tmp[] = $delRecip;
3177
				}
3178
			}
3179
3180
			$deletedRecips = $tmp;
3181
		}
3182
3183
		// Send cancellation to deleted attendees
3184
		if ($deletedRecips && !empty($deletedRecips)) {
3185
			$new = $this->createOutgoingMessage();
3186
3187
			mapi_message_modifyrecipients($new, MODRECIP_ADD, $deletedRecips);
3188
3189
			$newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled';
3190
			$newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
3191
			$newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
3192
			$newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;	// HIGH Importance
3193
			if (isset($newmessageprops[PR_SUBJECT])) {
3194
				$newmessageprops[PR_SUBJECT] = dgettext('zarafa', 'Canceled') . ': ' . $newmessageprops[PR_SUBJECT];
3195
			}
3196
3197
			mapi_setprops($new, $newmessageprops);
3198
			mapi_savechanges($new);
3199
3200
			// Submit message to non-resource recipients
3201
			mapi_message_submitmessage($new);
3202
		}
3203
3204
		// Set properties on meeting object in calendar
3205
		// Set requestsent to 'true' (turns on 'tracking', etc)
3206
		$props = [];
3207
		$props[$this->proptags['meetingstatus']] = olMeeting;
3208
		$props[$this->proptags['responsestatus']] = olResponseOrganized;
3209
		// Only set the 'requestsent' property if it wasn't set previously yet,
3210
		// this ensures we will not accidentally set it from true to false.
3211
		if (!isset($messageprops[$this->proptags['requestsent']]) || $messageprops[$this->proptags['requestsent']] !== true) {
3212
			$props[$this->proptags['requestsent']] = !empty($modifiedRecips) || ($this->includesResources && !$this->errorSetResource);
3213
		}
3214
		$props[$this->proptags['attendee_critical_change']] = time();
3215
		$props[$this->proptags['owner_critical_change']] = time();
3216
		$props[$this->proptags['meetingtype']] = mtgRequest;
3217
		// save the new updatecounter to exception/recurring series/normal meeting
3218
		$props[$this->proptags['updatecounter']] = $newmessageprops[$this->proptags['updatecounter']];
3219
3220
		// PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
3221
		$props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
3222
		$props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
3223
3224
		mapi_setprops($message, $props);
3225
3226
		// saving of these properties on calendar item should be handled by caller function
3227
		// based on sending meeting request was successful or not
3228
	}
3229
3230
	/**
3231
	 * OL2007 uses these 4 properties to specify occurrence that should be updated.
3232
	 * ical generates RECURRENCE-ID property based on exception's basedate (PidLidExceptionReplaceTime),
3233
	 * but OL07 doesn't send this property, so ical will generate RECURRENCE-ID property based on date
3234
	 * from GlobalObjId and time from StartRecurTime property, so we are sending basedate property and
3235
	 * also additionally we are sending these properties.
3236
	 * Ref: MS-OXCICAL 2.2.1.20.20 Property: RECURRENCE-ID.
3237
	 *
3238
	 * @param object $recurObject     instance of recurrence class for this message
3239
	 * @param array  $messageprops    properties of meeting object that is going to be sent
3240
	 * @param array  $newmessageprops properties of meeting request/response that is going to be sent
3241
	 */
3242
	public function generateRecurDates($recurObject, $messageprops, &$newmessageprops) {
3243
		if ($messageprops[$this->proptags['startdate']] && $messageprops[$this->proptags['duedate']]) {
3244
			$startDate = date('Y:n:j:G:i:s', $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['startdate']]));
3245
			$endDate = date('Y:n:j:G:i:s', $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['duedate']]));
3246
3247
			$startDate = explode(':', $startDate);
3248
			$endDate = explode(':', $endDate);
3249
3250
			// [0] => year, [1] => month, [2] => day, [3] => hour, [4] => minutes, [5] => seconds
3251
			// RecurStartDate = year * 512 + month_number * 32 + day_number
3252
			$newmessageprops[$this->proptags['start_recur_date']] = (((int) $startDate[0]) * 512) + (((int) $startDate[1]) * 32) + ((int) $startDate[2]);
3253
			// RecurStartTime = hour * 4096 + minutes * 64 + seconds
3254
			$newmessageprops[$this->proptags['start_recur_time']] = (((int) $startDate[3]) * 4096) + (((int) $startDate[4]) * 64) + ((int) $startDate[5]);
3255
3256
			$newmessageprops[$this->proptags['end_recur_date']] = (((int) $endDate[0]) * 512) + (((int) $endDate[1]) * 32) + ((int) $endDate[2]);
3257
			$newmessageprops[$this->proptags['end_recur_time']] = (((int) $endDate[3]) * 4096) + (((int) $endDate[4]) * 64) + ((int) $endDate[5]);
3258
		}
3259
	}
3260
3261
	/**
3262
	 * Function will create a new outgoing message that will be used to send meeting mail.
3263
	 *
3264
	 * @param mixed $store (optional) store that is used when creating response, if delegate is creating outgoing mail
3265
	 *                         then this would point to delegate store
3266
	 *
3267
	 * @return resource outgoing mail that is created and can be used for sending it
3268
	 */
3269
	public function createOutgoingMessage($store = false) {
3270
		// get logged in user's store that will be used to send mail, for delegate this will be
3271
		// delegate store
3272
		$userStore = $this->openDefaultStore();
3273
3274
		$sentprops = [];
3275
		$outbox = $this->openDefaultOutbox($userStore);
3276
3277
		$outgoing = mapi_folder_createmessage($outbox);
3278
3279
		// check if $store is set and it is not equal to $defaultStore (means its the delegation case)
3280
		if ($store !== false) {
3281
			$storeProps = mapi_getprops($store, [PR_ENTRYID]);
3282
			$userStoreProps = mapi_getprops($userStore, [PR_ENTRYID]);
3283
3284
			// @FIXME use entryid comparison functions here
3285
			if ($storeProps[PR_ENTRYID] !== $userStoreProps[PR_ENTRYID]) {
3286
				// get the delegator properties and set it into outgoing mail
3287
				$delegatorDetails = $this->getOwnerAddress($store, false);
3288
3289
				if ($delegatorDetails) {
0 ignored issues
show
introduced by
$delegatorDetails is a non-empty array, thus is always true.
Loading history...
Bug Best Practice introduced by
The expression $delegatorDetails of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
3290
					list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $delegatorDetails;
3291
					$sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3292
					$sentprops[PR_SENT_REPRESENTING_NAME] = $ownername;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3293
					$sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3294
					$sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3295
					$sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3296
				}
3297
3298
				// get the delegate properties and set it into outgoing mail
3299
				$delegateDetails = $this->getOwnerAddress($userStore, false);
3300
3301
				if ($delegateDetails) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $delegateDetails of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
introduced by
$delegateDetails is a non-empty array, thus is always true.
Loading history...
3302
					list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $delegateDetails;
3303
					$sentprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3304
					$sentprops[PR_SENDER_NAME] = $ownername;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3305
					$sentprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3306
					$sentprops[PR_SENDER_ENTRYID] = $ownerentryid;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3307
					$sentprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3308
				}
3309
			}
3310
		}
3311
		else {
3312
			// normal user is sending mail, so both set of properties will be same
3313
			$userDetails = $this->getOwnerAddress($userStore);
3314
3315
			if ($userDetails) {
0 ignored issues
show
introduced by
$userDetails is a non-empty array, thus is always true.
Loading history...
Bug Best Practice introduced by
The expression $userDetails of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
3316
				list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $userDetails;
3317
				$sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
3318
				$sentprops[PR_SENT_REPRESENTING_NAME] = $ownername;
3319
				$sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
3320
				$sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
3321
				$sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
3322
3323
				$sentprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
3324
				$sentprops[PR_SENDER_NAME] = $ownername;
3325
				$sentprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
3326
				$sentprops[PR_SENDER_ENTRYID] = $ownerentryid;
3327
				$sentprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
3328
			}
3329
		}
3330
3331
		$sentprops[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID($userStore);
0 ignored issues
show
Bug introduced by
The constant PR_SENTMAIL_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3332
3333
		mapi_setprops($outgoing, $sentprops);
3334
3335
		return $outgoing;
3336
	}
3337
3338
	/**
3339
	 * Function which checks that meeting in attendee's calendar is already updated
3340
	 * and we are checking an old meeting request. This function also will update property
3341
	 * meetingtype to indicate that its out of date meeting request.
3342
	 *
3343
	 * @return bool true if meeting request is outofdate else false if it is new
3344
	 */
3345
	public function isMeetingOutOfDate() {
3346
		$result = false;
3347
3348
		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], $this->proptags['updatecounter'], $this->proptags['meetingtype'], $this->proptags['owner_critical_change']]);
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3349
3350
		if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS])) {
3351
			return $result;
3352
		}
3353
3354
		if (isset($props[$this->proptags['meetingtype']]) && ($props[$this->proptags['meetingtype']] & mtgOutOfDate) == mtgOutOfDate) {
3355
			return true;
3356
		}
3357
3358
		// get the basedate to check for exception
3359
		$basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]);
3360
3361
		$calendarItem = $this->getCorrespondentCalendarItem(true);
3362
3363
		// if basedate is provided and we could not find the item then it could be that we are checking
3364
		// an exception so get the exception and check it
3365
		if ($basedate && $calendarItem !== false) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $basedate of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
3366
			$exception = $this->getExceptionItem($calendarItem, $basedate);
3367
3368
			if ($exception !== false) {
0 ignored issues
show
introduced by
The condition $exception !== false is always true.
Loading history...
3369
				// we are able to find the exception compare with it
3370
				$calendarItem = $exception;
3371
			}
3372
			// we are not able to find exception, could mean that a significant change has occurred on series
3373
				// and it deleted all exceptions, so compare with series
3374
				// $calendarItem already contains reference to series
3375
		}
3376
3377
		if ($calendarItem !== false) {
3378
			$calendarItemProps = mapi_getprops($calendarItem, [
3379
				$this->proptags['owner_critical_change'],
3380
				$this->proptags['updatecounter'],
3381
			]);
3382
3383
			$updateCounter = (isset($calendarItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]);
3384
3385
			$criticalChange = (isset($calendarItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $calendarItemProps[$this->proptags['owner_critical_change']]);
3386
3387
			if ($updateCounter || $criticalChange) {
3388
				// meeting request is out of date, set properties to indicate this
3389
				mapi_setprops($this->message, [$this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033]);
0 ignored issues
show
Bug introduced by
The constant PR_ICON_INDEX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3390
				mapi_savechanges($this->message);
3391
3392
				$result = true;
3393
			}
3394
		}
3395
3396
		return $result;
3397
	}
3398
3399
	/**
3400
	 * Function which checks that if we have received a meeting response for an updated meeting in organizer's calendar.
3401
	 *
3402
	 * @param mixed $basedate basedate of the exception if we want to compare with exception
3403
	 *
3404
	 * @return bool true if meeting request is updated later
3405
	 */
3406
	public function isMeetingUpdated($basedate = false) {
3407
		$result = false;
3408
3409
		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['updatecounter']]);
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3410
3411
		if (!$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS])) {
3412
			return $result;
3413
		}
3414
3415
		$calendarItem = $this->getCorrespondentCalendarItem(true);
3416
3417
		if ($calendarItem !== false) {
3418
			// basedate is provided so open exception
3419
			if ($basedate !== false) {
3420
				$exception = $this->getExceptionItem($calendarItem, $basedate);
3421
3422
				if ($exception !== false) {
0 ignored issues
show
introduced by
The condition $exception !== false is always true.
Loading history...
3423
					// we are able to find the exception compare with it
3424
					$calendarItem = $exception;
3425
				}
3426
				// we are not able to find exception, could mean that a significant change has occurred on series
3427
					// and it deleted all exceptions, so compare with series
3428
					// $calendarItem already contains reference to series
3429
			}
3430
3431
			if ($calendarItem !== false) {
0 ignored issues
show
introduced by
The condition $calendarItem !== false is always true.
Loading history...
3432
				$calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['updatecounter']]);
3433
3434
				/*
3435
				 * if(message_counter < appointment_counter) meeting object is newer then meeting response (meeting is updated)
3436
				 * if(message_counter >= appointment_counter) meeting is not updated, do normal processing
3437
				 */
3438
				if (isset($calendarItemProps[$this->proptags['updatecounter']], $props[$this->proptags['updatecounter']])) {
3439
					if ($props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]) {
3440
						$result = true;
3441
					}
3442
				}
3443
			}
3444
		}
3445
3446
		return $result;
3447
	}
3448
3449
	/**
3450
	 * Checks if there has been any significant changes on appointment/meeting item.
3451
	 * Significant changes be:
3452
	 * 1) startdate has been changed
3453
	 * 2) duedate has been changed OR
3454
	 * 3) recurrence pattern has been created, modified or removed.
3455
	 *
3456
	 * @param mixed $oldProps
3457
	 * @param mixed $basedate
3458
	 * @param mixed $isRecurrenceChanged for change in recurrence pattern.
3459
	 *                                   true means Recurrence pattern has been changed,
3460
	 *                                   so clear all attendees response
3461
	 */
3462
	public function checkSignificantChanges($oldProps, $basedate, $isRecurrenceChanged = false) {
3463
		$message = null;
3464
		$attach = null;
3465
3466
		// If basedate is specified then we need to open exception message to clear recipient responses
3467
		if ($basedate) {
3468
			$recurrence = new Recurrence($this->store, $this->message);
3469
			if ($recurrence->isException($basedate)) {
3470
				$attach = $recurrence->getExceptionAttachment($basedate);
3471
				if ($attach) {
3472
					$message = mapi_attach_openobj($attach, MAPI_MODIFY);
3473
				}
3474
			}
3475
		}
3476
		else {
3477
			// use normal message or recurring series message
3478
			$message = $this->message;
3479
		}
3480
3481
		if (!$message) {
3482
			return;
3483
		}
3484
3485
		$newProps = mapi_getprops($message, [$this->proptags['startdate'], $this->proptags['duedate'], $this->proptags['updatecounter']]);
3486
3487
		// Check whether message is updated or not.
3488
		if (isset($newProps[$this->proptags['updatecounter']]) && $newProps[$this->proptags['updatecounter']] == 0) {
3489
			return;
3490
		}
3491
3492
		if (($newProps[$this->proptags['startdate']] != $oldProps[$this->proptags['startdate']]) ||
3493
			($newProps[$this->proptags['duedate']] != $oldProps[$this->proptags['duedate']]) ||
3494
			$isRecurrenceChanged) {
3495
				$this->clearRecipientResponse($message);
3496
3497
				mapi_setprops($message, [$this->proptags['owner_critical_change'] => time()]);
3498
3499
				mapi_savechanges($message);
3500
				if ($attach) { // Also save attachment Object.
3501
					mapi_savechanges($attach);
3502
				}
3503
		}
3504
	}
3505
3506
	/**
3507
	 * Clear responses of all attendees who have replied in past.
3508
	 *
3509
	 * @param resource $message on which responses should be cleared
3510
	 */
3511
	public function clearRecipientResponse($message) {
3512
		$recipTable = mapi_message_getrecipienttable($message);
3513
		$recipsRows = mapi_table_queryallrows($recipTable, $this->recipprops);
3514
3515
		foreach ($recipsRows as $recipient) {
3516
			if (($recipient[PR_RECIPIENT_FLAGS] & recipOrganizer) != recipOrganizer) {
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3517
				// Recipient is attendee, set the trackstatus to 'Not Responded'
3518
				$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TRACKSTATUS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3519
			}
3520
			else {
3521
				// Recipient is organizer, this is not possible, but for safety
3522
				// it is best to clear the trackstatus for him as well by setting
3523
				// the trackstatus to 'Organized'.
3524
				$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3525
			}
3526
			mapi_message_modifyrecipients($message, MODRECIP_MODIFY, [$recipient]);
3527
		}
3528
	}
3529
3530
	/**
3531
	 * Function returns correspondent calendar item attached with the meeting request/response/cancellation.
3532
	 * This will only check for actual MAPIMessages in calendar folder, so if a meeting request is
3533
	 * for exception then this function will return recurring series for that meeting request
3534
	 * after that you need to use getExceptionItem function to get exception item that will be
3535
	 * fetched from the attachment table of recurring series MAPIMessage.
3536
	 *
3537
	 * @param bool $open boolean to indicate the function should return entryid or MAPIMessage. Defaults to true.
3538
	 *
3539
	 * @return resource|bool resource of calendar item
3540
	 */
3541
	public function getCorrespondentCalendarItem($open = true) {
3542
		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3543
3544
		if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS]) && !$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS]) && !$this->isMeetingCancellation($props[PR_MESSAGE_CLASS])) {
3545
			// can work only with meeting requests/responses/cancellations
3546
			return false;
3547
		}
3548
3549
		$globalId = $props[$this->proptags['goid']];
3550
		$cleanGlobalId = $props[$this->proptags['goid2']];
3551
3552
		// If Delegate is processing Meeting Request/Response for Delegator then retrieve Delegator's store and calendar.
3553
		if (isset($props[PR_RCVD_REPRESENTING_ENTRYID])) {
3554
			$delegatorStore = $this->getDelegatorStore($props[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_APPOINTMENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3555
3556
			$store = $delegatorStore['store'];
3557
			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
3558
		}
3559
		else {
3560
			$store = $this->store;
3561
			$calFolder = $this->openDefaultCalendar();
3562
		}
3563
3564
		$basedate = $this->getBasedateFromGlobalID($globalId);
3565
3566
		/**
3567
		 * First search for any appointments which correspond to the $globalId,
3568
		 * this can be the entire series (if the Meeting Request refers to the
3569
		 * entire series), or an particular Occurrence (if the meeting Request
3570
		 * contains a basedate).
3571
		 *
3572
		 * If we cannot find a corresponding item, and the $globalId contains
3573
		 * a $basedate, it might imply that a new exception will have to be
3574
		 * created for a series which is present in the calendar, we can look
3575
		 * that one up by searching for the $cleanGlobalId.
3576
		 */
3577
		$entryids = $this->findCalendarItems($globalId, $calFolder);
3578
		if ($basedate && empty($entryids)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $basedate of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
3579
			$entryids = $this->findCalendarItems($cleanGlobalId, $calFolder, true);
3580
		}
3581
3582
		// there should be only one item returned
3583
		if (!empty($entryids) && count($entryids) === 1) {
3584
			// return only entryid
3585
			if ($open === false) {
3586
				return $entryids[0];
3587
			}
3588
3589
			// open calendar item and return it
3590
			return mapi_msgstore_openentry($store, $entryids[0]);
3591
		}
3592
3593
		// no items found in calendar
3594
		return false;
3595
	}
3596
3597
	/**
3598
	 * Function returns exception item based on the basedate passed.
3599
	 *
3600
	 * @param mixed $recurringMessage Resource of Recurring meeting from calendar
3601
	 * @param mixed $basedate         basedate of exception that needs to be returned
3602
	 * @param mixed $store            store that contains the recurring calendar item
3603
	 *
3604
	 * @return entryid or MAPIMessage resource of exception item
0 ignored issues
show
Bug introduced by
The type entryid was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
3605
	 */
3606
	public function getExceptionItem($recurringMessage, $basedate, $store = false) {
3607
		$occurItem = false;
3608
3609
		$props = mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID, $this->proptags['recurring']]);
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3610
3611
		// check if the passed item is recurring series
3612
		if (isset($props[$this->proptags['recurring']]) && $props[$this->proptags['recurring']] !== false) {
3613
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type entryid.
Loading history...
3614
		}
3615
3616
		if ($store === false) {
3617
			// If Delegate is processing Meeting Request/Response for Delegator then retrieve Delegator's store and calendar.
3618
			if (isset($props[PR_RCVD_REPRESENTING_ENTRYID])) {
3619
				$delegatorStore = $this->getDelegatorStore($props[PR_RCVD_REPRESENTING_ENTRYID]);
3620
				$store = $delegatorStore['store'];
3621
			}
3622
			else {
3623
				$store = $this->store;
3624
			}
3625
		}
3626
3627
		$recurr = new Recurrence($store, $recurringMessage);
3628
		$attach = $recurr->getExceptionAttachment($basedate);
3629
		if ($attach) {
3630
			$occurItem = mapi_attach_openobj($attach);
3631
		}
3632
3633
		return $occurItem;
3634
	}
3635
3636
	/**
3637
	 * Function which checks whether received meeting request is either conflicting with other appointments or not.
3638
	 *
3639
	 * @param mixed $message   meeting request item that should be checked for conflicts in calendar
3640
	 * @param mixed $userStore store containing calendar folder that will be used for confilict checking
3641
	 * @param mixed $calFolder calendar folder for conflict checking
3642
	 *
3643
	 * @return mixed(boolean/integer) true if normal meeting is conflicting or an integer which specifies no of instances
3644
	 * conflict of recurring meeting and false if meeting is not conflicting
3645
	 * @return mixed if boolean then true/false for indicating conflict, if number then items that are conflicting with the message
3646
	 */
3647
	public function isMeetingConflicting($message = false, $userStore = false, $calFolder = false) {
3648
		$returnValue = false;
3649
		$noOfInstances = 0;
3650
3651
		if ($message === false) {
3652
			$message = $this->message;
3653
		}
3654
3655
		$messageProps = mapi_getprops(
3656
			$message,
3657
			[
3658
				PR_MESSAGE_CLASS,
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3659
				$this->proptags['goid'],
3660
				$this->proptags['goid2'],
3661
				$this->proptags['startdate'],
3662
				$this->proptags['duedate'],
3663
				$this->proptags['recurring'],
3664
				$this->proptags['clipstart'],
3665
				$this->proptags['clipend'],
3666
				PR_RCVD_REPRESENTING_ENTRYID,
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3667
				$this->proptags['basedate'],
3668
				PR_RCVD_REPRESENTING_NAME,
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3669
			]
3670
		);
3671
3672
		if ($userStore === false) {
3673
			$userStore = $this->store;
3674
3675
			// check if delegate is processing the response
3676
			if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) {
3677
				$delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_APPOINTMENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3678
3679
				$userStore = $delegatorStore['store'];
3680
				$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
3681
			}
3682
		}
3683
3684
		if ($calFolder === false) {
3685
			$calFolder = $this->openDefaultCalendar($userStore);
3686
		}
3687
3688
		if ($calFolder) {
3689
			// Meeting request is recurring, so get all occurrence and check for each occurrence whether it conflicts with other appointments in Calendar.
3690
			if (isset($messageProps[$this->proptags['recurring']]) && $messageProps[$this->proptags['recurring']] === true) {
3691
				// Apply recurrence class and retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date')
3692
				$recurr = new Recurrence($userStore, $message);
3693
				$items = $recurr->getItems($messageProps[$this->proptags['clipstart']], $messageProps[$this->proptags['clipend']] * (24 * 24 * 60), 30);
3694
3695
				foreach ($items as $item) {
3696
					// Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
3697
					$calendarItems = $recurr->getCalendarItems($userStore, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus']]);
3698
3699
					foreach ($calendarItems as $calendarItem) {
3700
						if ($calendarItem[$this->proptags['busystatus']] !== fbFree) {
3701
							/*
3702
							 * Only meeting requests have globalID, normal appointments do not have globalID
3703
							 * so if any normal appointment if found then it is assumed to be conflict.
3704
							 */
3705
							if (isset($calendarItem[$this->proptags['goid']])) {
3706
								if ($calendarItem[$this->proptags['goid']] !== $messageProps[$this->proptags['goid']]) {
3707
									++$noOfInstances;
3708
									break;
3709
								}
3710
							}
3711
							else {
3712
								++$noOfInstances;
3713
								break;
3714
							}
3715
						}
3716
					}
3717
				}
3718
3719
				if ($noOfInstances > 0) {
3720
					$returnValue = $noOfInstances;
3721
				}
3722
			}
3723
			else {
3724
				// Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
3725
				$items = getCalendarItems($userStore, $calFolder, $messageProps[$this->proptags['startdate']], $messageProps[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus']]);
3726
3727
				if (isset($messageProps[$this->proptags['basedate']]) && !empty($messageProps[$this->proptags['basedate']])) {
3728
					$basedate = $messageProps[$this->proptags['basedate']];
3729
					// Get the goid2 from recurring MR which further used to
3730
					// check the resource conflicts item.
3731
					$recurrItemProps = mapi_getprops($this->message, [$this->proptags['goid2']]);
3732
					$messageProps[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid2']], $basedate);
3733
					$messageProps[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']];
3734
				}
3735
3736
				foreach ($items as $item) {
3737
					if ($item[$this->proptags['busystatus']] !== fbFree) {
3738
						if (isset($item[$this->proptags['goid']])) {
3739
							if (($item[$this->proptags['goid']] !== $messageProps[$this->proptags['goid']]) &&
3740
								($item[$this->proptags['goid']] !== $messageProps[$this->proptags['goid2']])) {
3741
								$returnValue = true;
3742
								break;
3743
							}
3744
						}
3745
						else {
3746
							$returnValue = true;
3747
							break;
3748
						}
3749
					}
3750
				}
3751
			}
3752
		}
3753
3754
		return $returnValue;
3755
	}
3756
3757
	/**
3758
	 *  Function which adds organizer to recipient list which is passed.
3759
	 *  This function also checks if it has organizer.
3760
	 *
3761
	 * @param array $messageProps message properties
3762
	 * @param array $recipients   recipients list of message
3763
	 */
3764
	public function addDelegator($messageProps, &$recipients) {
3765
		$hasDelegator = false;
3766
		// Check if meeting already has an organizer.
3767
		foreach ($recipients as $key => $recipient) {
3768
			if (isset($messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) && $recipient[PR_EMAIL_ADDRESS] == $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) {
0 ignored issues
show
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_RCVD_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3769
				$hasDelegator = true;
3770
			}
3771
		}
3772
3773
		if (!$hasDelegator) {
3774
			// Create delegator.
3775
			$delegator = [];
3776
			$delegator[PR_ENTRYID] = $messageProps[PR_RCVD_REPRESENTING_ENTRYID];
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3777
			$delegator[PR_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3778
			$delegator[PR_EMAIL_ADDRESS] = $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS];
3779
			$delegator[PR_RECIPIENT_TYPE] = MAPI_TO;
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3780
			$delegator[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3781
			$delegator[PR_ADDRTYPE] = empty($messageProps[PR_RCVD_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_RCVD_REPRESENTING_ADDRTYPE];
0 ignored issues
show
Bug introduced by
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_RCVD_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3782
			$delegator[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TRACKSTATUS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3783
			$delegator[PR_RECIPIENT_FLAGS] = recipSendable;
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3784
			$delegator[PR_SEARCH_KEY] = $messageProps[PR_RCVD_REPRESENTING_SEARCH_KEY];
0 ignored issues
show
Bug introduced by
The constant PR_RCVD_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3785
3786
			// Add organizer to recipients list.
3787
			array_unshift($recipients, $delegator);
3788
		}
3789
	}
3790
3791
	/**
3792
	 * Function will return delegator's store and calendar folder for processing meetings.
3793
	 *
3794
	 * @param string $receivedRepresentingEntryId entryid of the delegator user
3795
	 * @param array  $foldersToOpen               contains list of folder types that should be returned in result
3796
	 *
3797
	 * @return array contains store of the delegator and resource of folders if $foldersToOpen is not empty
3798
	 */
3799
	public function getDelegatorStore($receivedRepresentingEntryId, $foldersToOpen = []) {
3800
		$returnData = [];
3801
3802
		$delegatorStore = $this->openCustomUserStore($receivedRepresentingEntryId);
3803
		$returnData['store'] = $delegatorStore;
3804
3805
		if (!empty($foldersToOpen)) {
3806
			for ($index = 0, $len = count($foldersToOpen); $index < $len; ++$index) {
3807
				$folderType = $foldersToOpen[$index];
3808
3809
				// first try with default folders
3810
				$folder = $this->openDefaultFolder($folderType, $delegatorStore);
3811
3812
				// if folder not found then try with base folders
3813
				if ($folder === false) {
3814
					$folder = $this->openBaseFolder($folderType, $delegatorStore);
3815
				}
3816
3817
				if ($folder === false) {
3818
					// we are still not able to get the folder so give up
3819
					continue;
3820
				}
3821
3822
				$returnData[$folderType] = $folder;
3823
			}
3824
		}
3825
3826
		return $returnData;
3827
	}
3828
3829
	/**
3830
	 * Function returns extra info about meeting timing along with message body
3831
	 * which will be included in body while sending meeting request/response.
3832
	 *
3833
	 * @return string|bool $meetingTimeInfo info about meeting timing along with message body
3834
	 */
3835
	public function getMeetingTimeInfo() {
3836
		return $this->meetingTimeInfo;
3837
	}
3838
3839
	/**
3840
	 * Function sets extra info about meeting timing along with message body
3841
	 * which will be included in body while sending meeting request/response.
3842
	 *
3843
	 * @param string $meetingTimeInfo info about meeting timing along with message body
3844
	 */
3845
	public function setMeetingTimeInfo($meetingTimeInfo) {
3846
		$this->meetingTimeInfo = $meetingTimeInfo;
3847
	}
3848
3849
	/**
3850
	 * Helper function which is use to get local categories of all occurrence.
3851
	 *
3852
	 * @param mixed $calendarItem meeting request item
3853
	 * @param mixed $store        store containing calendar folder
3854
	 * @param mixed $calFolder    calendar folder
3855
	 *
3856
	 * @return array $localCategories which contain array of basedate along with categories
3857
	 */
3858
	public function getLocalCategories($calendarItem, $store, $calFolder) {
3859
		$calendarItemProps = mapi_getprops($calendarItem);
3860
		$recurrence = new Recurrence($store, $calendarItem);
3861
3862
		// Retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date')
3863
		$items = $recurrence->getItems($calendarItemProps[$this->proptags['clipstart']], $calendarItemProps[$this->proptags['clipend']] * (24 * 24 * 60), 30);
3864
		$localCategories = [];
3865
3866
		foreach ($items as $item) {
3867
			$recurrenceItems = $recurrence->getCalendarItems($store, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus'], $this->proptags['categories']]);
3868
			foreach ($recurrenceItems as $recurrenceItem) {
3869
				// Check if occurrence is exception then get the local categories of that occurrence.
3870
				if (isset($recurrenceItem[$this->proptags['goid']]) && $recurrenceItem[$this->proptags['goid']] == $calendarItemProps[$this->proptags['goid']]) {
3871
					$exceptionAttach = $recurrence->getExceptionAttachment($recurrenceItem['basedate']);
3872
3873
					if ($exceptionAttach) {
3874
						$exception = mapi_attach_openobj($exceptionAttach, 0);
3875
						$exceptionProps = mapi_getprops($exception, [$this->proptags['categories']]);
3876
						if (isset($exceptionProps[$this->proptags['categories']])) {
3877
							$localCategories[$recurrenceItem['basedate']] = $exceptionProps[$this->proptags['categories']];
3878
						}
3879
					}
3880
				}
3881
			}
3882
		}
3883
3884
		return $localCategories;
3885
	}
3886
3887
	/**
3888
	 * Helper function which is use to apply local categories on respective occurrences.
3889
	 *
3890
	 * @param mixed $calendarItem    meeting request item
3891
	 * @param mixed $store           store containing calendar folder
3892
	 * @param array $localCategories array contains basedate and array of categories
3893
	 */
3894
	public function applyLocalCategories($calendarItem, $store, $localCategories) {
3895
		$calendarItemProps = mapi_getprops($calendarItem, [PR_PARENT_ENTRYID, PR_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_PARENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3896
		$message = mapi_msgstore_openentry($store, $calendarItemProps[PR_ENTRYID]);
3897
		$recurrence = new Recurrence($store, $message);
3898
3899
		// Check for all occurrence if it is exception then modify the exception by setting up categories,
3900
		// Otherwise create new exception with categories.
3901
		foreach ($localCategories as $key => $value) {
3902
			if ($recurrence->isException($key)) {
3903
				$recurrence->modifyException([$this->proptags['categories'] => $value], $key);
3904
			}
3905
			else {
3906
				$recurrence->createException([$this->proptags['categories'] => $value], $key, false);
3907
			}
3908
			mapi_savechanges($message);
3909
		}
3910
	}
3911
}
3912