Test Failed
Push — master ( 647c72...cd42b5 )
by
unknown
10:25
created

Meetingrequest::replaceAttachments()   B

Complexity

Conditions 11
Paths 8

Size

Total Lines 31
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 18
nc 8
nop 3
dl 0
loc 31
rs 7.3166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
322
	 * @param boolean $basedate if present the create an exception
323
	 * @param array $messageprops contains message properties.
324
	 */
325
	function processResponse($store, $calendarItem, $basedate, $messageprops)
326
	{
327
		$senderentryid = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
328
		$messageclass = $messageprops[PR_MESSAGE_CLASS];
329
		$deliverytime = $messageprops[PR_MESSAGE_DELIVERY_TIME];
330
331
		// Open the calendar item, find the sender in the recipient table and update all the recipients of the calendar item that match
332
		// the email address of the response.
333
		$calendarItemProps = mapi_getprops($calendarItem, array($this->proptags['recurring'], PR_STORE_ENTRYID, PR_PARENT_ENTRYID, PR_ENTRYID, $this->proptags['updatecounter']));
334
335
		// check if meeting response is already processed
336
		if(isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
337
			// meeting is already processed
338
			return;
339
		} else {
340
			mapi_setprops($this->message, Array(PR_PROCESSED => true));
341
			mapi_savechanges($this->message);
342
		}
343
344
		// if meeting is updated in organizer's calendar then we don't need to process
345
		// old response
346
		if($this->isMeetingUpdated($basedate)) {
0 ignored issues
show
Bug introduced by
$basedate of type boolean is incompatible with the type double|integer expected by parameter $basedate of Meetingrequest::isMeetingUpdated(). ( Ignorable by Annotation )

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

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

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

352
			$recurr = new Recurrence($store, /** @scrutinizer ignore-type */ $calendarItem);
Loading history...
353
354
			// Copy properties from meeting request
355
			$exception_props = mapi_getprops($this->message, array(
356
												PR_OWNER_APPT_ID,
357
												$this->proptags['proposed_start_whole'],
358
												$this->proptags['proposed_end_whole'],
359
												$this->proptags['proposed_duration'],
360
												$this->proptags['counter_proposal']
361
											));
362
363
			// Create/modify exception
364
			if($recurr->isException($basedate)) {
365
				$recurr->modifyException($exception_props, $basedate);
366
			} else {
367
				// When we are creating an exception we need copy recipients from main recurring item
368
				$recipTable =  mapi_message_getrecipienttable($calendarItem);
369
				$recips = mapi_table_queryallrows($recipTable, $this->recipprops);
370
371
				// Retrieve actual start/due dates from calendar item.
372
				$exception_props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
373
				$exception_props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
374
375
				$recurr->createException($exception_props, $basedate, false, $recips);
0 ignored issues
show
Bug introduced by
$basedate of type true is incompatible with the type date expected by parameter $base_date of Recurrence::createException(). ( Ignorable by Annotation )

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

375
				$recurr->createException($exception_props, /** @scrutinizer ignore-type */ $basedate, false, $recips);
Loading history...
376
			}
377
378
			mapi_savechanges($calendarItem);
379
380
			$attach = $recurr->getExceptionAttachment($basedate);
381
			if ($attach) {
382
				$recurringItem = $calendarItem;
383
				$calendarItem = mapi_attach_openobj($attach, MAPI_MODIFY);
384
			} else {
385
				return false;
386
			}
387
		}
388
389
		// Get the recipients of the calendar item
390
		$reciptable = mapi_message_getrecipienttable($calendarItem);
391
		$recipients = mapi_table_queryallrows($reciptable, $this->recipprops);
392
393
		// FIXME we should look at the updatecounter property and compare it
394
		// to the counter in the recipient to see if this update is actually
395
		// newer than the status in the calendar item
396
		$found = false;
397
398
		$totalrecips = 0;
399
		$acceptedrecips = 0;
400
		foreach($recipients as $recipient) {
401
			$totalrecips++;
402
			if(isset($recipient[PR_ENTRYID]) && $this->compareABEntryIDs($recipient[PR_ENTRYID], $senderentryid)) {
403
				$found = true;
404
405
				/**
406
				 * If value of attendee_critical_change on meeting response mail is less than PR_RECIPIENT_TRACKSTATUS_TIME
407
				 * on the corresponding recipientRow of meeting then we ignore this response mail.
408
				 */
409
				if (isset($recipient[PR_RECIPIENT_TRACKSTATUS_TIME]) && ($messageprops[$this->proptags['attendee_critical_change']] < $recipient[PR_RECIPIENT_TRACKSTATUS_TIME])) {
410
					continue;
411
				}
412
413
				// The email address matches, update the row
414
				$recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
415
				$recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $messageprops[$this->proptags['attendee_critical_change']];
416
417
				// If this is a counter proposal, set the proposal properties in the recipient row
418
				if(isset($messageprops[$this->proptags['counter_proposal']]) && $messageprops[$this->proptags['counter_proposal']]){
419
					$recipient[PR_PROPOSENEWTIME_START] = $messageprops[$this->proptags['proposed_start_whole']];
420
					$recipient[PR_PROPOSENEWTIME_END] = $messageprops[$this->proptags['proposed_end_whole']];
421
					$recipient[PR_PROPOSEDNEWTIME] = $messageprops[$this->proptags['counter_proposal']];
422
				}
423
424
				mapi_message_modifyrecipients($calendarItem, MODRECIP_MODIFY, Array($recipient));
425
			}
426
			if(isset($recipient[PR_RECIPIENT_TRACKSTATUS]) && $recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted)
427
				$acceptedrecips++;
428
		}
429
430
		// If the recipient was not found in the original calendar item,
431
		// then add the recpient as a new optional recipient
432
		if(!$found) {
433
			$recipient = Array();
434
			$recipient[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
435
			$recipient[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
436
			$recipient[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
437
			$recipient[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
438
			$recipient[PR_RECIPIENT_TYPE] = MAPI_CC;
439
			$recipient[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
440
			$recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
441
			$recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $deliverytime;
442
443
			// If this is a counter proposal, set the proposal properties in the recipient row
444
			if(isset($messageprops[$this->proptags['counter_proposal']])){
445
				$recipient[PR_PROPOSENEWTIME_START] = $messageprops[$this->proptags['proposed_start_whole']];
446
				$recipient[PR_PROPOSENEWTIME_END] = $messageprops[$this->proptags['proposed_end_whole']];
447
				$recipient[PR_PROPOSEDNEWTIME] = $messageprops[$this->proptags['counter_proposal']];
448
			}
449
450
			mapi_message_modifyrecipients($calendarItem, MODRECIP_ADD, Array($recipient));
451
			$totalrecips++;
452
			if($recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted) {
453
				$acceptedrecips++;
454
			}
455
		}
456
457
//TODO: Update counter proposal number property on message
458
/*
459
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.
460
*/
461
		// If this is a counter proposal, set the counter proposal indicator boolean
462
		if(isset($messageprops[$this->proptags['counter_proposal']])){
463
			$props = Array();
464
			if($messageprops[$this->proptags['counter_proposal']]){
465
				$props[$this->proptags['counter_proposal']] = true;
466
			} else {
467
				$props[$this->proptags['counter_proposal']] = false;
468
			}
469
470
			mapi_setprops($calendarItem, $props);
471
		}
472
473
		mapi_savechanges($calendarItem);
474
		if (isset($attach)) {
475
			mapi_savechanges($attach);
476
			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...
477
		}
478
	}
479
480
	/**
481
	 * Process an incoming meeting request cancellation. This updates the
482
	 * appointment in your calendar to show that the meeting has been cancelled.
483
	 */
484
	function processMeetingCancellation()
485
	{
486
		if(!$this->isMeetingCancellation()) {
487
			return;
488
		}
489
490
		if($this->isLocalOrganiser()) {
491
			return;
492
		}
493
494
		if(!$this->isInCalendar()) {
495
			return;
496
		}
497
498
		$listProperties = $this->proptags;
499
		$listProperties['subject'] = PR_SUBJECT;
500
		$listProperties['sent_representing_name'] = PR_SENT_REPRESENTING_NAME;
501
		$listProperties['sent_representing_address_type'] = PR_SENT_REPRESENTING_ADDRTYPE;
502
		$listProperties['sent_representing_email_address'] = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
503
		$listProperties['sent_representing_entryid'] = PR_SENT_REPRESENTING_ENTRYID;
504
		$listProperties['sent_representing_search_key'] = PR_SENT_REPRESENTING_SEARCH_KEY;
505
		$listProperties['rcvd_representing_name'] = PR_RCVD_REPRESENTING_NAME;
506
		$listProperties['rcvd_representing_address_type'] = PR_RCVD_REPRESENTING_ADDRTYPE;
507
		$listProperties['rcvd_representing_email_address'] = PR_RCVD_REPRESENTING_EMAIL_ADDRESS;
508
		$listProperties['rcvd_representing_entryid'] = PR_RCVD_REPRESENTING_ENTRYID;
509
		$listProperties['rcvd_representing_search_key'] = PR_RCVD_REPRESENTING_SEARCH_KEY;
510
		$messageProps = mapi_getprops($this->message, $listProperties);
511
512
		$goid = $messageProps[$this->proptags['goid']];	//GlobalID (0x3)
513
		if(!isset($goid)) {
514
			return;
515
		}
516
517
		// get delegator store, if delegate is processing this cancellation
518
		if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])){
519
			$delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID], array(PR_IPM_APPOINTMENT_ENTRYID));
520
521
			$store = $delegatorStore['store'];
522
			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
0 ignored issues
show
Unused Code introduced by
The assignment to $calFolder is dead and can be removed.
Loading history...
523
		} else {
524
			$store = $this->store;
525
			$calFolder = $this->openDefaultCalendar();
526
		}
527
528
		// check for calendar access
529
		if($this->checkCalendarWriteAccess($store) !== true) {
530
			// Throw an exception that we don't have write permissions on calendar folder,
531
			// allow caller to fill the error message
532
			throw new MAPIException(null, MAPI_E_NO_ACCESS);
533
		}
534
535
		$calendarItem = $this->getCorrespondentCalendarItem(true);
536
		$basedate = $this->getBasedateFromGlobalID($goid);
537
538
		if($calendarItem !== false) {
0 ignored issues
show
introduced by
The condition $calendarItem !== false is always true.
Loading history...
539
			// if basedate is provided and we could not find the item then it could be that we are processing
540
			// an exception so get the exception and process it
541
			if($basedate) {
542
				$calendarItemProps = mapi_getprops($calendarItem, array($this->proptags['recurring']));
543
				if ($calendarItemProps[$this->proptags['recurring']] === true){
544
					$recurr = new Recurrence($store, $calendarItem);
0 ignored issues
show
Bug introduced by
$calendarItem of type entryid is incompatible with the type resource expected by parameter $message of Recurrence::__construct(). ( Ignorable by Annotation )

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

544
					$recurr = new Recurrence($store, /** @scrutinizer ignore-type */ $calendarItem);
Loading history...
545
546
					// Set message class
547
					$messageProps[PR_MESSAGE_CLASS] = 'IPM.Appointment';
548
549
					if($recurr->isException($basedate)) {
550
						$recurr->modifyException($messageProps, $basedate);
551
					} else {
552
						$recurr->createException($messageProps, $basedate);
0 ignored issues
show
Bug introduced by
$basedate of type true is incompatible with the type date expected by parameter $base_date of Recurrence::createException(). ( Ignorable by Annotation )

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

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

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

811
								$this->mergeException($calendarItem, $occurrenceItem, /** @scrutinizer ignore-type */ $basedate, $store);
Loading history...
812
							}
813
						}
814
					}
815
				}
816
817
				mapi_savechanges($calendarItem);
818
819
				// After applying update of organiser all local categories of occurrence was removed,
820
				// So if local categories exist then apply it on respective occurrence.
821
				if (!empty($localCategories)) {
822
					$this->applyLocalCategories($calendarItem, $store, $localCategories);
823
				}
824
825
				if ($move) {
826
					// open wastebasket of currently logged in user and move the meeting request to it
827
					// for delegates this will be delegate's wastebasket folder
828
					$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
0 ignored issues
show
Bug introduced by
It seems like $this->openDefaultStore() can also be of type false; however, parameter $store of Meetingrequest::openDefaultWastebasket() does only seem to accept MAPIStore, maybe add an additional type check? ( Ignorable by Annotation )

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

828
					$wastebasket = $this->openDefaultWastebasket(/** @scrutinizer ignore-type */ $this->openDefaultStore());
Loading history...
829
					mapi_folder_copymessages($calFolder, Array($props[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
830
				}
831
832
				$entryid = $props[PR_ENTRYID];
833
			} else {
834
				/**
835
				 * This meeting request is not recurring, so can be an exception or normal meeting.
836
				 * If exception then find main recurring item and update exception
837
				 * If main recurring item is not found then put exception into Calendar as normal meeting.
838
				 */
839
				$calendarItem = false;
840
841
				// We found basedate in GlobalID of this meeting request, so this meeting request if for an occurrence.
842
				if ($basedate) {
843
					// Find main recurring item from CleanGlobalID of this meeting request
844
					$items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
845
					if (is_array($items)) {
846
						foreach($items as $key => $entryid) {
847
							$calendarItem = mapi_msgstore_openentry($store, $entryid);
848
						}
849
					}
850
851
					// Main recurring item is found, so now update exception
852
					if ($calendarItem) {
853
						$this->acceptException($calendarItem, $this->message, $basedate, $move, $tentative, $userAction, $store, $isDelegate);
854
						$calendarItemProps = mapi_getprops($calendarItem, array(PR_ENTRYID));
855
						$entryid = $calendarItemProps[PR_ENTRYID];
856
					}
857
				}
858
859
				if (!$calendarItem) {
860
					$items = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
861
					if (is_array($items)) {
862
						// Get local categories before deleting MR.
863
						$message = mapi_msgstore_openentry($store, $items[0]);
864
						$localCategories = mapi_getprops($message, array($this->proptags['categories']));
865
						mapi_folder_deletemessages($calFolder, $items);
866
					}
867
868
					if ($move) {
869
						// All we have to do is open the default calendar,
870
						// set the message class correctly to be an appointment item
871
						// and move it to the calendar folder
872
						$sourcefolder = $this->openParentFolder();
873
874
						// create a new calendar message, and copy the message to there,
875
						// since we want to delete (move to wastebasket) the original message
876
						$old_entryid = mapi_getprops($this->message, Array(PR_ENTRYID));
877
						$calmsg = mapi_folder_createmessage($calFolder);
878
						mapi_copyto($this->message, array(), array(), $calmsg); /* includes attachments and recipients */
879
						// reset the PidLidMeetingType to Unspecified for outlook display the item
880
						$tmp_props = array();
881
						$tmp_props[$this->proptags['meetingtype']] = mtgEmpty;
882
						//OL needs this field always being set, or it will not display item
883
						$tmp_props[$this->proptags['recurring']] = false;
884
						mapi_setprops($calmsg, $tmp_props);
885
						
886
						// After creating new MR, If local categories exist then apply it on new MR.
887
						if(!empty($localCategories)) {
888
							mapi_setprops($calmsg, $localCategories);
889
						}
890
891
						$calItemProps = Array();
892
						$calItemProps[PR_MESSAGE_CLASS] = 'IPM.Appointment';
893
894
						/*
895
						 * the client which has sent this meeting request can generate wrong flagdueby
896
						 * time (mainly OL), so regenerate that property so we will always show reminder
897
						 * on right time
898
						 */
899
						if (isset($messageprops[$this->proptags['reminderminutes']])) {
900
							$calItemProps[$this->proptags['flagdueby']] = $messageprops[$this->proptags['startdate']] - ($messageprops[$this->proptags['reminderminutes']] * 60);
901
						}
902
903
						if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
904
							if($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
905
								$calItemProps[$this->proptags['busystatus']] = fbTentative;
906
							} else {
907
								$calItemProps[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
908
							}
909
							$calItemProps[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
910
						} else {
911
							$calItemProps[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
912
						}
913
914
						// when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
915
						$calItemProps[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
916
						if($userAction) {
917
							$addrInfo = $this->getOwnerAddress($this->store);
918
919
							// if user has responded then set replytime and name
920
							$calItemProps[$this->proptags['replytime']] = time();
921
							if(!empty($addrInfo)) {
922
								$calItemProps[$this->proptags['apptreplyname']] = $addrInfo[0];
923
							}
924
						}
925
926
						mapi_setprops($calmsg, $proposeNewTimeProps + $calItemProps);
927
928
						// get properties which stores owner information in meeting request mails
929
						$props = mapi_getprops($calmsg, array(PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY));
930
931
						// add owner to recipient table
932
						$recips = array();
933
						$this->addOrganizer($props, $recips);
934
						mapi_message_modifyrecipients($calmsg, MODRECIP_ADD, $recips);
935
						mapi_savechanges($calmsg);
936
937
						// Move the message to the wastebasket
938
						$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
939
						mapi_folder_copymessages($sourcefolder, array($old_entryid[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
940
941
						$messageprops = mapi_getprops($calmsg, array(PR_ENTRYID));
942
						$entryid = $messageprops[PR_ENTRYID];
943
					} else {
944
						// Create a new appointment with duplicate properties and recipient, but as an IPM.Appointment
945
						$new = mapi_folder_createmessage($calFolder);
946
						$props = mapi_getprops($this->message);
947
948
						$props[PR_MESSAGE_CLASS] = 'IPM.Appointment';
949
						// reset the PidLidMeetingType to Unspecified for outlook display the item
950
						$props[$this->proptags['meetingtype']] = mtgEmpty;
951
						//OL needs this field always being set, or it will not display item
952
						$props[$this->proptags['recurring']] = false;
953
954
						// After creating new MR, If local categories exist then apply it on new MR.
955
						if (!empty($localCategories)) {
956
							mapi_setprops($new, $localCategories);
957
						}
958
959
						/*
960
						 * the client which has sent this meeting request can generate wrong flagdueby
961
						 * time (mainly OL), so regenerate that property so we will always show reminder
962
						 * on right time
963
						 */
964
						if (isset($props[$this->proptags['reminderminutes']])) {
965
							$props[$this->proptags['flagdueby']] = $props[$this->proptags['startdate']] - ($props[$this->proptags['reminderminutes']] * 60);
966
						}
967
968
						// When meeting requests are generated by third-party solutions, we might be missing the updatecounter property.
969
						if (!isset($props[$this->proptags['updatecounter']])) {
970
							$props[$this->proptags['updatecounter']] = 0;
971
						}
972
						// when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
973
						$props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
974
975
						if (isset($props[$this->proptags['intendedbusystatus']])) {
976
							if($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
977
								$props[$this->proptags['busystatus']] = fbTentative;
978
							} else {
979
								$props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
980
							}
981
							// we already have intendedbusystatus value in $props so no need to copy it
982
						} else {
983
							$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
984
						}
985
986
						if($userAction) {
987
							$addrInfo = $this->getOwnerAddress($this->store);
988
989
							// if user has responded then set replytime and name
990
							$props[$this->proptags['replytime']] = time();
991
							if(!empty($addrInfo)) {
992
								$props[$this->proptags['apptreplyname']] = $addrInfo[0];
993
							}
994
						}
995
996
						mapi_setprops($new, $proposeNewTimeProps + $props);
997
998
						$reciptable = mapi_message_getrecipienttable($this->message);
999
1000
						$recips = array();
1001
						if(!$isDelegate)
1002
							$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
1003
1004
						$this->addOrganizer($props, $recips);
1005
						mapi_message_modifyrecipients($new, MODRECIP_ADD, $recips);
1006
						mapi_savechanges($new);
1007
1008
						$props = mapi_getprops($new, array(PR_ENTRYID));
1009
						$entryid = $props[PR_ENTRYID];
1010
					}
1011
				}
1012
			}
1013
		} else {
1014
			// Here only properties are set on calendaritem, because user is responding from calendar.
1015
			$props = array();
1016
			$props[$this->proptags['responsestatus']] = $tentative ? olResponseTentative : olResponseAccepted;
1017
1018
			if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
1019
				if($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
1020
					$props[$this->proptags['busystatus']] = fbTentative;
1021
				} else {
1022
					$props[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
1023
				}
1024
				$props[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
1025
			} else {
1026
				$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
1027
			}
1028
1029
			$props[$this->proptags['meetingstatus']] = olMeetingReceived;
1030
1031
			$addrInfo = $this->getOwnerAddress($this->store);
1032
1033
			// if user has responded then set replytime and name
1034
			$props[$this->proptags['replytime']] = time();
1035
			if(!empty($addrInfo)) {
1036
				$props[$this->proptags['apptreplyname']] = $addrInfo[0];
1037
			}
1038
1039
			if ($basedate) {
1040
				$recurr = new Recurrence($store, $this->message);
1041
1042
				// Copy recipients list
1043
				$reciptable = mapi_message_getrecipienttable($this->message);
1044
				$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
1045
1046
				if($recurr->isException($basedate)) {
1047
					$recurr->modifyException($proposeNewTimeProps + $props, $basedate, $recips);
1048
				} else {
1049
					$props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
1050
					$props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
1051
1052
					$props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
1053
					$props[PR_SENT_REPRESENTING_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
1054
					$props[PR_SENT_REPRESENTING_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
1055
					$props[PR_SENT_REPRESENTING_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
1056
					$props[PR_SENT_REPRESENTING_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
1057
1058
					$recurr->createException($proposeNewTimeProps + $props, $basedate, false, $recips);
1059
				}
1060
			} else {
1061
				mapi_setprops($this->message, $proposeNewTimeProps + $props);
1062
			}
1063
			mapi_savechanges($this->message);
1064
1065
			$entryid = $messageprops[PR_ENTRYID];
1066
		}
1067
1068
		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...
1069
	}
1070
1071
	/**
1072
	 * Declines the meeting request by moving the item to the deleted
1073
	 * items folder and sending a decline message. After declining, you
1074
	 * can't use this class instance any more. The message is closed.
1075
	 * When an occurrence is decline then false is returned because that
1076
	 * occurrence is deleted not the recurring item.
1077
	 *
1078
	 * @param boolean $sendresponse true if a response has to be sent to organizer
1079
	 * @param string $basedate if specified contains starttime of day of an occurrence
1080
	 * @return boolean true if item is deleted from Calendar else false
1081
	 */
1082
	function doDecline($sendresponse, $basedate = false, $body = false)
1083
	{
1084
		if($this->isLocalOrganiser()) {
1085
			return false;
1086
		}
1087
1088
		$result = false;
1089
		$calendaritem = false;
1090
1091
		// Remove any previous calendar items with this goid and appt id
1092
		$messageprops = mapi_getprops($this->message, Array($this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_ENTRYID));
1093
1094
		// If this meeting request is received by a delegate then open delegator's store.
1095
		if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) {
1096
			$delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], array(PR_IPM_APPOINTMENT_ENTRYID));
1097
1098
			$store = $delegatorStore['store'];
1099
			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
1100
		} else {
1101
			$calFolder = $this->openDefaultCalendar();
1102
			$store = $this->store;
1103
		}
1104
1105
		// check for calendar access before deleting the calendar item
1106
		if($this->checkCalendarWriteAccess($store) !== true) {
1107
			// Throw an exception that we don't have write permissions on calendar folder,
1108
			// allow caller to fill the error message
1109
			throw new MAPIException(null, MAPI_E_NO_ACCESS);
1110
		}
1111
1112
		$goid = $messageprops[$this->proptags['goid']];
1113
1114
		// First, find the items in the calendar by GlobalObjid (0x3)
1115
		$entryids = $this->findCalendarItems($goid, $calFolder);
1116
1117
		if (!$basedate) {
1118
			$basedate = $this->getBasedateFromGlobalID($goid);
1119
		}
1120
1121
		if($sendresponse) {
1122
			$this->createResponse(olResponseDeclined, array(), $body, $store, $basedate, $calFolder);
0 ignored issues
show
Bug introduced by
$basedate of type boolean|string is incompatible with the type integer expected by parameter $basedate of Meetingrequest::createResponse(). ( Ignorable by Annotation )

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

1122
			$this->createResponse(olResponseDeclined, array(), $body, $store, /** @scrutinizer ignore-type */ $basedate, $calFolder);
Loading history...
1123
		}
1124
1125
		if ($basedate) {
1126
			// use CleanGlobalObjid (0x23)
1127
			$calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
1128
1129
			if (is_array($calendaritems)) {
1130
				foreach($calendaritems as $entryid) {
1131
					// Open each calendar item and set the properties of the cancellation object
1132
					$calendaritem = mapi_msgstore_openentry($store, $entryid);
1133
1134
					// Recurring item is found, now delete exception
1135
					if ($calendaritem) {
1136
						$this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store);
0 ignored issues
show
Bug introduced by
It seems like $basedate can also be of type true; however, parameter $basedate of Meetingrequest::doRemoveExceptionFromCalendar() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1136
						$this->doRemoveExceptionFromCalendar(/** @scrutinizer ignore-type */ $basedate, $calendaritem, $store);
Loading history...
1137
						$result = true;
1138
					}
1139
				}
1140
			}
1141
1142
			if ($this->isMeetingRequest()) {
1143
				$calendaritem = false;
1144
			}
1145
		}
1146
1147
		if (!$calendaritem) {
1148
			$calendar = $this->openDefaultCalendar($store);
1149
1150
			if(!empty($entryids)) {
1151
				mapi_folder_deletemessages($calendar, $entryids);
1152
			}
1153
1154
			// All we have to do to decline, is to move the item to the waste basket
1155
			$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
0 ignored issues
show
Bug introduced by
It seems like $this->openDefaultStore() can also be of type false; however, parameter $store of Meetingrequest::openDefaultWastebasket() does only seem to accept MAPIStore, maybe add an additional type check? ( Ignorable by Annotation )

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

1155
			$wastebasket = $this->openDefaultWastebasket(/** @scrutinizer ignore-type */ $this->openDefaultStore());
Loading history...
1156
			$sourcefolder = $this->openParentFolder();
1157
1158
			$messageprops = mapi_getprops($this->message, Array(PR_ENTRYID));
1159
1160
			// Release the message
1161
			$this->message = null;
0 ignored issues
show
Bug Best Practice introduced by
The property message does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1162
1163
			// Move the message to the waste basket
1164
			mapi_folder_copymessages($sourcefolder, Array($messageprops[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
1165
1166
			$result = true;
1167
		}
1168
1169
		return $result;
1170
	}
1171
1172
	/**
1173
	 * Removes a meeting request from the calendar when the user presses the
1174
	 * 'remove from calendar' button in response to a meeting cancellation.
1175
	 * @param string $basedate if specified contains starttime of day of an occurrence
1176
	 */
1177
	function doRemoveFromCalendar($basedate)
1178
	{
1179
		if($this->isLocalOrganiser()) {
1180
			return false;
1181
		}
1182
1183
		$messageprops = mapi_getprops($this->message, Array(PR_ENTRYID, $this->proptags['goid'], PR_RCVD_REPRESENTING_ENTRYID, PR_MESSAGE_CLASS));
1184
1185
		$goid = $messageprops[$this->proptags['goid']];
1186
1187
		if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) {
1188
			$delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], array(PR_IPM_APPOINTMENT_ENTRYID));
1189
1190
			$store = $delegatorStore['store'];
1191
			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
1192
		} else {
1193
			$store = $this->store;
1194
			$calFolder = $this->openDefaultCalendar();
1195
		}
1196
1197
		// check for calendar access before deleting the calendar item
1198
		if($this->checkCalendarWriteAccess($store) !== true) {
1199
			// Throw an exception that we don't have write permissions on calendar folder,
1200
			// allow caller to fill the error message
1201
			throw new MAPIException(null, MAPI_E_NO_ACCESS);
1202
		}
1203
1204
		$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
0 ignored issues
show
Bug introduced by
It seems like $this->openDefaultStore() can also be of type false; however, parameter $store of Meetingrequest::openDefaultWastebasket() does only seem to accept MAPIStore, maybe add an additional type check? ( Ignorable by Annotation )

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

1204
		$wastebasket = $this->openDefaultWastebasket(/** @scrutinizer ignore-type */ $this->openDefaultStore());
Loading history...
1205
		// get the source folder of the meeting message
1206
		$sourcefolder = $this->openParentFolder();
1207
1208
		// Check if the message is a meeting request in the inbox or a calendaritem by checking the message class
1209
		if ($this->isMeetingCancellation($messageprops[PR_MESSAGE_CLASS])) {
1210
			// get the basedate to check for exception
1211
			$basedate = $this->getBasedateFromGlobalID($goid);
1212
1213
			$calendarItem = $this->getCorrespondentCalendarItem(true);
1214
1215
			if($calendarItem !== false) {
0 ignored issues
show
introduced by
The condition $calendarItem !== false is always true.
Loading history...
1216
				// basedate is provided so open exception
1217
				if($basedate) {
1218
					$exception = $this->getExceptionItem($calendarItem, $basedate);
0 ignored issues
show
Bug introduced by
$basedate of type true is incompatible with the type Unixtime expected by parameter $basedate of Meetingrequest::getExceptionItem(). ( Ignorable by Annotation )

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

1218
					$exception = $this->getExceptionItem($calendarItem, /** @scrutinizer ignore-type */ $basedate);
Loading history...
1219
1220
					if($exception !== false) {
0 ignored issues
show
introduced by
The condition $exception !== false is always true.
Loading history...
1221
						// exception found, remove it from calendar
1222
						$this->doRemoveExceptionFromCalendar($basedate, $calendarItem, $store);
0 ignored issues
show
Bug introduced by
$calendarItem of type entryid is incompatible with the type resource expected by parameter $message of Meetingrequest::doRemoveExceptionFromCalendar(). ( Ignorable by Annotation )

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

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

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

1222
						$this->doRemoveExceptionFromCalendar(/** @scrutinizer ignore-type */ $basedate, $calendarItem, $store);
Loading history...
1223
					}
1224
				} else {
1225
					// remove normal / recurring series from calendar
1226
					$entryids = mapi_getprops($calendarItem, array(PR_ENTRYID));
1227
1228
					$entryids = array($entryids[PR_ENTRYID]);
1229
1230
					mapi_folder_copymessages($calFolder, $entryids, $wastebasket, MESSAGE_MOVE);
1231
				}
1232
			}
1233
1234
			// Release the message, because we are going to move it to wastebasket
1235
			$this->message = null;
0 ignored issues
show
Bug Best Practice introduced by
The property message does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1236
1237
			// Move the cancellation mail to wastebasket
1238
			mapi_folder_copymessages($sourcefolder, Array($messageprops[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
1239
		} else {
1240
			// Here only properties are set on calendaritem, because user is responding from calendar.
1241
			if ($basedate) {
1242
				// remove the occurrence
1243
				$this->doRemoveExceptionFromCalendar($basedate, $this->message, $store);
1244
			} else {
1245
				// remove normal/recurring meeting item.
1246
				// Move the message to the waste basket
1247
				mapi_folder_copymessages($sourcefolder, Array($messageprops[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
1248
			}
1249
		}
1250
	}
1251
1252
	/**
1253
	 * Function can be used to cancel any existing meeting and send cancellation mails to attendees.
1254
	 * Should only be called from meeting object from calendar.
1255
	 * @param String $basedate (optional) basedate of occurrence which should be cancelled.
1256
	 * @FIXME cancellation mail is also sent to attendee which has declined the meeting
1257
	 * @FIXME don't send canellation mail when cancelling meeting from past
1258
	 */
1259
	function doCancelInvitation($basedate = false)
1260
	{
1261
		if(!$this->isLocalOrganiser()) {
1262
			return;
1263
		}
1264
1265
		// check write access for delegate
1266
		if($this->checkCalendarWriteAccess($this->store) !== true) {
1267
			// Throw an exception that we don't have write permissions on calendar folder,
1268
			// error message will be filled by module
1269
			throw new MAPIException(null, MAPI_E_NO_ACCESS);
1270
		}
1271
1272
		$messageProps = mapi_getprops($this->message, array(PR_ENTRYID, $this->proptags['recurring']));
1273
1274
		if(isset($messageProps[$this->proptags['recurring']]) && $messageProps[$this->proptags['recurring']] === true) {
1275
			// cancellation of recurring series or one occurrence
1276
			$recurrence = new Recurrence($this->store, $this->message);
1277
1278
			// if basedate is specified then we are cancelling only one occurrence, so create exception for that occurrence
1279
			if($basedate) {
1280
				$recurrence->createException(array(), $basedate, true);
0 ignored issues
show
Bug introduced by
$basedate of type string is incompatible with the type date expected by parameter $base_date of Recurrence::createException(). ( Ignorable by Annotation )

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

1280
				$recurrence->createException(array(), /** @scrutinizer ignore-type */ $basedate, true);
Loading history...
1281
			}
1282
1283
			// update the meeting request
1284
			$this->updateMeetingRequest();
1285
1286
			// send cancellation mails
1287
			$this->sendMeetingRequest(true, dgettext('zarafa', 'Canceled') . ': ', $basedate);
1288
1289
			// save changes in the message
1290
			mapi_savechanges($this->message);
1291
		} else {
1292
			// cancellation of normal meeting request
1293
			// Send the cancellation
1294
			$this->updateMeetingRequest();
1295
			$this->sendMeetingRequest(true, dgettext('zarafa', 'Canceled') . ': ');
1296
1297
			// save changes in the message
1298
			mapi_savechanges($this->message);
1299
		}
1300
1301
		// if basedate is specified then we have already created exception of it so nothing should be done now
1302
		// but when cancelling normal / recurring meeting request we need to remove meeting from calendar
1303
		if($basedate === false) {
1304
			// get the wastebasket folder, for delegate this will give wastebasket of delegate
1305
			$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
0 ignored issues
show
Bug introduced by
It seems like $this->openDefaultStore() can also be of type false; however, parameter $store of Meetingrequest::openDefaultWastebasket() does only seem to accept MAPIStore, maybe add an additional type check? ( Ignorable by Annotation )

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

1305
			$wastebasket = $this->openDefaultWastebasket(/** @scrutinizer ignore-type */ $this->openDefaultStore());
Loading history...
1306
1307
			// get the source folder of the meeting message
1308
			$sourcefolder = $this->openParentFolder();
1309
1310
			// Move the message to the deleted items
1311
			mapi_folder_copymessages($sourcefolder, array($messageProps[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
1312
		}
1313
	}
1314
1315
	/**
1316
	 * Convert epoch to MAPI FileTime, number of 100-nanosecond units since
1317
	 * the start of January 1, 1601.
1318
	 * https://msdn.microsoft.com/en-us/library/office/cc765906.aspx
1319
	 *
1320
	 * @param integer the current epoch
1321
	 * @return the MAPI FileTime equalevent to the given epoch time
1322
	 */
1323
	function epochToMapiFileTime($epoch)
1324
	{
1325
		$nanoseconds_between_epoch = 116444736000000000;
1326
		return ($epoch * 10000000) + $nanoseconds_between_epoch;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $epoch * 10000000...noseconds_between_epoch returns the type integer which is incompatible with the documented return type the.
Loading history...
1327
	}
1328
1329
	/**
1330
	 * Sets the properties in the message so that is can be sent
1331
	 * as a meeting request. The caller has to submit the message. This
1332
	 * is only used for new MeetingRequests. Pass the appointment item as $message
1333
	 * in the constructor to do this.
1334
	 */
1335
	function setMeetingRequest($basedate = false)
1336
	{
1337
		$props = mapi_getprops($this->message, Array($this->proptags['updatecounter']));
1338
1339
		// Create a new global id for this item
1340
		// https://msdn.microsoft.com/en-us/library/ee160198(v=exchg.80).aspx
1341
		$goid = pack('H*', '040000008200E00074C5B7101A82E00800000000');
1342
		/*
1343
		$year = gmdate("Y");
1344
		$month = gmdate("n");
1345
		$day = gmdate("j");
1346
		$goid .= pack('n', $year);
1347
		$goid .= pack('C', $month);
1348
		$goid .= pack('C', $day);
1349
		*/
1350
		// Creation Time
1351
		$time = $this->epochToMapiFileTime(time());
1352
		$goid .= pack('V', $time & 0xFFFFFFFF);
1353
		$goid .= pack('V', $time >> 32);
1354
		// 8 Zeros
1355
		$goid .= pack('H*', '0000000000000000');
1356
		// Length of the random data
1357
		$goid .= pack('V', 16);
1358
		// Random data.
1359
		for ($i=0; $i<16; $i++)
1360
			$goid .= chr(rand(0, 255));
1361
1362
		// Create a new appointment id for this item
1363
		$apptid = rand();
1364
1365
		$props[PR_OWNER_APPT_ID] = $apptid;
1366
		$props[PR_ICON_INDEX] = 1026;
1367
		$props[$this->proptags['goid']] = $goid;
1368
		$props[$this->proptags['goid2']] = $goid;
1369
1370
		if (!isset($props[$this->proptags['updatecounter']])) {
1371
			$props[$this->proptags['updatecounter']] = 0;			// OL also starts sequence no with zero.
1372
			$props[$this->proptags['last_updatecounter']] = 0;
1373
		}
1374
1375
		mapi_setprops($this->message, $props);
1376
	}
1377
1378
	/**
1379
	 * Sends a meeting request by copying it to the outbox, converting
1380
	 * the message class, adding some properties that are required only
1381
	 * for sending the message and submitting the message. Set cancel to
1382
	 * true if you wish to completely cancel the meeting request. You can
1383
	 * specify an optional 'prefix' to prefix the sent message, which is normally
1384
	 * 'Canceled: '
1385
	 */
1386
	function sendMeetingRequest($cancel, $prefix = false, $basedate = false, $modifiedRecips = false, $deletedRecips = false)
1387
	{
1388
		$this->includesResources = false;
0 ignored issues
show
Bug Best Practice introduced by
The property includesResources does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1389
		$this->nonAcceptingResources = Array();
0 ignored issues
show
Bug Best Practice introduced by
The property nonAcceptingResources does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1390
1391
		// Get the properties of the message
1392
		$messageprops = mapi_getprops($this->message, Array($this->proptags['recurring']));
1393
1394
		/*****************************************************************************************
1395
		 * Submit message to non-resource recipients
1396
		 */
1397
		// Set BusyStatus to olTentative (1)
1398
		// Set MeetingStatus to olMeetingReceived
1399
		// Set ResponseStatus to olResponseNotResponded
1400
1401
		/**
1402
		 * While sending recurrence meeting exceptions are not send as attachments
1403
		 * because first all exceptions are send and then recurrence meeting is sent.
1404
		 */
1405
		if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']] && !$basedate) {
1406
			// Book resource
1407
			$resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
0 ignored issues
show
Unused Code introduced by
The assignment to $resourceRecipData is dead and can be removed.
Loading history...
1408
1409
			if (!$this->errorSetResource) {
1410
				$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

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

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

1413
				$this->submitMeetingRequest($this->message, $cancel, $prefix, /** @scrutinizer ignore-type */ false, $recurr, false, $modifiedRecips, $deletedRecips);
Loading history...
1414
1415
				// Then send all meeting request for all exceptions
1416
				$exceptions = $recurr->getAllExceptions();
1417
				if ($exceptions) {
1418
					foreach($exceptions as $exceptionBasedate) {
1419
						$attach = $recurr->getExceptionAttachment($exceptionBasedate);
1420
1421
						if ($attach) {
1422
							$occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
1423
							$this->submitMeetingRequest($occurrenceItem, $cancel, false, $exceptionBasedate, $recurr, false, $modifiedRecips, $deletedRecips);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type string expected by parameter $prefix of Meetingrequest::submitMeetingRequest(). ( Ignorable by Annotation )

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

1423
							$this->submitMeetingRequest($occurrenceItem, $cancel, /** @scrutinizer ignore-type */ false, $exceptionBasedate, $recurr, false, $modifiedRecips, $deletedRecips);
Loading history...
1424
							mapi_savechanges($attach);
1425
						}
1426
					}
1427
				}
1428
			}
1429
		} else {
1430
			// Basedate found, an exception is to be send
1431
			if ($basedate) {
1432
				$recurr = new Recurrence($this->openDefaultStore(), $this->message);
1433
1434
				if ($cancel) {
1435
					//@TODO: remove occurrence from Resource's Calendar if resource was booked for whole series
1436
					$this->submitMeetingRequest($this->message, $cancel, $prefix, $basedate, $recurr, false);
1437
				} else {
1438
					$attach = $recurr->getExceptionAttachment($basedate);
1439
1440
					if ($attach) {
1441
						$occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
1442
1443
						// Book resource for this occurrence
1444
						$resourceRecipData = $this->bookResources($occurrenceItem, $cancel, $prefix, $basedate);
1445
1446
						if (!$this->errorSetResource) {
1447
							// Save all previous changes
1448
							mapi_savechanges($this->message);
1449
1450
							$this->submitMeetingRequest($occurrenceItem, $cancel, $prefix, $basedate, $recurr, true, $modifiedRecips, $deletedRecips);
1451
							mapi_savechanges($occurrenceItem);
1452
							mapi_savechanges($attach);
1453
						}
1454
					}
1455
				}
1456
			} else {
1457
				// This is normal meeting
1458
				$resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
1459
1460
				if (!$this->errorSetResource) {
1461
					$this->submitMeetingRequest($this->message, $cancel, $prefix, false, false, false, $modifiedRecips, $deletedRecips);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type object expected by parameter $recurObject of Meetingrequest::submitMeetingRequest(). ( Ignorable by Annotation )

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

1461
					$this->submitMeetingRequest($this->message, $cancel, $prefix, false, /** @scrutinizer ignore-type */ false, false, $modifiedRecips, $deletedRecips);
Loading history...
1462
				}
1463
			}
1464
		}
1465
1466
		if(isset($this->errorSetResource) && $this->errorSetResource){
1467
			return Array(
1468
				'error' => $this->errorSetResource,
1469
				'displayname' => $this->recipientDisplayname
1470
			);
1471
		} else {
1472
			return true;
1473
		}
1474
	}
1475
1476
	/**
1477
	 * This function will get freebusy data for user based on the timeframe passed in arguments.
1478
	 *
1479
	 * @param {HexString} $entryID Entryid of the user for which we need to get freebusy data
0 ignored issues
show
Documentation Bug introduced by
The doc comment {HexString} at position 0 could not be parsed: Unknown type name '{' at position 0 in {HexString}.
Loading history...
1480
	 * @param {Number} $start start offset for freebusy publish range
1481
	 * @param {Number} $end end offset for freebusy publish range
1482
	 * @return {Array} freebusy blocks for passed publish range
0 ignored issues
show
Documentation Bug introduced by
The doc comment {Array} at position 0 could not be parsed: Unknown type name '{' at position 0 in {Array}.
Loading history...
1483
	 */
1484
	function getFreeBusyInfo($entryID, $start, $end)
1485
	{
1486
		$result = array();
1487
			
1488
		$retval = mapi_getuseravailability($GLOBALS['mapisession']->getSession(), $entryID, $start, $end);
1489
		if (empty($retval)) {
1490
			return $result;
1491
		}
1492
		$freebusy = json_decode($retval, true);
1493
		if (0 == strcasecmp($freebusy['permission'], 'none')) {
1494
			return $result;
1495
		}
1496
		$last_end = $start;
0 ignored issues
show
Unused Code introduced by
The assignment to $last_end is dead and can be removed.
Loading history...
1497
		foreach ($freebusy['events'] as $event) {
1498
			$blockItem = array();
1499
			$blockItem['start'] = $event['StartTime'];
1500
			$blockItem['end'] = $event['EndTime'];
1501
			if ('Free' == $event['BusyType']) {
1502
				$blockItem['status'] = 0;
1503
			} else if ('Tentative' == $event['BusyType']) {
1504
				$blockItem['status'] = 1;
1505
			} else if ('Busy' == $event['BusyType']) {
1506
				$blockItem['status'] = 2;
1507
			} else if ('OOF' == $event['BusyType']) {
1508
				$blockItem['status'] = 3;
1509
			} else if ('WorkingElsewhere' == $event['BusyType']) {
1510
				$blockItem['status'] = 4;
1511
			} else {
1512
				$blockItem['status'] = -1;
1513
			}
1514
			$last_end = $event['EndTime'];
1515
			$result[] = $blockItem;	
1516
		}
1517
		
1518
		return $result;
1519
	}
1520
1521
	/**
1522
	 * Updates the message after an update has been performed (for example,
1523
	 * changing the time of the meeting). This must be called before re-sending
1524
	 * the meeting request. You can also call this function instead of 'setMeetingRequest()'
1525
	 * as it will automatically call setMeetingRequest on this object if it is the first
1526
	 * call to this function.
1527
	 */
1528
	function updateMeetingRequest($basedate = false)
1529
	{
1530
		$messageprops = mapi_getprops($this->message, Array($this->proptags['last_updatecounter'], $this->proptags['goid']));
1531
1532
		if ( !isset($messageprops[$this->proptags['goid']]) ) {
1533
			$this->setMeetingRequest($basedate);
1534
		} else {
1535
			$counter = $messageprops[$this->proptags['last_updatecounter']] + 1;
1536
1537
			// increment value of last_updatecounter, last_updatecounter will be common for recurring series
1538
			// so even if you sending an exception only you need to update the last_updatecounter in the recurring series message
1539
			// this way we can make sure that every time we will be using a uniwue number for every operation
1540
			mapi_setprops($this->message, Array($this->proptags['last_updatecounter'] => $counter));
1541
		}
1542
	}
1543
1544
	/**
1545
	 * Returns TRUE if we are the organiser of the meeting. Can be used with any type of meeting object.
1546
	 */
1547
	function isLocalOrganiser()
1548
	{
1549
		$props = mapi_getprops($this->message, array($this->proptags['goid'], PR_MESSAGE_CLASS));
1550
1551
		if(!$this->isMeetingRequest($props[PR_MESSAGE_CLASS]) && !$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS]) && !$this->isMeetingCancellation($props[PR_MESSAGE_CLASS])) {
1552
			// we are checking with calendar item
1553
			$calendarItem = $this->message;
1554
		} else {
1555
			// we are checking with meeting request / response / cancellation mail
1556
			// get calendar items
1557
			$calendarItem = $this->getCorrespondentCalendarItem(true);
1558
		}
1559
1560
		// even if we have received request/response for exception/occurrence then also
1561
		// we can check recurring series for organizer, no need to check with exception/occurrence
1562
1563
		if($calendarItem !== false) {
1564
			$messageProps = mapi_getprops($calendarItem, Array($this->proptags['responsestatus']));
1565
1566
			if(isset($messageProps[$this->proptags['responsestatus']]) && $messageProps[$this->proptags['responsestatus']] === olResponseOrganized) {
1567
				return true;
1568
			}
1569
		}
1570
1571
		return false;
1572
	}
1573
1574
	/***************************************************************************************************
1575
	 * Support functions - INTERNAL ONLY
1576
	 ***************************************************************************************************
1577
	 */
1578
1579
	/**
1580
	 * Return the tracking status of a recipient based on the IPM class (passed)
1581
	 */
1582
	function getTrackStatus($class)
1583
	{
1584
		$status = olRecipientTrackStatusNone;
1585
		switch($class)
1586
		{
1587
			case 'IPM.Schedule.Meeting.Resp.Pos':
1588
				$status = olRecipientTrackStatusAccepted;
1589
				break;
1590
1591
			case 'IPM.Schedule.Meeting.Resp.Tent':
1592
				$status = olRecipientTrackStatusTentative;
1593
				break;
1594
1595
			case 'IPM.Schedule.Meeting.Resp.Neg':
1596
				$status = olRecipientTrackStatusDeclined;
1597
				break;
1598
		}
1599
		return $status;
1600
	}
1601
1602
	/**
1603
	 * Function returns MAPIFolder resource of the folder that currently holds this meeting/meeting request
1604
	 * object.
1605
	 */
1606
	function openParentFolder()
1607
	{
1608
		$messageprops = mapi_getprops($this->message, Array(PR_PARENT_ENTRYID));
1609
		$parentfolder = mapi_msgstore_openentry($this->store, $messageprops[PR_PARENT_ENTRYID]);
1610
1611
		return $parentfolder;
1612
	}
1613
1614
	/**
1615
	 * Function will return resource of the default calendar folder of store.
1616
	 * @param MAPIStore $store {optional} user store whose default calendar should be opened.
0 ignored issues
show
Bug introduced by
The type MAPIStore was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
1617
	 * @return MAPIFolder default calendar folder of store.
0 ignored issues
show
Bug introduced by
The type MAPIFolder was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
1618
	 */
1619
	function openDefaultCalendar($store = false)
1620
	{
1621
		return $this->openDefaultFolder(PR_IPM_APPOINTMENT_ENTRYID, $store);
0 ignored issues
show
Bug introduced by
It seems like $store can also be of type false; however, parameter $store of Meetingrequest::openDefaultFolder() does only seem to accept MAPIStore, maybe add an additional type check? ( Ignorable by Annotation )

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

1621
		return $this->openDefaultFolder(PR_IPM_APPOINTMENT_ENTRYID, /** @scrutinizer ignore-type */ $store);
Loading history...
1622
	}
1623
1624
	/**
1625
	 * Function will return resource of the default outbox folder of store.
1626
	 * @param MAPIStore $store {optional} user store whose default outbox should be opened.
1627
	 * @return MAPIFolder default outbox folder of store.
1628
	 */
1629
	function openDefaultOutbox($store = false)
1630
	{
1631
		return $this->openBaseFolder(PR_IPM_OUTBOX_ENTRYID, $store);
0 ignored issues
show
Bug introduced by
It seems like $store can also be of type false; however, parameter $store of Meetingrequest::openBaseFolder() does only seem to accept MAPIStore, maybe add an additional type check? ( Ignorable by Annotation )

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

1631
		return $this->openBaseFolder(PR_IPM_OUTBOX_ENTRYID, /** @scrutinizer ignore-type */ $store);
Loading history...
1632
	}
1633
1634
	/**
1635
	 * Function will return resource of the default wastebasket folder of store.
1636
	 * @param MAPIStore $store {optional} user store whose default wastebasket should be opened.
1637
	 * @return MAPIFolder default wastebasket folder of store.
1638
	 */
1639
	function openDefaultWastebasket($store = false)
1640
	{
1641
		return $this->openBaseFolder(PR_IPM_WASTEBASKET_ENTRYID, $store);
0 ignored issues
show
Bug introduced by
It seems like $store can also be of type false; however, parameter $store of Meetingrequest::openBaseFolder() does only seem to accept MAPIStore, maybe add an additional type check? ( Ignorable by Annotation )

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

1641
		return $this->openBaseFolder(PR_IPM_WASTEBASKET_ENTRYID, /** @scrutinizer ignore-type */ $store);
Loading history...
1642
	}
1643
1644
	/**
1645
	 * Function will return resource of the default calendar folder of store.
1646
	 * @param MAPIStore $store {optional} user store whose default calendar should be opened.
1647
	 * @return MAPIFolder default calendar folder of store.
1648
	 */
1649
	function getDefaultWastebasketEntryID($store = false)
1650
	{
1651
		return $this->getBaseEntryID(PR_IPM_WASTEBASKET_ENTRYID, $store);
0 ignored issues
show
Bug introduced by
It seems like $store can also be of type false; however, parameter $store of Meetingrequest::getBaseEntryID() does only seem to accept MAPIStore, maybe add an additional type check? ( Ignorable by Annotation )

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

1651
		return $this->getBaseEntryID(PR_IPM_WASTEBASKET_ENTRYID, /** @scrutinizer ignore-type */ $store);
Loading history...
1652
	}
1653
1654
	/**
1655
	 * Function will return resource of the default sent mail folder of store.
1656
	 * @param MAPIStore $store {optional} user store whose default sent mail should be opened.
1657
	 * @return MAPIFolder default sent mail folder of store.
1658
	 */
1659
	function getDefaultSentmailEntryID($store = false)
1660
	{
1661
		return $this->getBaseEntryID(PR_IPM_SENTMAIL_ENTRYID, $store);
0 ignored issues
show
Bug introduced by
It seems like $store can also be of type false; however, parameter $store of Meetingrequest::getBaseEntryID() does only seem to accept MAPIStore, maybe add an additional type check? ( Ignorable by Annotation )

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

1661
		return $this->getBaseEntryID(PR_IPM_SENTMAIL_ENTRYID, /** @scrutinizer ignore-type */ $store);
Loading history...
1662
	}
1663
1664
	/**
1665
	 * Function will return entryid of any default folder of store. This method is useful when you want
1666
	 * to get entryid of folder which is stored as properties of inbox folder
1667
	 * (PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_JOURNAL_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_TASK_ENTRYID).
1668
	 * @param PropTag $prop proptag of the folder for which we want to get entryid.
0 ignored issues
show
Bug introduced by
The type PropTag was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
1669
	 * @param MAPIStore $store {optional} user store from which we need to get entryid of default folder.
1670
	 * @return BinString entryid of folder pointed by $prop.
0 ignored issues
show
Bug introduced by
The type BinString was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
1671
	 */
1672
	function getDefaultFolderEntryID($prop, $store = false)
1673
	{
1674
		try {
1675
			$inbox = mapi_msgstore_getreceivefolder($store ? $store : $this->store);
1676
			$inboxprops = mapi_getprops($inbox, Array($prop));
1677
			if (isset($inboxprops[$prop])) {
1678
				return $inboxprops[$prop];
1679
			}
1680
		} catch (MAPIException $e) {
1681
			// public store doesn't support this method
1682
			if ($e->getCode() == MAPI_E_NO_SUPPORT) {
1683
				// don't propagate this error to parent handlers, if store doesn't support it
1684
				$e->setHandled();
1685
			}
1686
		}
1687
1688
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type BinString.
Loading history...
1689
	}
1690
1691
	/**
1692
	 * Function will return resource of any default folder of store.
1693
	 * @param PropTag $prop proptag of the folder that we want to open.
1694
	 * @param MAPIStore $store {optional} user store from which we need to open default folder.
1695
	 * @return MAPIFolder default folder of store.
1696
	 */
1697
	function openDefaultFolder($prop, $store = false)
1698
	{
1699
		$folder = false;
1700
		$entryid = $this->getDefaultFolderEntryID($prop, $store);
0 ignored issues
show
Bug introduced by
It seems like $store can also be of type false; however, parameter $store of Meetingrequest::getDefaultFolderEntryID() does only seem to accept MAPIStore, maybe add an additional type check? ( Ignorable by Annotation )

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

1700
		$entryid = $this->getDefaultFolderEntryID($prop, /** @scrutinizer ignore-type */ $store);
Loading history...
1701
1702
		if($entryid !== false) {
0 ignored issues
show
introduced by
The condition $entryid !== false is always true.
Loading history...
1703
			$folder = mapi_msgstore_openentry($store ? $store : $this->store, $entryid);
1704
		}
1705
1706
		return $folder;
1707
	}
1708
1709
	/**
1710
	 * Function will return entryid of default folder from store. This method is useful when you want
1711
	 * to get entryid of folder which is stored as store properties
1712
	 * (PR_IPM_FAVORITES_ENTRYID, PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID).
1713
	 * @param PropTag $prop proptag of the folder whose entryid we want to get.
1714
	 * @param MAPIStore $store {optional} user store from which we need to get entryid of default folder.
1715
	 * @return BinString entryid of default folder from store.
1716
	 */
1717
	function getBaseEntryID($prop, $store = false)
1718
	{
1719
		$storeprops = mapi_getprops($store ? $store : $this->store, Array($prop));
1720
		if(!isset($storeprops[$prop])) {
1721
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type BinString.
Loading history...
1722
		}
1723
1724
		return $storeprops[$prop];
1725
	}
1726
1727
	/**
1728
	 * Function will return resource of any default folder of store.
1729
	 * @param PropTag $prop proptag of the folder that we want to open.
1730
	 * @param MAPIStore $store {optional} user store from which we need to open default folder.
1731
	 * @return MAPIFolder default folder of store.
1732
	 */
1733
	function openBaseFolder($prop, $store = false)
1734
	{
1735
		$folder = false;
1736
		$entryid = $this->getBaseEntryID($prop, $store);
0 ignored issues
show
Bug introduced by
It seems like $store can also be of type false; however, parameter $store of Meetingrequest::getBaseEntryID() does only seem to accept MAPIStore, maybe add an additional type check? ( Ignorable by Annotation )

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

1736
		$entryid = $this->getBaseEntryID($prop, /** @scrutinizer ignore-type */ $store);
Loading history...
1737
1738
		if($entryid !== false) {
0 ignored issues
show
introduced by
The condition $entryid !== false is always true.
Loading history...
1739
			$folder = mapi_msgstore_openentry($store ? $store : $this->store, $entryid);
1740
		}
1741
1742
		return $folder;
1743
	}
1744
1745
	/**
1746
	 * Function checks whether user has access over the specified folder or not.
1747
	 * @param Binary $entryid entryid The entryid of the folder to check
0 ignored issues
show
Bug introduced by
The type Binary was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
1748
	 * @param MAPIStore $store (optional) store from which folder should be opened
1749
	 * @return boolean true if user has an access over the folder, false if not.
1750
	 */
1751
	function checkFolderWriteAccess($entryid, $store = false)
1752
	{
1753
		$accessToFolder = false;
1754
1755
		if(!empty($entryid)) {
1756
			if($store === false) {
1757
				$store = $this->store;
1758
			}
1759
1760
			try {
1761
				$folder = mapi_msgstore_openentry($store, $entryid);
1762
				$folderProps = mapi_getprops($folder, Array(PR_ACCESS));
1763
				if(($folderProps[PR_ACCESS] & MAPI_ACCESS_CREATE_CONTENTS) === MAPI_ACCESS_CREATE_CONTENTS) {
1764
					$accessToFolder = true;
1765
				}
1766
			} catch (MAPIException $e) {
1767
				// we don't have rights to open folder, so return false
1768
				if($e->getCode() == MAPI_E_NO_ACCESS) {
1769
					return $accessToFolder;
1770
				}
1771
1772
				// rethrow other errors
1773
				throw $e;
1774
			}
1775
		}
1776
1777
		return $accessToFolder;
1778
	}
1779
1780
	/**
1781
	 * Function checks whether user has access over the specified folder or not.
1782
	 * @param object MAPI Message Store Object
1783
	 * @return boolean true if user has an access over the folder, false if not.
1784
	 */
1785
	function checkCalendarWriteAccess($store = false)
1786
	{
1787
		if($store === false) {
1788
			// If this meeting request is received by a delegate then open delegator's store.
1789
			$messageProps = mapi_getprops($this->message, array(PR_RCVD_REPRESENTING_ENTRYID));
1790
			if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) {
1791
				$delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID]);
1792
1793
				$store = $delegatorStore['store'];
1794
			} else {
1795
				$store = $this->store;
1796
			}
1797
		}
1798
1799
		// If the store is a public folder, the calendar folder is the PARENT_ENTRYID of the calendar item
1800
		$provider = mapi_getprops($store, array(PR_MDB_PROVIDER));
1801
		if(isset($provider[PR_MDB_PROVIDER]) && $provider[PR_MDB_PROVIDER] === ZARAFA_STORE_PUBLIC_GUID) {
1802
			$entryid = mapi_getprops($this->message, array(PR_PARENT_ENTRYID));
1803
			$entryid = $entryid[PR_PARENT_ENTRYID];
1804
		} else {
1805
			$entryid = $this->getDefaultFolderEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store);
1806
			if ($entryid === false) {
0 ignored issues
show
introduced by
The condition $entryid === false is always false.
Loading history...
1807
				$entryid = $this->getBaseEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store);
1808
			}
1809
1810
			if ($entryid === false) {
0 ignored issues
show
introduced by
The condition $entryid === false is always false.
Loading history...
1811
				return false;
1812
			}
1813
		}
1814
1815
		return $this->checkFolderWriteAccess($entryid, $store);
1816
	}
1817
1818
	/**
1819
	 * Function will resolve the user and open its store
1820
	 * @param String $ownerentryid the entryid of the user
1821
	 * @return MAPIStore store of the user
1822
	 */
1823
	function openCustomUserStore($ownerentryid)
1824
	{
1825
		$ab = mapi_openaddressbook($this->session);
1826
1827
		try {
1828
			$mailuser = mapi_ab_openentry($ab, $ownerentryid);
1829
		} catch (MAPIException $e) {
1830
			return;
1831
		}
1832
1833
		$mailuserprops = mapi_getprops($mailuser, array(PR_EMAIL_ADDRESS));
1834
		$storeid = mapi_msgstore_createentryid($this->store, $mailuserprops[PR_EMAIL_ADDRESS]);
1835
		$userStore = mapi_openmsgstore($this->session, $storeid);
1836
1837
		return $userStore;
1838
	}
1839
1840
	/**
1841
	 * Function which sends response to organizer when attendee accepts, declines or proposes new time to a received meeting request.
1842
	 * @param integer $status response status of attendee
1843
	 * @param Array $proposeNewTimeProps properties of attendee's proposal
1844
	 * @param integer $basedate date of occurrence which attendee has responded
1845
	 */
1846
	function createResponse($status, $proposeNewTimeProps = array(), $body = false, $store, $basedate = false, $calFolder) {
1847
		$messageprops = mapi_getprops($this->message, Array(PR_SENT_REPRESENTING_ENTRYID,
1848
															PR_SENT_REPRESENTING_EMAIL_ADDRESS,
1849
															PR_SENT_REPRESENTING_ADDRTYPE,
1850
															PR_SENT_REPRESENTING_NAME,
1851
															PR_SENT_REPRESENTING_SEARCH_KEY,
1852
															$this->proptags['goid'],
1853
															$this->proptags['goid2'],
1854
															$this->proptags['location'],
1855
															$this->proptags['startdate'],
1856
															$this->proptags['duedate'],
1857
															$this->proptags['recurring'],
1858
															$this->proptags['recurring_pattern'],
1859
															$this->proptags['recurrence_data'],
1860
															$this->proptags['timezone_data'],
1861
															$this->proptags['timezone'],
1862
															$this->proptags['updatecounter'],
1863
															PR_SUBJECT,
1864
															PR_MESSAGE_CLASS,
1865
															PR_OWNER_APPT_ID,
1866
															$this->proptags['is_exception']
1867
									));
1868
1869
		if ($basedate && !$this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $basedate of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1870
			// we are creating response from a recurring calendar item object
1871
			// We found basedate,so opened occurrence and get properties.
1872
			$recurr = new Recurrence($store, $this->message);
1873
			$exception = $recurr->getExceptionAttachment($basedate);
1874
1875
			if ($exception) {
1876
				// Exception found, Now retrieve properties
1877
				$imessage = mapi_attach_openobj($exception, 0);
1878
				$imsgprops = mapi_getprops($imessage);
1879
1880
				// If location is provided, copy it to the response
1881
				if (isset($imsgprops[$this->proptags['location']])) {
1882
					$messageprops[$this->proptags['location']] = $imsgprops[$this->proptags['location']];
1883
				}
1884
1885
				// Update $messageprops with timings of occurrence
1886
				$messageprops[$this->proptags['startdate']] = $imsgprops[$this->proptags['startdate']];
1887
				$messageprops[$this->proptags['duedate']] = $imsgprops[$this->proptags['duedate']];
1888
1889
				// Meeting related properties
1890
				$props[$this->proptags['meetingstatus']] = $imsgprops[$this->proptags['meetingstatus']];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$props was never initialized. Although not strictly required by PHP, it is generally a good practice to add $props = array(); before regardless.
Loading history...
1891
				$props[$this->proptags['responsestatus']] = $imsgprops[$this->proptags['responsestatus']];
1892
				$props[PR_SUBJECT] = $imsgprops[PR_SUBJECT];
1893
			} else {
1894
				// Exceptions is deleted.
1895
				// Update $messageprops with timings of occurrence
1896
				$messageprops[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
1897
				$messageprops[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
1898
1899
				$props[$this->proptags['meetingstatus']] = olNonMeeting;
1900
				$props[$this->proptags['responsestatus']] = olResponseNone;
1901
			}
1902
1903
			$props[$this->proptags['recurring']] = false;
1904
			$props[$this->proptags['is_exception']] = true;
1905
		} else {
1906
			// we are creating a response from meeting request mail (it could be recurring or non-recurring)
1907
			// Send all recurrence info in response, if this is a recurrence meeting.
1908
			$isRecurring = isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']];
1909
			$isException = isset($messageprops[$this->proptags['is_exception']]) && $messageprops[$this->proptags['is_exception']];
1910
			if ($isRecurring || $isException) {
1911
				if($isRecurring) {
1912
					$props[$this->proptags['recurring']] = $messageprops[$this->proptags['recurring']];
1913
				}
1914
				if($isException) {
1915
					$props[$this->proptags['is_exception']] = $messageprops[$this->proptags['is_exception']];
1916
				}
1917
				$calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
1918
1919
				$calendaritem = mapi_msgstore_openentry($store, $calendaritems[0]);
1920
				$recurr = new Recurrence($store, $calendaritem);
1921
			}
1922
		}
1923
1924
		// we are sending a response for recurring meeting request (or exception), so set some required properties
1925
		if(isset($recurr) && $recurr) {
1926
			if(!empty($messageprops[$this->proptags['recurring_pattern']])) {
1927
				$props[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
1928
			}
1929
1930
			if(!empty($messageprops[$this->proptags['recurrence_data']])) {
1931
				$props[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
1932
			}
1933
1934
			$props[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
1935
			$props[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
1936
1937
			$this->generateRecurDates($recurr, $messageprops, $props);
1938
		}
1939
1940
		// Create a response message
1941
		$recip = Array();
1942
		$recip[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
1943
		$recip[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
1944
		$recip[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
1945
		$recip[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
1946
		$recip[PR_RECIPIENT_TYPE] = MAPI_TO;
1947
		$recip[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
1948
1949
		switch($status) {
1950
			case olResponseAccepted:
1951
				$classpostfix = 'Pos';
1952
				$subjectprefix = dgettext('zarafa','Accepted');
1953
				break;
1954
			case olResponseDeclined:
1955
				$classpostfix = 'Neg';
1956
				$subjectprefix = dgettext('zarafa','Declined');
1957
				break;
1958
			case olResponseTentative:
1959
				$classpostfix = 'Tent';
1960
				$subjectprefix = dgettext('zarafa','Tentatively accepted');
1961
				break;
1962
		}
1963
1964
		if (!empty($proposeNewTimeProps)) {
1965
			// if attendee has proposed new time then change subject prefix
1966
			$subjectprefix = dgettext('zarafa','New Time Proposed');
1967
		}
1968
1969
		$props[PR_SUBJECT] = $subjectprefix . ': ' . $messageprops[PR_SUBJECT];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $subjectprefix does not seem to be defined for all execution paths leading up to this point.
Loading history...
1970
1971
		$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...
1972
		if(isset($messageprops[PR_OWNER_APPT_ID]))
1973
			$props[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
1974
1975
		// Set GlobalId AND CleanGlobalId, if exception then also set basedate into GlobalId(0x3).
1976
		$props[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
1977
		$props[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
1978
		$props[$this->proptags['updatecounter']] = isset($messageprops[$this->proptags['updatecounter']]) ? $messageprops[$this->proptags['updatecounter']] : 0;
1979
1980
		if (!empty($proposeNewTimeProps)) {
1981
			// merge proposal properties to message properties which will be sent to organizer
1982
			$props = $proposeNewTimeProps + $props;
1983
		}
1984
1985
		//Set body message in Appointment
1986
		if(isset($body)) {
1987
			$props[PR_BODY] = $this->getMeetingTimeInfo() ? $this->getMeetingTimeInfo() : $body;
1988
		}
1989
1990
		// PR_START_DATE/PR_END_DATE is used in the UI in Outlook on the response message
1991
		$props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
1992
		$props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
1993
1994
		// Set startdate and duedate in response mail.
1995
		$props[$this->proptags['startdate']] = $messageprops[$this->proptags['startdate']];
1996
		$props[$this->proptags['duedate']] = $messageprops[$this->proptags['duedate']];
1997
1998
		// responselocation is used in the UI in Outlook on the response message
1999
		if (isset($messageprops[$this->proptags['location']])) {
2000
			$props[$this->proptags['responselocation']] = $messageprops[$this->proptags['location']];
2001
			$props[$this->proptags['location']] = $messageprops[$this->proptags['location']];
2002
		}
2003
2004
		$message = $this->createOutgoingMessage($store);
2005
2006
		mapi_setprops($message, $props);
2007
		mapi_message_modifyrecipients($message, MODRECIP_ADD, Array($recip));
2008
		mapi_savechanges($message);
2009
		mapi_message_submitmessage($message);
2010
	}
2011
2012
	/**
2013
	 * Function which finds items in calendar based on globalId and cleanGlobalId.
2014
	 * @param binary $goid GlobalID(0x3) of item
0 ignored issues
show
Bug introduced by
The type binary was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
2015
	 * @param MAPIFolder $calendar MAPI_folder of user (optional)
2016
	 * @param boolean $useCleanGlobalId if true then search should be performed on cleanGlobalId(0x23) else globalId(0x3)
2017
	 */
2018
	function findCalendarItems($goid, $calendar = false, $useCleanGlobalId = false)
2019
	{
2020
		if($calendar === false) {
2021
			// Open the Calendar
2022
			$calendar = $this->openDefaultCalendar();
2023
		}
2024
2025
		// Find the item by restricting all items to the correct ID
2026
		$restrict = Array(RES_AND, Array(
2027
										Array(RES_PROPERTY,
2028
											Array(
2029
												RELOP => RELOP_EQ,
2030
												ULPROPTAG => ($useCleanGlobalId === true ? $this->proptags['goid2'] : $this->proptags['goid']),
2031
												VALUE => $goid
2032
											)
2033
										)
2034
						));
2035
2036
		$calendarcontents = mapi_folder_getcontentstable($calendar);
2037
2038
		$rows = mapi_table_queryallrows($calendarcontents, Array(PR_ENTRYID), $restrict);
2039
2040
		if(empty($rows))
2041
			return;
2042
2043
		$calendaritems = Array();
2044
2045
		// In principle, there should only be one row, but we'll handle them all just in case
2046
		foreach($rows as $row) {
2047
			$calendaritems[] = $row[PR_ENTRYID];
2048
		}
2049
2050
		return $calendaritems;
2051
	}
2052
2053
	// Returns TRUE if both entryid's are equal. Equality is defined by both entryid's pointing at the
2054
	// same SMTP address when converted to SMTP
2055
	function compareABEntryIDs($entryid1, $entryid2)
2056
	{
2057
		// If the session was not passed, just do a 'normal' compare.
2058
		if(!$this->session) {
2059
			return $entryid1 == $entryid2;
2060
		}
2061
2062
		$smtp1 = $this->getSMTPAddress($entryid1);
2063
		$smtp2 = $this->getSMTPAddress($entryid2);
2064
2065
		if($smtp1 == $smtp2) {
2066
			return true;
2067
		} else {
2068
			return false;
2069
		}
2070
	}
2071
2072
	// Gets the SMTP address of the passed addressbook entryid
2073
	function getSMTPAddress($entryid)
2074
	{
2075
		if(!$this->session) {
2076
			return false;
2077
		}
2078
		
2079
		try {
2080
			$ab = mapi_openaddressbook($this->session);
2081
			$abitem = mapi_ab_openentry($ab, $entryid);
2082
2083
			if(!$abitem) {
2084
				return '';
2085
			}
2086
		} catch (MAPIException $e) {
2087
			return '';
2088
		}
2089
2090
		$props = mapi_getprops($abitem, array(PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS));
2091
2092
		if($props[PR_ADDRTYPE] == 'SMTP') {
2093
			return $props[PR_EMAIL_ADDRESS];
2094
		} else {
2095
			return $props[PR_SMTP_ADDRESS];
2096
		}
2097
	}
2098
2099
	/**
2100
	 * Gets the properties associated with the owner of the passed store:
2101
	 * PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_ADDRTYPE, PR_ENTRYID, PR_SEARCH_KEY
2102
	 *
2103
	 * @param $store message store
2104
	 * @param $fallbackToLoggedInUser if true then return properties of logged in user instead of mailbox owner
0 ignored issues
show
Bug introduced by
The type if was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
2105
	 * not used when passed store is public store. for public store we are always returning logged in user's info.
2106
	 * @return properties of logged in user in an array in sequence of display_name, email address, address type,
2107
	 * entryid and search key.
2108
	 */
2109
	function getOwnerAddress($store, $fallbackToLoggedInUser = true)
2110
	{
2111
		if(!$this->session)
2112
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type properties.
Loading history...
2113
2114
		$storeProps = mapi_getprops($store, array(PR_MAILBOX_OWNER_ENTRYID, PR_USER_ENTRYID));
2115
2116
		$ownerEntryId = false;
2117
		if(isset($storeProps[PR_USER_ENTRYID]) && $storeProps[PR_USER_ENTRYID]) {
2118
			$ownerEntryId = $storeProps[PR_USER_ENTRYID];
2119
		}
2120
2121
		if(isset($storeProps[PR_MAILBOX_OWNER_ENTRYID]) && $storeProps[PR_MAILBOX_OWNER_ENTRYID] && !$fallbackToLoggedInUser) {
2122
			$ownerEntryId = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
2123
		}
2124
2125
		if($ownerEntryId) {
2126
			$ab = mapi_openaddressbook($this->session);
2127
2128
			$zarafaUser = mapi_ab_openentry($ab, $ownerEntryId);
2129
			if(!$zarafaUser)
2130
				return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type properties.
Loading history...
2131
2132
			$ownerProps = mapi_getprops($zarafaUser, array(PR_ADDRTYPE, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_SEARCH_KEY));
2133
2134
			$addrType = $ownerProps[PR_ADDRTYPE];
2135
			$name = $ownerProps[PR_DISPLAY_NAME];
2136
			$emailAddr = $ownerProps[PR_EMAIL_ADDRESS];
2137
			$searchKey = $ownerProps[PR_SEARCH_KEY];
2138
			$entryId = $ownerEntryId;
2139
2140
			return array($name, $emailAddr, $addrType, $entryId, $searchKey);
0 ignored issues
show
Bug Best Practice introduced by
The expression return array($name, $ema..., $entryId, $searchKey) returns the type array which is incompatible with the documented return type properties.
Loading history...
2141
		}
2142
2143
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type properties.
Loading history...
2144
	}
2145
2146
	// Opens this session's default message store
2147
	function openDefaultStore()
2148
	{
2149
		$storestable = mapi_getmsgstorestable($this->session);
2150
		$rows = mapi_table_queryallrows($storestable, array(PR_ENTRYID, PR_DEFAULT_STORE));
2151
2152
		foreach($rows as $row) {
2153
			if(isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE]) {
2154
				$entryid = $row[PR_ENTRYID];
2155
				break;
2156
			}
2157
		}
2158
2159
		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...
2160
			return false;
2161
2162
		return mapi_openmsgstore($this->session, $entryid);
2163
	}
2164
2165
	/**
2166
	 *  Function which adds organizer to recipient list which is passed.
2167
	 *  This function also checks if it has organizer.
2168
	 *
2169
	 * @param array $messageProps message properties
2170
	 * @param array $recipients	recipients list of message.
2171
	 * @param boolean $isException true if we are processing recipient of exception
2172
	 */
2173
	function addOrganizer($messageProps, &$recipients, $isException = false){
2174
2175
		$hasOrganizer = false;
2176
		// Check if meeting already has an organizer.
2177
		foreach ($recipients as $key => $recipient){
2178
			if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
2179
				$hasOrganizer = true;
2180
			} else if ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])){
2181
				// Recipients for an occurrence
2182
				$recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
2183
			}
2184
		}
2185
2186
		if (!$hasOrganizer){
2187
			// Create organizer.
2188
			$organizer = array();
2189
			$organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID];
2190
			$organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
2191
			$organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
2192
			$organizer[PR_RECIPIENT_TYPE] = MAPI_TO;
2193
			$organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
2194
			$organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
2195
			$organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
2196
			$organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
2197
			$organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY];
2198
2199
			// Add organizer to recipients list.
2200
			array_unshift($recipients, $organizer);
2201
		}
2202
	}
2203
2204
	/**
2205
	 * Function which removes an exception/occurrence from recurrencing meeting
2206
	 * when a meeting cancellation of an occurrence is processed.
2207
	 * @param string $basedate basedate of an occurrence
2208
	 * @param resource $message recurring item from which occurrence has to be deleted
2209
	 * @param resource $store MAPI_MSG_Store which contains the item
2210
	 */
2211
	function doRemoveExceptionFromCalendar($basedate, $message, $store)
2212
	{
2213
		$recurr = new Recurrence($store, $message);
2214
		$recurr->createException(array(), $basedate, true);
0 ignored issues
show
Bug introduced by
$basedate of type string is incompatible with the type date expected by parameter $base_date of Recurrence::createException(). ( Ignorable by Annotation )

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

2214
		$recurr->createException(array(), /** @scrutinizer ignore-type */ $basedate, true);
Loading history...
2215
		mapi_savechanges($message);
2216
	}
2217
2218
	/**
2219
	 * Function which returns basedate of an changed occurrence from globalID of meeting request.
2220
	 *@param binary $goid globalID
2221
	 *@return boolean true if basedate is found else false it not found
2222
	 */
2223
	function getBasedateFromGlobalID($goid)
2224
	{
2225
		$hexguid = bin2hex($goid);
2226
		$hexbase = substr($hexguid, 32, 8);
2227
		$day = hexdec(substr($hexbase, 6, 2));
2228
		$month = hexdec(substr($hexbase, 4, 2));
2229
		$year = hexdec(substr($hexbase, 0, 4));
2230
2231
		if ($day && $month && $year) {
2232
			return gmmktime(0, 0, 0, $month, $day, $year);
0 ignored issues
show
Bug introduced by
It seems like $year can also be of type double; however, parameter $year of gmmktime() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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

2232
			return gmmktime(0, 0, 0, $month, /** @scrutinizer ignore-type */ $day, $year);
Loading history...
Bug Best Practice introduced by
The expression return gmmktime(0, 0, 0, $month, $day, $year) returns the type integer which is incompatible with the documented return type boolean.
Loading history...
2233
		} else {
2234
			return false;
2235
		}
2236
	}
2237
2238
	/**
2239
	 * Function which sets basedate in globalID of changed occurrence which is to be send.
2240
	 *@param binary $goid globalID
2241
	 *@param string basedate of changed occurrence
0 ignored issues
show
Bug introduced by
The type basedate was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
2242
	 *@return binary globalID with basedate in it
2243
	 */
2244
	function setBasedateInGlobalID($goid, $basedate = false)
2245
	{
2246
		$hexguid = bin2hex($goid);
2247
		$year = $basedate ? sprintf('%04s', dechex(date('Y', $basedate))) : '0000';
0 ignored issues
show
Bug introduced by
date('Y', $basedate) of type string is incompatible with the type integer expected by parameter $num of dechex(). ( Ignorable by Annotation )

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

2247
		$year = $basedate ? sprintf('%04s', dechex(/** @scrutinizer ignore-type */ date('Y', $basedate))) : '0000';
Loading history...
2248
		$month = $basedate ? sprintf('%02s', dechex(date('m', $basedate))) : '00';
2249
		$day = $basedate ? sprintf('%02s', dechex(date('d', $basedate))) : '00';
2250
2251
		return hex2bin(strtoupper(substr($hexguid, 0, 32) . $year . $month . $day . substr($hexguid, 40)));
0 ignored issues
show
Bug Best Practice introduced by
The expression return hex2bin(strtouppe... substr($hexguid, 40))) returns the type string which is incompatible with the documented return type binary.
Loading history...
2252
	}
2253
2254
	/**
2255
	 * Function which replaces attachments with copy_from in copy_to.
2256
	 * @param MAPIMessage $copy_from MAPI_message from which attachments are to be copied.
2257
	 * @param MAPIMessage $copy_to MAPI_message to which attachment are to be copied.
2258
	 * @param Boolean $copyExceptions if true then all exceptions should also be sent as attachments
2259
	 */
2260
	function replaceAttachments($copyFrom, $copyTo, $copyExceptions = true)
2261
	{
2262
		/* remove all old attachments */
2263
		$attachmentTable = mapi_message_getattachmenttable($copyTo);
2264
		if($attachmentTable) {
2265
			$attachments = mapi_table_queryallrows($attachmentTable, array(PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME));
2266
2267
			foreach($attachments as $attachProps){
2268
				/* remove exceptions too? */
2269
				if (!$copyExceptions && $attachProps[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG && isset($attachProps[PR_EXCEPTION_STARTTIME])) {
2270
					continue;
2271
				}
2272
				mapi_message_deleteattach($copyTo, $attachProps[PR_ATTACH_NUM]);
2273
			}
2274
		}
2275
		$attachmentTable = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $attachmentTable is dead and can be removed.
Loading history...
2276
2277
		/* copy new attachments */
2278
		$attachmentTable = mapi_message_getattachmenttable($copyFrom);
2279
		if($attachmentTable) {
2280
			$attachments = mapi_table_queryallrows($attachmentTable, array(PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME));
2281
2282
			foreach($attachments as $attachProps){
2283
				if (!$copyExceptions && $attachProps[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG && isset($attachProps[PR_EXCEPTION_STARTTIME])) {
2284
					continue;
2285
				}
2286
2287
				$attachOld = mapi_message_openattach($copyFrom, (int) $attachProps[PR_ATTACH_NUM]);
2288
				$attachNewResourceMsg = mapi_message_createattach($copyTo);
2289
				mapi_copyto($attachOld, array(), array(), $attachNewResourceMsg, 0);
2290
				mapi_savechanges($attachNewResourceMsg);
2291
			}
2292
		}
2293
	}
2294
2295
	/**
2296
	 * Function which replaces recipients in copy_to with recipients from copyFrom.
2297
	 * @param MAPIMessage $copyFrom MAPI_message from which recipients are to be copied.
2298
	 * @param MAPIMessage $copyTo MAPI_message to which recipients are to be copied.
2299
	 * @param Boolean $isDelegate indicates delegate is processing
2300
	 * so don't copy delegate information to recipient table.
2301
	 */
2302
	function replaceRecipients($copyFrom, $copyTo, $isDelegate = false)
2303
	{
2304
		$recipientTable = mapi_message_getrecipienttable($copyFrom);
2305
2306
		// If delegate, then do not add the delegate in recipients
2307
		if ($isDelegate) {
2308
			$delegate = mapi_getprops($copyFrom, array(PR_RECEIVED_BY_EMAIL_ADDRESS));
2309
			$res = array(RES_PROPERTY, array(
2310
											RELOP => RELOP_NE,
2311
											ULPROPTAG => PR_EMAIL_ADDRESS,
2312
											VALUE => array(PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS])
2313
										)
2314
						);
2315
			$recipients = mapi_table_queryallrows($recipientTable, $this->recipprops, $res);
2316
		} else {
2317
			$recipients = mapi_table_queryallrows($recipientTable, $this->recipprops);
2318
		}
2319
2320
		$copyToRecipientTable = mapi_message_getrecipienttable($copyTo);
2321
		$copyToRecipientRows = mapi_table_queryallrows($copyToRecipientTable, array(PR_ROWID));
2322
2323
		mapi_message_modifyrecipients($copyTo, MODRECIP_REMOVE, $copyToRecipientRows);
2324
		mapi_message_modifyrecipients($copyTo, MODRECIP_ADD, $recipients);
2325
	}
2326
2327
	/**
2328
	 * Function creates meeting item in resource's calendar.
2329
	 * @param resource $message MAPI_message which is to create in resource's calendar
2330
	 * @param boolean $cancel cancel meeting
2331
	 * @param string $prefix prefix for subject of meeting
2332
	 */
2333
	function bookResources($message, $cancel, $prefix, $basedate = false)
2334
	{
2335
		if(!$this->enableDirectBooking) {
2336
			return array();
2337
		}
2338
2339
		// Get the properties of the message
2340
		$messageprops = mapi_getprops($message);
2341
2342
		if ($basedate) {
2343
			$recurrItemProps = mapi_getprops($this->message, array($this->proptags['goid'], $this->proptags['goid2'], $this->proptags['timezone_data'], $this->proptags['timezone'], PR_OWNER_APPT_ID));
2344
2345
			$messageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid']], $basedate);
2346
			$messageprops[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']];
2347
2348
			// Delete properties which are not needed.
2349
			$deleteProps = array($this->proptags['basedate'], PR_DISPLAY_NAME, PR_ATTACHMENT_FLAGS, PR_ATTACHMENT_HIDDEN, PR_ATTACHMENT_LINKID, PR_ATTACH_FLAGS, PR_ATTACH_METHOD);
2350
			foreach ($deleteProps as $propID) {
2351
				if (isset($messageprops[$propID])) {
2352
					unset($messageprops[$propID]);
2353
				}
2354
			}
2355
2356
			if (isset($messageprops[$this->proptags['recurring']])) {
2357
				$messageprops[$this->proptags['recurring']] = false;
2358
			}
2359
2360
			// Set Outlook properties
2361
			$messageprops[$this->proptags['clipstart']] = $messageprops[$this->proptags['startdate']];
2362
			$messageprops[$this->proptags['clipend']] = $messageprops[$this->proptags['duedate']];
2363
			$messageprops[$this->proptags['timezone_data']] = $recurrItemProps[$this->proptags['timezone_data']];
2364
			$messageprops[$this->proptags['timezone']] = $recurrItemProps[$this->proptags['timezone']];
2365
			$messageprops[$this->proptags['attendee_critical_change']] = time();
2366
			$messageprops[$this->proptags['owner_critical_change']] = time();
2367
		}
2368
2369
		// Get resource recipients
2370
		$getResourcesRestriction = Array(RES_AND,
2371
			Array(Array(RES_PROPERTY,
2372
				Array(RELOP => RELOP_EQ,	// Equals recipient type 3: Resource
2373
					ULPROPTAG => PR_RECIPIENT_TYPE,
2374
					VALUE => array(PR_RECIPIENT_TYPE =>MAPI_BCC)
2375
				)
2376
			))
2377
		);
2378
		$recipienttable = mapi_message_getrecipienttable($message);
2379
		$resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
2380
2381
		$this->errorSetResource = false;
2382
		$resourceRecipData = Array();
2383
2384
		// Put appointment into store resource users
2385
		$i = 0;
2386
		$len = count($resourceRecipients);
2387
		while(!$this->errorSetResource && $i < $len){
2388
			$userStore = $this->openCustomUserStore($resourceRecipients[$i][PR_ENTRYID]);
2389
2390
			// Open root folder
2391
			$userRoot = mapi_msgstore_openentry($userStore, null);
2392
2393
			// Get calendar entryID
2394
			$userRootProps = mapi_getprops($userRoot, array(PR_STORE_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_FREEBUSY_ENTRYIDS));
2395
2396
			// Open Calendar folder
2397
			$accessToFolder = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $accessToFolder is dead and can be removed.
Loading history...
2398
			try {
2399
				// @FIXME this checks delegate has access to resource's calendar folder
2400
				// but it should use boss' credentials
2401
2402
				$accessToFolder = $this->checkCalendarWriteAccess($this->store);
2403
				if ($accessToFolder) {
2404
					$calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
2405
				}
2406
			} catch (MAPIException $e) {
2407
				$e->setHandled();
2408
				$this->errorSetResource = 1; // No access
2409
			}
2410
2411
			if($accessToFolder) {
2412
				/**
2413
				 * Get the LocalFreebusy message that contains the properties that
2414
				 * are set to accept or decline resource meeting requests
2415
				 */
2416
				$localFreebusyMsg = freebusy::getLocalFreeBusyMessage($userStore);
2417
				if($localFreebusyMsg){
2418
					$props = mapi_getprops($localFreebusyMsg, array(PR_PROCESS_MEETING_REQUESTS, PR_DECLINE_RECURRING_MEETING_REQUESTS, PR_DECLINE_CONFLICTING_MEETING_REQUESTS));
2419
2420
					$acceptMeetingRequests = isset($props[PR_PROCESS_MEETING_REQUESTS]) ? $props[PR_PROCESS_MEETING_REQUESTS] : false;
2421
					$declineRecurringMeetingRequests = isset($props[PR_DECLINE_RECURRING_MEETING_REQUESTS]) ? $props[PR_DECLINE_RECURRING_MEETING_REQUESTS] : false;
2422
					$declineConflictingMeetingRequests = isset($props[PR_DECLINE_CONFLICTING_MEETING_REQUESTS]) ? $props[PR_DECLINE_CONFLICTING_MEETING_REQUESTS] : false;
2423
2424
					if(!$acceptMeetingRequests){
2425
						/**
2426
						 * When a resource has not been set to automatically accept meeting requests,
2427
						 * the meeting request has to be sent to him rather than being put directly into
2428
						 * his calendar. No error should be returned.
2429
						 */
2430
						//$errorSetResource = 2;
2431
						$this->nonAcceptingResources[] = $resourceRecipients[$i];
0 ignored issues
show
Bug Best Practice introduced by
The property nonAcceptingResources does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2432
					} else {
2433
						if($declineRecurringMeetingRequests && !$cancel){
2434
							// Check if appointment is recurring
2435
							if($messageprops[ $this->proptags['recurring'] ]){
2436
								$this->errorSetResource = 3;
2437
							}
2438
						}
2439
						if($declineConflictingMeetingRequests && !$cancel){
2440
							// Check for conflicting items
2441
							if ($calFolder && $this->isMeetingConflicting($message, $userStore, $calFolder)) {
0 ignored issues
show
Bug introduced by
$message of type resource is incompatible with the type MAPIMessage expected by parameter $message of Meetingrequest::isMeetingConflicting(). ( Ignorable by Annotation )

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

2441
							if ($calFolder && $this->isMeetingConflicting(/** @scrutinizer ignore-type */ $message, $userStore, $calFolder)) {
Loading history...
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...
2442
								$this->errorSetResource = 4; // Conflict
2443
							}
2444
						}
2445
					}
2446
				}
2447
			}
2448
2449
			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...
2450
				/**
2451
				 * First search on GlobalID(0x3)
2452
				 * 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.
2453
				 * If (normal meeting) then GlobalID(0x3) and CleanGlobalID(0x23) are same, so doesn't matter if search is based on GlobalID.
2454
				 */
2455
				$rows = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
2456
2457
				/**
2458
				 * If no entry is found then
2459
				 * 1) Resource doesn't have meeting in Calendar. Seriously!!
2460
				 * OR
2461
				 * 2) We were looking for occurrence item but Resource has whole series
2462
				 */
2463
				if(empty($rows)){
2464
					/**
2465
					 * Now search on CleanGlobalID(0x23) WHY???
2466
					 * Because we are looking recurring item
2467
					 *
2468
					 * Possible results of this search
2469
					 * 1) If Resource was booked for more than one occurrences then this search will return all those occurrence because search is perform on CleanGlobalID
2470
					 * 2) If Resource was booked for whole series then it should return series.
2471
					 */
2472
					$rows = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
2473
2474
					$newResourceMsg = false;
2475
					if (!empty($rows)) {
2476
						// Since we are looking for recurring item, open every result and check for 'recurring' property.
2477
						foreach($rows as $row) {
2478
							$ResourceMsg = mapi_msgstore_openentry($userStore, $row);
2479
							$ResourceMsgProps = mapi_getprops($ResourceMsg, array($this->proptags['recurring']));
2480
2481
							if (isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2482
								$newResourceMsg = $ResourceMsg;
2483
								break;
2484
							}
2485
						}
2486
					}
2487
2488
					// Still no results found. I giveup, create new message.
2489
					if (!$newResourceMsg)
2490
						$newResourceMsg = mapi_folder_createmessage($calFolder);
2491
				} else {
2492
					$newResourceMsg = mapi_msgstore_openentry($userStore, $rows[0]);
2493
				}
2494
2495
				// Prefix the subject if needed
2496
				if($prefix && isset($messageprops[PR_SUBJECT])) {
2497
					$messageprops[PR_SUBJECT] = $prefix . $messageprops[PR_SUBJECT];
2498
				}
2499
2500
				// Set status to cancelled if needed
2501
				$messageprops[$this->proptags['busystatus']] = fbBusy; // The default status (Busy)
2502
				if($cancel) {
2503
					$messageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // The meeting has been canceled
2504
					$messageprops[$this->proptags['busystatus']] = fbFree; // Free
2505
				} else {
2506
					$messageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
2507
				}
2508
				$messageprops[$this->proptags['responsestatus']] = olResponseAccepted; // The resource automatically accepts the appointment
2509
2510
				$messageprops[PR_MESSAGE_CLASS] = 'IPM.Appointment';
2511
2512
				// Remove the PR_ICON_INDEX as it is not needed in the sent message.
2513
				$messageprops[PR_ICON_INDEX] = null;
2514
				$messageprops[PR_RESPONSE_REQUESTED] = true;
2515
2516
				// get the store of organizer, in case of delegates it will be delegate store
2517
				$defaultStore = $this->openDefaultStore();
2518
2519
				$storeProps = mapi_getprops($this->store, array(PR_ENTRYID));
2520
				$defaultStoreProps = mapi_getprops($defaultStore, array(PR_ENTRYID));
2521
2522
				// @FIXME use entryid comparison functions here
2523
				if($storeProps[PR_ENTRYID] !== $defaultStoreProps[PR_ENTRYID]) {
2524
					// get delegate information
2525
					$addrInfo = $this->getOwnerAddress($defaultStore, false);
2526
2527
					if($addrInfo) {
2528
						list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrInfo;
2529
2530
						$messageprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
2531
						$messageprops[PR_SENDER_NAME] = $ownername;
2532
						$messageprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
2533
						$messageprops[PR_SENDER_ENTRYID] = $ownerentryid;
2534
						$messageprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
2535
					}
2536
2537
					// get delegator information
2538
					$addrInfo = $this->getOwnerAddress($this->store, false);
2539
2540
					if($addrInfo) {
2541
						list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrInfo;
2542
2543
						$messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
2544
						$messageprops[PR_SENT_REPRESENTING_NAME] = $ownername;
2545
						$messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
2546
						$messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
2547
						$messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
2548
					}
2549
				} else {
2550
					// get organizer information
2551
					$addrinfo = $this->getOwnerAddress($this->store);
2552
2553
					if($addrinfo) {
2554
						list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrinfo;
2555
2556
						$messageprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
2557
						$messageprops[PR_SENDER_NAME] = $ownername;
2558
						$messageprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
2559
						$messageprops[PR_SENDER_ENTRYID] = $ownerentryid;
2560
						$messageprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
2561
2562
						$messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
2563
						$messageprops[PR_SENT_REPRESENTING_NAME] = $ownername;
2564
						$messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
2565
						$messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
2566
						$messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
2567
					}
2568
				}
2569
2570
				$messageprops[$this->proptags['replytime']] = time();
2571
2572
				if ($basedate && isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2573
					$recurr = new Recurrence($userStore, $newResourceMsg);
0 ignored issues
show
Bug introduced by
$userStore of type MAPIStore is incompatible with the type resource expected by parameter $store of Recurrence::__construct(). ( Ignorable by Annotation )

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

2573
					$recurr = new Recurrence(/** @scrutinizer ignore-type */ $userStore, $newResourceMsg);
Loading history...
2574
2575
					// Copy recipients list
2576
					$reciptable = mapi_message_getrecipienttable($message);
2577
					$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2578
2579
					// add owner to recipient table
2580
					$this->addOrganizer($messageprops, $recips, true);
2581
2582
					// Update occurrence
2583
					if($recurr->isException($basedate))
2584
						$recurr->modifyException($messageprops, $basedate, $recips);
2585
					else
2586
						$recurr->createException($messageprops, $basedate, false, $recips);
2587
				} else {
2588
2589
					mapi_setprops($newResourceMsg, $messageprops);
2590
2591
					// Copy attachments
2592
					$this->replaceAttachments($message, $newResourceMsg);
2593
2594
					// Copy all recipients too
2595
					$this->replaceRecipients($message, $newResourceMsg);
0 ignored issues
show
Bug introduced by
$message of type resource is incompatible with the type MAPIMessage expected by parameter $copyFrom of Meetingrequest::replaceRecipients(). ( Ignorable by Annotation )

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

2595
					$this->replaceRecipients(/** @scrutinizer ignore-type */ $message, $newResourceMsg);
Loading history...
2596
2597
					// Now add organizer also to recipient table
2598
					$recips = Array();
2599
					$this->addOrganizer($messageprops, $recips);
2600
2601
					mapi_message_modifyrecipients($newResourceMsg, MODRECIP_ADD, $recips);
2602
				}
2603
2604
				mapi_savechanges($newResourceMsg);
2605
2606
				$resourceRecipData[] = Array(
2607
					'store' => $userStore,
2608
					'folder' => $calFolder,
2609
					'msg' => $newResourceMsg,
2610
				);
2611
				$this->includesResources = true;
0 ignored issues
show
Bug Best Practice introduced by
The property includesResources does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2612
			} else {
2613
				/**
2614
				 * If no other errors occurred and you have no access to the
2615
				 * folder of the resource, throw an error=1.
2616
				 */
2617
				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...
2618
					$this->errorSetResource = 1;
2619
				}
2620
2621
				for($j = 0, $len = count($resourceRecipData); $j < $len; $j++){
2622
					// Get the EntryID
2623
					$props = mapi_message_getprops($resourceRecipData[$j]['msg']);
2624
2625
					mapi_folder_deletemessages($resourceRecipData[$j]['folder'], Array($props[PR_ENTRYID]), DELETE_HARD_DELETE);
2626
				}
2627
				$this->recipientDisplayname = $resourceRecipients[$i][PR_DISPLAY_NAME];
0 ignored issues
show
Bug Best Practice introduced by
The property recipientDisplayname does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2628
			}
2629
			$i++;
2630
		}
2631
2632
		/**************************************************************
2633
		 * Set the BCC-recipients (resources) tackstatus to accepted.
2634
		 */
2635
		// Get resource recipients
2636
		$getResourcesRestriction = Array(RES_AND,
2637
			Array(Array(RES_PROPERTY,
2638
				Array(RELOP => RELOP_EQ,	// Equals recipient type 3: Resource
2639
					ULPROPTAG => PR_RECIPIENT_TYPE,
2640
					VALUE => array(PR_RECIPIENT_TYPE =>MAPI_BCC)
2641
				)
2642
			))
2643
		);
2644
		$recipienttable = mapi_message_getrecipienttable($message);
2645
		$resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
2646
		if(!empty($resourceRecipients)){
2647
			// Set Tracking status of resource recipients to olResponseAccepted (3)
2648
			for($i = 0, $len = count($resourceRecipients); $i < $len; $i++){
2649
				$resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusAccepted;
2650
				$resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS_TIME] = time();
2651
			}
2652
			mapi_message_modifyrecipients($message, MODRECIP_MODIFY, $resourceRecipients);
2653
		}
2654
2655
		return $resourceRecipData;
2656
	}
2657
2658
	/**
2659
	 * Function which save an exception into recurring item
2660
	 *
2661
	 * @param resource $recurringItem reference to MAPI_message of recurring item
2662
	 * @param resource $occurrenceItem reference to MAPI_message of occurrence
2663
	 * @param string $basedate basedate of occurrence
2664
	 * @param boolean $move if true then occurrence item is deleted
2665
	 * @param boolean $tentative true if user has tentatively accepted it or false if user has accepted it.
2666
	 * @param boolean $userAction true if user has manually responded to meeting request
2667
	 * @param resource $store user store
2668
	 * @param boolean $isDelegate true if delegate is processing this meeting request
2669
	 */
2670
	function acceptException(&$recurringItem, &$occurrenceItem, $basedate, $move = false, $tentative, $userAction = false, $store, $isDelegate = false)
2671
	{
2672
		$recurr = new Recurrence($store, $recurringItem);
2673
2674
		// Copy properties from meeting request
2675
		$exception_props = mapi_getprops($occurrenceItem);
2676
2677
		// Copy recipients list
2678
		$reciptable = mapi_message_getrecipienttable($occurrenceItem);
2679
		// If delegate, then do not add the delegate in recipients
2680
		if ($isDelegate) {
2681
			$delegate = mapi_getprops($this->message, array(PR_RECEIVED_BY_EMAIL_ADDRESS));
2682
			$res = array(RES_PROPERTY, array(
2683
											RELOP => RELOP_NE,
2684
											ULPROPTAG => PR_EMAIL_ADDRESS,
2685
											VALUE => array( PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS] )
2686
										)
2687
						);
2688
			$recips = mapi_table_queryallrows($reciptable, $this->recipprops, $res);
2689
		} else {
2690
			$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2691
		}
2692
2693
		// add owner to recipient table
2694
		$this->addOrganizer($exception_props, $recips, true);
2695
2696
		// add delegator to meetings
2697
		if ($isDelegate) {
2698
			$this->addDelegator($exception_props, $recips);
2699
		}
2700
2701
		$exception_props[$this->proptags['meetingstatus']] = olMeetingReceived;
2702
		$exception_props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
2703
2704
		if (isset($exception_props[$this->proptags['intendedbusystatus']])) {
2705
			if($tentative && $exception_props[$this->proptags['intendedbusystatus']] !== fbFree) {
2706
				$exception_props[$this->proptags['busystatus']] = fbTentative;
2707
			} else {
2708
				$exception_props[$this->proptags['busystatus']] = $exception_props[$this->proptags['intendedbusystatus']];
2709
			}
2710
			// we already have intendedbusystatus value in $exception_props so no need to copy it
2711
		} else {
2712
			$exception_props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
2713
		}
2714
2715
		if($userAction) {
2716
			$addrInfo = $this->getOwnerAddress($this->store);
2717
2718
			// if user has responded then set replytime and name
2719
			$exception_props[$this->proptags['replytime']] = time();
2720
			if(!empty($addrInfo)) {
2721
				$exception_props[$this->proptags['apptreplyname']] = $addrInfo[0];
2722
			}
2723
		}
2724
2725
		if($recurr->isException($basedate)) {
2726
			$recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
2727
		} else {
2728
			$recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
0 ignored issues
show
Bug introduced by
$basedate of type string is incompatible with the type date expected by parameter $base_date of Recurrence::createException(). ( Ignorable by Annotation )

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

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

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

2728
			$recurr->createException($exception_props, $basedate, false, $recips, /** @scrutinizer ignore-type */ $occurrenceItem);
Loading history...
2729
		}
2730
2731
		// Move the occurrenceItem to the waste basket
2732
		if ($move) {
2733
			$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
0 ignored issues
show
Bug introduced by
It seems like $this->openDefaultStore() can also be of type false; however, parameter $store of Meetingrequest::openDefaultWastebasket() does only seem to accept MAPIStore, maybe add an additional type check? ( Ignorable by Annotation )

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

2733
			$wastebasket = $this->openDefaultWastebasket(/** @scrutinizer ignore-type */ $this->openDefaultStore());
Loading history...
2734
			$sourcefolder = mapi_msgstore_openentry($store, $exception_props[PR_PARENT_ENTRYID]);
2735
			mapi_folder_copymessages($sourcefolder, Array($exception_props[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
2736
		}
2737
2738
		mapi_savechanges($recurringItem);
2739
	}
2740
2741
	/**
2742
	 * Function which merges an exception mapi message to recurring message.
2743
	 * This will be used when we receive recurring meeting request and we already have an exception message
2744
	 * of same meeting in calendar and we need to remove that exception message and add it to attachment table
2745
	 * of recurring meeting.
2746
	 *
2747
	 * @param resource $recurringItem reference to MAPI_message of recurring item
2748
	 * @param resource $occurrenceItem reference to MAPI_message of occurrence
2749
	 * @param string $basedate basedate of occurrence
2750
	 * @param resource $store user store
2751
	 */
2752
	function mergeException(&$recurringItem, &$occurrenceItem, $basedate, $store)
2753
	{
2754
		$recurr = new Recurrence($store, $recurringItem);
2755
2756
		// Copy properties from meeting request
2757
		$exception_props = mapi_getprops($occurrenceItem);
2758
2759
		// Get recipient list from message and add it to exception attachment
2760
		$reciptable = mapi_message_getrecipienttable($occurrenceItem);
2761
		$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2762
2763
		if($recurr->isException($basedate)) {
2764
			$recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
2765
		} else {
2766
			$recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
0 ignored issues
show
Bug introduced by
$occurrenceItem of type resource is incompatible with the type mapi_message expected by parameter $copy_attach_from of Recurrence::createException(). ( Ignorable by Annotation )

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

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

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

2766
			$recurr->createException($exception_props, /** @scrutinizer ignore-type */ $basedate, false, $recips, $occurrenceItem);
Loading history...
2767
		}
2768
2769
		// Move the occurrenceItem to the waste basket
2770
		$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
0 ignored issues
show
Bug introduced by
It seems like $this->openDefaultStore() can also be of type false; however, parameter $store of Meetingrequest::openDefaultWastebasket() does only seem to accept MAPIStore, maybe add an additional type check? ( Ignorable by Annotation )

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

2770
		$wastebasket = $this->openDefaultWastebasket(/** @scrutinizer ignore-type */ $this->openDefaultStore());
Loading history...
2771
		$sourcefolder = mapi_msgstore_openentry($store, $exception_props[PR_PARENT_ENTRYID]);
2772
		mapi_folder_copymessages($sourcefolder, Array($exception_props[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
2773
2774
		mapi_savechanges($recurringItem);
2775
	}
2776
2777
	/**
2778
	 * Function which submits meeting request based on arguments passed to it.
2779
	 * @param resource $message MAPI_message whose meeting request is to be send
2780
	 * @param boolean $cancel if true send request, else send cancellation
2781
	 * @param string $prefix subject prefix
2782
	 * @param integer $basedate basedate for an occurrence
2783
	 * @param Object $recurObject recurrence object of mr
2784
	 * @param boolean $copyExceptions When sending update mail for recurring item then we dont send exceptions in attachments
2785
	 */
2786
	function submitMeetingRequest($message, $cancel, $prefix, $basedate = false, $recurObject = false, $copyExceptions = true, $modifiedRecips = false, $deletedRecips = false)
2787
	{
2788
		$newmessageprops = $messageprops = mapi_getprops($this->message);
2789
		$new = $this->createOutgoingMessage();
2790
2791
		// Copy the entire message into the new meeting request message
2792
		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...
2793
			// messageprops contains properties of whole recurring series
2794
			// and newmessageprops contains properties of exception item
2795
			$newmessageprops = mapi_getprops($message);
2796
2797
			// Ensure that the correct basedate is set in the new message
2798
			$newmessageprops[$this->proptags['basedate']] = $basedate;
2799
2800
			// Set isRecurring to false, because this is an exception
2801
			$newmessageprops[$this->proptags['recurring']] = false;
2802
2803
			// set LID_IS_EXCEPTION to true
2804
			$newmessageprops[$this->proptags['is_exception']] = true;
2805
2806
			// Set to high importance
2807
			if($cancel) $newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;
2808
2809
			// Set startdate and enddate of exception
2810
			if ($cancel && $recurObject) {
2811
				$newmessageprops[$this->proptags['startdate']] = $recurObject->getOccurrenceStart($basedate);
2812
				$newmessageprops[$this->proptags['duedate']] = $recurObject->getOccurrenceEnd($basedate);
2813
			}
2814
2815
			// Set basedate in guid (0x3)
2816
			$newmessageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
2817
			$newmessageprops[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
2818
			$newmessageprops[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
2819
2820
			// Get deleted recipiets from exception msg
2821
			$restriction = Array(RES_AND,
2822
							Array(
2823
								Array(RES_BITMASK,
2824
									Array(
2825
											ULTYPE		=>	BMR_NEZ,
2826
											ULPROPTAG	=>	PR_RECIPIENT_FLAGS,
2827
											ULMASK		=>	recipExceptionalDeleted
2828
									)
2829
								),
2830
								Array(RES_BITMASK,
2831
									Array(
2832
											ULTYPE		=>	BMR_EQZ,
2833
											ULPROPTAG	=>	PR_RECIPIENT_FLAGS,
2834
											ULMASK		=>	recipOrganizer
2835
									)
2836
								),
2837
							)
2838
			);
2839
2840
			// In direct-booking mode, we don't need to send cancellations to resources
2841
			if($this->enableDirectBooking) {
2842
				$restriction[1][] = Array(RES_PROPERTY,
2843
										Array(RELOP => RELOP_NE,	// Does not equal recipient type: MAPI_BCC (Resource)
2844
											ULPROPTAG => PR_RECIPIENT_TYPE,
2845
											VALUE => array(PR_RECIPIENT_TYPE => MAPI_BCC)
2846
										)
2847
									);
2848
			}
2849
2850
			$recipienttable = mapi_message_getrecipienttable($message);
2851
			$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $restriction);
2852
2853
			if (!$deletedRecips) {
2854
				$deletedRecips = array_merge(array(), $recipients);
2855
			} else {
2856
				$deletedRecips = array_merge($deletedRecips, $recipients);
2857
			}
2858
		}
2859
2860
		// Remove the PR_ICON_INDEX as it is not needed in the sent message.
2861
		$newmessageprops[PR_ICON_INDEX] = null;
2862
		$newmessageprops[PR_RESPONSE_REQUESTED] = true;
2863
2864
		// PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
2865
		$newmessageprops[PR_START_DATE] = $newmessageprops[$this->proptags['startdate']];
2866
		$newmessageprops[PR_END_DATE] = $newmessageprops[$this->proptags['duedate']];
2867
2868
		// Set updatecounter/AppointmentSequenceNumber
2869
		// get the value of latest updatecounter for the whole series and use it
2870
		$newmessageprops[$this->proptags['updatecounter']] = $messageprops[$this->proptags['last_updatecounter']];
2871
2872
		$meetingTimeInfo = $this->getMeetingTimeInfo();
2873
2874
		if($meetingTimeInfo) {
2875
			// Needs to unset PR_HTML and PR_RTF_COMPRESSED props 
2876
			// because while canceling meeting requests with edit text 
2877
			// will override the PR_BODY because body value is not consistent with 
2878
			// PR_HTML and PR_RTF_COMPRESSED value so in this case PR_RTF_COMPRESSED will 
2879
			// get priority which override the PR_BODY value.
2880
			unset($newmessageprops[PR_HTML]);
2881
			unset($newmessageprops[PR_RTF_COMPRESSED]);
2882
2883
			$newmessageprops[PR_BODY] = $meetingTimeInfo;
2884
		}
2885
2886
		// Send all recurrence info in mail, if this is a recurrence meeting.
2887
		if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']]) {
2888
			if(!empty($messageprops[$this->proptags['recurring_pattern']])) {
2889
				$newmessageprops[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
2890
			}
2891
			$newmessageprops[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
2892
			$newmessageprops[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
2893
			$newmessageprops[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
2894
2895
			if($recurObject) {
2896
				$this->generateRecurDates($recurObject, $messageprops, $newmessageprops);
2897
			}
2898
		}
2899
2900
		if (isset($newmessageprops[$this->proptags['counter_proposal']])) {
2901
			unset($newmessageprops[$this->proptags['counter_proposal']]);
2902
		}
2903
2904
		// Prefix the subject if needed
2905
		if ($prefix && isset($newmessageprops[PR_SUBJECT]))
2906
			$newmessageprops[PR_SUBJECT] = $prefix . $newmessageprops[PR_SUBJECT];
2907
2908
		if(isset($newmessageprops[$this->proptags['categories']]) &&
2909
			!empty($newmessageprops[$this->proptags['categories']])) {
2910
			unset($newmessageprops[$this->proptags['categories']]);
2911
		}
2912
		mapi_setprops($new, $newmessageprops);
2913
2914
		// Copy attachments
2915
		$this->replaceAttachments($message, $new, $copyExceptions);
2916
2917
		// Retrieve only those recipient who should receive this meeting request.
2918
		$stripResourcesRestriction = Array(RES_AND,
2919
								Array(
2920
									Array(RES_BITMASK,
2921
										Array(	ULTYPE		=>	BMR_EQZ,
2922
												ULPROPTAG	=>	PR_RECIPIENT_FLAGS,
2923
												ULMASK		=>	recipExceptionalDeleted
2924
										)
2925
									),
2926
									Array(RES_BITMASK,
2927
										Array(	ULTYPE		=>	BMR_EQZ,
2928
												ULPROPTAG	=>	PR_RECIPIENT_FLAGS,
2929
												ULMASK		=>	recipOrganizer
2930
										)
2931
									),
2932
								)
2933
		);
2934
2935
		// In direct-booking mode, resources do not receive a meeting request
2936
		if($this->enableDirectBooking) {
2937
			$stripResourcesRestriction[1][] =
2938
									Array(RES_PROPERTY,
2939
										Array(RELOP => RELOP_NE,	// Does not equal recipient type: MAPI_BCC (Resource)
2940
											ULPROPTAG => PR_RECIPIENT_TYPE,
2941
											VALUE => array(PR_RECIPIENT_TYPE => MAPI_BCC)
2942
										)
2943
									);
2944
		}
2945
2946
		// If no recipients were explicitly provided, we will send the update to all
2947
		// recipients from the meeting.
2948
		if ($modifiedRecips === false) {
2949
			$recipienttable = mapi_message_getrecipienttable($message);
2950
			$modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
2951
2952
			if ($basedate && empty($modifiedRecips)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $basedate of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2953
				// Retrieve full list
2954
				$recipienttable = mapi_message_getrecipienttable($this->message);
2955
				$modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops);
2956
2957
				// Save recipients in exceptions
2958
				mapi_message_modifyrecipients($message, MODRECIP_ADD, $modifiedRecips);
2959
2960
				// Now retrieve only those recipient who should receive this meeting request.
2961
				$modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
2962
			}
2963
		}
2964
2965
		//@TODO: handle nonAcceptingResources
2966
		/**
2967
		 * Add resource recipients that did not automatically accept the meeting request.
2968
		 * (note: meaning that they did not decline the meeting request)
2969
		 *//*
2970
		for($i=0;$i<count($this->nonAcceptingResources);$i++){
2971
			$recipients[] = $this->nonAcceptingResources[$i];
2972
		}*/
2973
2974
		if(!empty($modifiedRecips)) {
2975
			// Strip out the sender/'owner' recipient
2976
			mapi_message_modifyrecipients($new, MODRECIP_ADD, $modifiedRecips);
2977
2978
			// Set some properties that are different in the sent request than
2979
			// in the item in our calendar
2980
2981
			// we should store busystatus value to intendedbusystatus property, because busystatus for outgoing meeting request
2982
			// should always be fbTentative
2983
			$newmessageprops[$this->proptags['intendedbusystatus']] = isset($newmessageprops[$this->proptags['busystatus']]) ? $newmessageprops[$this->proptags['busystatus']] : $messageprops[$this->proptags['busystatus']];
2984
			$newmessageprops[$this->proptags['busystatus']] = fbTentative; // The default status when not accepted
2985
			$newmessageprops[$this->proptags['responsestatus']] = olResponseNotResponded; // The recipient has not responded yet
2986
			$newmessageprops[$this->proptags['attendee_critical_change']] = time();
2987
			$newmessageprops[$this->proptags['owner_critical_change']] = time();
2988
			$newmessageprops[$this->proptags['meetingtype']] = mtgRequest;
2989
2990
			if ($cancel) {
2991
				$newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled';
2992
				$newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
2993
				$newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
2994
			} else {
2995
				$newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Request';
2996
				$newmessageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
2997
			}
2998
2999
			mapi_setprops($new, $newmessageprops);
3000
			mapi_savechanges($new);
3001
3002
			// Submit message to non-resource recipients
3003
			mapi_message_submitmessage($new);
3004
		}
3005
3006
3007
		// Search through the deleted recipients, and see if any of them is also
3008
		// listed as a recipient to whom we have send an update. As we don't
3009
		// want to send a cancellation message to recipients who will also receive
3010
		// an meeting update, we have to filter those recipients out.
3011
		if ($deletedRecips) {
3012
			$tmp = array();
3013
3014
			foreach ($deletedRecips as $delRecip) {
3015
				$found = false;
3016
3017
				// Search if the deleted recipient can be found inside
3018
				// the updated recipients as well.
3019
				foreach ($modifiedRecips as $recip) {
3020
					if ($this->compareABEntryIDs($recip[PR_ENTRYID], $delRecip[PR_ENTRYID])) {
3021
						$found = true;
3022
						break;
3023
					}
3024
				}
3025
3026
				// If the recipient was not found, it truly is deleted,
3027
				// and we can safely send a cancellation message
3028
				if (!$found) {
3029
					$tmp[] = $delRecip;
3030
				}
3031
			}
3032
3033
			$deletedRecips = $tmp;
3034
		}
3035
3036
		// Send cancellation to deleted attendees
3037
		if ($deletedRecips && !empty($deletedRecips)) {
3038
			$new = $this->createOutgoingMessage();
3039
3040
			mapi_message_modifyrecipients($new, MODRECIP_ADD, $deletedRecips);
3041
3042
			$newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled';
3043
			$newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
3044
			$newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
3045
			$newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;	// HIGH Importance
3046
			if (isset($newmessageprops[PR_SUBJECT])) {
3047
				$newmessageprops[PR_SUBJECT] = dgettext('zarafa','Canceled') . ': ' . $newmessageprops[PR_SUBJECT];
3048
			}
3049
3050
			mapi_setprops($new, $newmessageprops);
3051
			mapi_savechanges($new);
3052
3053
			// Submit message to non-resource recipients
3054
			mapi_message_submitmessage($new);
3055
		}
3056
3057
		// Set properties on meeting object in calendar
3058
		// Set requestsent to 'true' (turns on 'tracking', etc)
3059
		$props = array();
3060
		$props[$this->proptags['meetingstatus']] = olMeeting;
3061
		$props[$this->proptags['responsestatus']] = olResponseOrganized;
3062
		// Only set the 'requestsent' property if it wasn't set previously yet,
3063
		// this ensures we will not accidentally set it from true to false.
3064
		if (!isset($messageprops[$this->proptags['requestsent']]) || $messageprops[$this->proptags['requestsent']] !== true) {
3065
			$props[$this->proptags['requestsent']] = !empty($modifiedRecips) || ($this->includesResources && !$this->errorSetResource);
3066
		}
3067
		$props[$this->proptags['attendee_critical_change']] = time();
3068
		$props[$this->proptags['owner_critical_change']] = time();
3069
		$props[$this->proptags['meetingtype']] = mtgRequest;
3070
		// save the new updatecounter to exception/recurring series/normal meeting
3071
		$props[$this->proptags['updatecounter']] = $newmessageprops[$this->proptags['updatecounter']];
3072
3073
		// PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
3074
		$props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
3075
		$props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
3076
3077
		mapi_setprops($message, $props);
3078
3079
		// saving of these properties on calendar item should be handled by caller function
3080
		// based on sending meeting request was successful or not
3081
	}
3082
3083
	/**
3084
	 * OL2007 uses these 4 properties to specify occurrence that should be updated.
3085
	 * ical generates RECURRENCE-ID property based on exception's basedate (PidLidExceptionReplaceTime),
3086
	 * but OL07 doesn't send this property, so ical will generate RECURRENCE-ID property based on date
3087
	 * from GlobalObjId and time from StartRecurTime property, so we are sending basedate property and
3088
	 * also additionally we are sending these properties.
3089
	 * Ref: MS-OXCICAL 2.2.1.20.20 Property: RECURRENCE-ID
3090
	 * @param Object $recurObject instance of recurrence class for this message
3091
	 * @param Array $messageprops properties of meeting object that is going to be send
3092
	 * @param Array $newmessageprops properties of meeting request/response that is going to be send
3093
	 */
3094
	function generateRecurDates($recurObject, $messageprops, &$newmessageprops)
3095
	{
3096
		if($messageprops[$this->proptags['startdate']] && $messageprops[$this->proptags['duedate']]) {
3097
			$startDate = date('Y:n:j:G:i:s', $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['startdate']]));
3098
			$endDate = date('Y:n:j:G:i:s', $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['duedate']]));
3099
3100
			$startDate = explode(':', $startDate);
3101
			$endDate = explode(':', $endDate);
3102
3103
			// [0] => year, [1] => month, [2] => day, [3] => hour, [4] => minutes, [5] => seconds
3104
			// RecurStartDate = year * 512 + month_number * 32 + day_number
3105
			$newmessageprops[$this->proptags['start_recur_date']] = (((int) $startDate[0]) * 512) + (((int) $startDate[1]) * 32) + ((int) $startDate[2]);
3106
			// RecurStartTime = hour * 4096 + minutes * 64 + seconds
3107
			$newmessageprops[$this->proptags['start_recur_time']] = (((int) $startDate[3]) * 4096) + (((int) $startDate[4]) * 64) + ((int) $startDate[5]);
3108
3109
			$newmessageprops[$this->proptags['end_recur_date']] = (((int) $endDate[0]) * 512) + (((int) $endDate[1]) * 32) + ((int) $endDate[2]);
3110
			$newmessageprops[$this->proptags['end_recur_time']] = (((int) $endDate[3]) * 4096) + (((int) $endDate[4]) * 64) + ((int) $endDate[5]);
3111
		}
3112
	}
3113
3114
	/**
3115
	 * Function will create a new outgoing message that will be used to send meeting mail.
3116
	 * @param MAPIStore $store (optional) store that is used when creating response, if delegate is creating outgoing mail
3117
	 * then this would point to delegate store.
3118
	 * @return MAPIMessage outgoing mail that is created and can be used for sending it.
3119
	 */
3120
	function createOutgoingMessage($store = false)
3121
	{
3122
		// get logged in user's store that will be used to send mail, for delegate this will be
3123
		// delegate store
3124
		$userStore = $this->openDefaultStore();
3125
3126
		$sentprops = array();
3127
		$outbox = $this->openDefaultOutbox($userStore);
0 ignored issues
show
Bug introduced by
It seems like $userStore can also be of type false; however, parameter $store of Meetingrequest::openDefaultOutbox() does only seem to accept MAPIStore, maybe add an additional type check? ( Ignorable by Annotation )

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

3127
		$outbox = $this->openDefaultOutbox(/** @scrutinizer ignore-type */ $userStore);
Loading history...
3128
3129
		$outgoing = mapi_folder_createmessage($outbox);
3130
3131
		// check if $store is set and it is not equal to $defaultStore (means its the delegation case)
3132
		if($store !== false) {
3133
			$storeProps = mapi_getprops($store, array(PR_ENTRYID));
3134
			$userStoreProps = mapi_getprops($userStore, array(PR_ENTRYID));
3135
3136
			// @FIXME use entryid comparison functions here
3137
			if($storeProps[PR_ENTRYID] !== $userStoreProps[PR_ENTRYID]) {
3138
				// get the delegator properties and set it into outgoing mail
3139
				$delegatorDetails = $this->getOwnerAddress($store, false);
3140
3141
				if($delegatorDetails) {
0 ignored issues
show
introduced by
$delegatorDetails is of type properties, thus it always evaluated to true.
Loading history...
3142
					list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $delegatorDetails;
3143
					$sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
3144
					$sentprops[PR_SENT_REPRESENTING_NAME] = $ownername;
3145
					$sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
3146
					$sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
3147
					$sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
3148
				}
3149
3150
				// get the delegate properties and set it into outgoing mail
3151
				$delegateDetails = $this->getOwnerAddress($userStore, false);
3152
3153
				if($delegateDetails) {
0 ignored issues
show
introduced by
$delegateDetails is of type properties, thus it always evaluated to true.
Loading history...
3154
					list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $delegateDetails;
3155
					$sentprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
3156
					$sentprops[PR_SENDER_NAME] = $ownername;
3157
					$sentprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
3158
					$sentprops[PR_SENDER_ENTRYID] = $ownerentryid;
3159
					$sentprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
3160
				}
3161
			}
3162
		} else {
3163
			// normal user is sending mail, so both set of properties will be same
3164
			$userDetails = $this->getOwnerAddress($userStore);
3165
3166
			if($userDetails) {
0 ignored issues
show
introduced by
$userDetails is of type properties, thus it always evaluated to true.
Loading history...
3167
				list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $userDetails;
3168
				$sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
3169
				$sentprops[PR_SENT_REPRESENTING_NAME] = $ownername;
3170
				$sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
3171
				$sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
3172
				$sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
3173
3174
				$sentprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
3175
				$sentprops[PR_SENDER_NAME] = $ownername;
3176
				$sentprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
3177
				$sentprops[PR_SENDER_ENTRYID] = $ownerentryid;
3178
				$sentprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
3179
			}
3180
		}
3181
3182
		$sentprops[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID($userStore);
0 ignored issues
show
Bug introduced by
It seems like $userStore can also be of type false; however, parameter $store of Meetingrequest::getDefaultSentmailEntryID() does only seem to accept MAPIStore, maybe add an additional type check? ( Ignorable by Annotation )

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

3182
		$sentprops[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID(/** @scrutinizer ignore-type */ $userStore);
Loading history...
3183
3184
		mapi_setprops($outgoing, $sentprops);
3185
3186
		return $outgoing;
3187
	}
3188
3189
	/**
3190
	 * Function which checks that meeting in attendee's calendar is already updated
3191
	 * and we are checking an old meeting request. This function also will update property
3192
	 * meetingtype to indicate that its out of date meeting request.
3193
	 * @return boolean true if meeting request is outofdate else false if it is new
3194
	 */
3195
	function isMeetingOutOfDate()
3196
	{
3197
		$result = false;
3198
3199
		$props = mapi_getprops($this->message, array(PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], $this->proptags['updatecounter'], $this->proptags['meetingtype'], $this->proptags['owner_critical_change']));
3200
3201
		if(!$this->isMeetingRequest($props[PR_MESSAGE_CLASS])) {
3202
			return $result;
3203
		}
3204
3205
		if (isset($props[$this->proptags['meetingtype']]) && ($props[$this->proptags['meetingtype']] & mtgOutOfDate) == mtgOutOfDate) {
3206
			return true;
3207
		}
3208
3209
		// get the basedate to check for exception
3210
		$basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]);
3211
3212
		$calendarItem = $this->getCorrespondentCalendarItem(true);
3213
3214
		// if basedate is provided and we could not find the item then it could be that we are checking
3215
		// an exception so get the exception and check it
3216
		if($basedate && $calendarItem !== false) {
3217
			$exception = $this->getExceptionItem($calendarItem, $basedate);
0 ignored issues
show
Bug introduced by
$basedate of type true is incompatible with the type Unixtime expected by parameter $basedate of Meetingrequest::getExceptionItem(). ( Ignorable by Annotation )

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

3217
			$exception = $this->getExceptionItem($calendarItem, /** @scrutinizer ignore-type */ $basedate);
Loading history...
3218
3219
			if($exception !== false) {
0 ignored issues
show
introduced by
The condition $exception !== false is always true.
Loading history...
3220
				// we are able to find the exception compare with it
3221
				$calendarItem = $exception;
3222
			} else {
3223
				// we are not able to find exception, could mean that a significant change has occurred on series
3224
				// and it deleted all exceptions, so compare with series
3225
				// $calendarItem already contains reference to series
3226
			}
3227
		}
3228
3229
		if($calendarItem !== false) {
0 ignored issues
show
introduced by
The condition $calendarItem !== false is always true.
Loading history...
3230
			$calendarItemProps = mapi_getprops($calendarItem, array(
3231
													$this->proptags['owner_critical_change'],
3232
													$this->proptags['updatecounter']
3233
												));
3234
3235
			$updateCounter = (isset($calendarItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]);
3236
3237
			$criticalChange = (isset($calendarItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $calendarItemProps[$this->proptags['owner_critical_change']]);
3238
3239
			if($updateCounter || $criticalChange) {
3240
				// meeting request is out of date, set properties to indicate this
3241
				mapi_setprops($this->message, array($this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033));
3242
				mapi_savechanges($this->message);
3243
3244
				$result = true;
3245
			}
3246
		}
3247
3248
		return $result;
3249
	}
3250
3251
	/**
3252
	 * Function which checks that if we have received a meeting response for an updated meeting in organizer's calendar.
3253
	 * @param Number $basedate basedate of the exception if we want to compare with exception.
3254
	 * @return Boolean true if meeting request is updated later.
3255
	 */
3256
	function isMeetingUpdated($basedate = false)
3257
	{
3258
		$result = false;
3259
3260
		$props = mapi_getprops($this->message, array(PR_MESSAGE_CLASS, $this->proptags['updatecounter']));
3261
3262
		if(!$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS])) {
3263
			return $result;
3264
		}
3265
3266
		$calendarItem = $this->getCorrespondentCalendarItem(true);
3267
3268
		if($calendarItem !== false) {
0 ignored issues
show
introduced by
The condition $calendarItem !== false is always true.
Loading history...
3269
			// basedate is provided so open exception
3270
			if($basedate !== false) {
3271
				$exception = $this->getExceptionItem($calendarItem, $basedate);
0 ignored issues
show
Bug introduced by
$basedate of type double|integer is incompatible with the type Unixtime expected by parameter $basedate of Meetingrequest::getExceptionItem(). ( Ignorable by Annotation )

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

3271
				$exception = $this->getExceptionItem($calendarItem, /** @scrutinizer ignore-type */ $basedate);
Loading history...
3272
3273
				if($exception !== false) {
0 ignored issues
show
introduced by
The condition $exception !== false is always true.
Loading history...
3274
					// we are able to find the exception compare with it
3275
					$calendarItem = $exception;
3276
				} else {
3277
					// we are not able to find exception, could mean that a significant change has occurred on series
3278
					// and it deleted all exceptions, so compare with series
3279
					// $calendarItem already contains reference to series
3280
				}
3281
			}
3282
3283
			if($calendarItem !== false) {
0 ignored issues
show
introduced by
The condition $calendarItem !== false is always true.
Loading history...
3284
				$calendarItemProps = mapi_getprops($calendarItem, array($this->proptags['updatecounter']));
3285
3286
				/*
3287
				 * if(message_counter < appointment_counter) meeting object is newer then meeting response (meeting is updated)
3288
				 * if(message_counter >= appointment_counter) meeting is not updated, do normal processing
3289
				 */
3290
				if(isset($calendarItemProps[$this->proptags['updatecounter']]) && isset($props[$this->proptags['updatecounter']])) {
3291
					if($props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]) {
3292
						$result = true;
3293
					}
3294
				}
3295
			}
3296
		}
3297
3298
		return $result;
3299
	}
3300
3301
	/**
3302
	 * Checks if there has been any significant changes on appointment/meeting item.
3303
	 * Significant changes be:
3304
	 * 1) startdate has been changed
3305
	 * 2) duedate has been changed OR
3306
	 * 3) recurrence pattern has been created, modified or removed
3307
	 *
3308
	 * @param Array oldProps old props before an update
0 ignored issues
show
Bug introduced by
The type oldProps was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
3309
	 * @param Number basedate basedate
3310
	 * @param Boolean isRecurrenceChanged for change in recurrence pattern.
0 ignored issues
show
Bug introduced by
The type isRecurrenceChanged was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
3311
	 * isRecurrenceChanged true means Recurrence pattern has been changed, so clear all attendees response
3312
	 */
3313
	function checkSignificantChanges($oldProps, $basedate, $isRecurrenceChanged = false)
3314
	{
3315
		$message = null;
3316
		$attach = null;
3317
3318
		// If basedate is specified then we need to open exception message to clear recipient responses
3319
		if($basedate) {
3320
			$recurrence = new Recurrence($this->store, $this->message);
3321
			if($recurrence->isException($basedate)){
3322
				$attach = $recurrence->getExceptionAttachment($basedate);
3323
				if ($attach) {
3324
					$message = mapi_attach_openobj($attach, MAPI_MODIFY);
3325
				}
3326
			}
3327
		} else {
3328
			// use normal message or recurring series message
3329
			$message = $this->message;
3330
		}
3331
3332
		if(!$message) {
3333
			return;
3334
		}
3335
3336
		$newProps = mapi_getprops($message, array($this->proptags['startdate'], $this->proptags['duedate'], $this->proptags['updatecounter']));
3337
3338
		// Check whether message is updated or not.
3339
		if(isset($newProps[$this->proptags['updatecounter']]) && $newProps[$this->proptags['updatecounter']] == 0) {
3340
			return;
3341
		}
3342
3343
		if (($newProps[$this->proptags['startdate']] != $oldProps[$this->proptags['startdate']])
3344
			|| ($newProps[$this->proptags['duedate']] != $oldProps[$this->proptags['duedate']])
3345
			|| $isRecurrenceChanged) {
3346
			$this->clearRecipientResponse($message);
3347
3348
			mapi_setprops($message, array($this->proptags['owner_critical_change'] => time()));
3349
3350
			mapi_savechanges($message);
3351
			if ($attach) { // Also save attachment Object.
3352
				mapi_savechanges($attach);
3353
			}
3354
		}
3355
	}
3356
3357
	/**
3358
	 * Clear responses of all attendees who have replied in past.
3359
	 * @param MAPI_MESSAGE $message on which responses should be cleared
0 ignored issues
show
Bug introduced by
The type MAPI_MESSAGE was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
3360
	 */
3361
	function clearRecipientResponse($message)
3362
	{
3363
		$recipTable = mapi_message_getrecipienttable($message);
3364
		$recipsRows = mapi_table_queryallrows($recipTable, $this->recipprops);
3365
3366
		foreach($recipsRows as $recipient) {
3367
			if(($recipient[PR_RECIPIENT_FLAGS] & recipOrganizer) != recipOrganizer){
3368
				// Recipient is attendee, set the trackstatus to 'Not Responded'
3369
				$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3370
			} else {
3371
				// Recipient is organizer, this is not possible, but for safety
3372
				// it is best to clear the trackstatus for him as well by setting
3373
				// the trackstatus to 'Organized'.
3374
				$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3375
			}
3376
			mapi_message_modifyrecipients($message, MODRECIP_MODIFY, array($recipient));
3377
		}
3378
	}
3379
3380
	/**
3381
	 * Function returns correspondent calendar item attached with the meeting request/response/cancellation.
3382
	 * This will only check for actual MAPIMessages in calendar folder, so if a meeting request is
3383
	 * for exception then this function will return recurring series for that meeting request
3384
	 * after that you need to use getExceptionItem function to get exception item that will be
3385
	 * fetched from the attachment table of recurring series MAPIMessage.
3386
	 * @param Boolean $open boolean to indicate the function should return entryid or MAPIMessage. Defaults to true.
3387
	 * @return entryid or MAPIMessage resource of calendar item.
3388
	 */
3389
	function getCorrespondentCalendarItem($open = true)
3390
	{
3391
		$props = mapi_getprops($this->message, array(PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_ENTRYID));
3392
3393
		if(!$this->isMeetingRequest($props[PR_MESSAGE_CLASS]) && !$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS]) && !$this->isMeetingCancellation($props[PR_MESSAGE_CLASS])) {
3394
			// can work only with meeting requests/responses/cancellations
3395
			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...
3396
		}
3397
3398
		$globalId = $props[$this->proptags['goid']];
3399
		$cleanGlobalId = $props[$this->proptags['goid2']];
3400
3401
		// If Delegate is processing Meeting Request/Response for Delegator then retrieve Delegator's store and calendar.
3402
		if (isset($props[PR_RCVD_REPRESENTING_ENTRYID])) {
3403
			$delegatorStore = $this->getDelegatorStore($props[PR_RCVD_REPRESENTING_ENTRYID], array(PR_IPM_APPOINTMENT_ENTRYID));
3404
3405
			$store = $delegatorStore['store'];
3406
			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
3407
		} else {
3408
			$store = $this->store;
3409
			$calFolder = $this->openDefaultCalendar();
3410
		}
3411
3412
		$basedate = $this->getBasedateFromGlobalID($globalId);
3413
3414
		/**
3415
		 * First search for any appointments which correspond to the $globalId,
3416
		 * this can be the entire series (if the Meeting Request refers to the
3417
		 * entire series), or an particular Occurrence (if the meeting Request
3418
		 * contains a basedate).
3419
		 *
3420
		 * If we cannot find a corresponding item, and the $globalId contains
3421
		 * a $basedate, it might imply that a new exception will have to be
3422
		 * created for a series which is present in the calendar, we can look
3423
		 * that one up by searching for the $cleanGlobalId.
3424
		 */
3425
		$entryids = $this->findCalendarItems($globalId, $calFolder);
3426
		if ($basedate && empty($entryids)) {
3427
			$entryids = $this->findCalendarItems($cleanGlobalId, $calFolder, true);
3428
		}
3429
3430
		// there should be only one item returned
3431
		if(!empty($entryids) && count($entryids) === 1) {
3432
			// return only entryid
3433
			if($open === false) {
3434
				return $entryids[0];
3435
			}
3436
3437
			// open calendar item and return it
3438
			return mapi_msgstore_openentry($store, $entryids[0]);
3439
		}
3440
3441
		// no items found in calendar
3442
		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...
3443
	}
3444
3445
	/**
3446
	 * Function returns exception item based on the basedate passed.
3447
	 * @param MAPIMessage $recurringMessage Resource of Recurring meeting from calendar
3448
	 * @param Unixtime $basedate basedate of exception that needs to be returned.
0 ignored issues
show
Bug introduced by
The type Unixtime was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
3449
	 * @param MAPIStore $store store that contains the recurring calendar item.
3450
	 * @return entryid or MAPIMessage resource of exception item.
3451
	 */
3452
	function getExceptionItem($recurringMessage, $basedate, $store = false)
3453
	{
3454
		$occurItem = false;
3455
3456
		$props = mapi_getprops($this->message, array(PR_RCVD_REPRESENTING_ENTRYID, $this->proptags['recurring']));
3457
3458
		// check if the passed item is recurring series
3459
		if($props[$this->proptags['recurring']] !== false) {
3460
			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...
3461
		}
3462
3463
		if($store === false) {
3464
			// If Delegate is processing Meeting Request/Response for Delegator then retrieve Delegator's store and calendar.
3465
			if (isset($props[PR_RCVD_REPRESENTING_ENTRYID])) {
3466
				$delegatorStore = $this->getDelegatorStore($props[PR_RCVD_REPRESENTING_ENTRYID]);
3467
				$store = $delegatorStore['store'];
3468
			} else {
3469
				$store = $this->store;
3470
			}
3471
		}
3472
3473
		$recurr = new Recurrence($store, $recurringMessage);
0 ignored issues
show
Bug introduced by
$recurringMessage of type MAPIMessage is incompatible with the type resource expected by parameter $message of Recurrence::__construct(). ( Ignorable by Annotation )

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

3473
		$recurr = new Recurrence($store, /** @scrutinizer ignore-type */ $recurringMessage);
Loading history...
3474
		$attach = $recurr->getExceptionAttachment($basedate);
3475
		if($attach) {
3476
			$occurItem = mapi_attach_openobj($attach);
3477
		}
3478
3479
		return $occurItem;
3480
	}
3481
3482
	/**
3483
	 * Function which checks whether received meeting request is either conflicting with other appointments or not.
3484
	 * @return mixed(boolean/integer) true if normal meeting is conflicting or an integer which specifies no of instances
3485
	 * conflict of recurring meeting and false if meeting is not conflicting.
3486
	 * @param MAPIMessage $message meeting request item that should be checked for conflicts in calendar
3487
	 * @param MAPIStore $userStore store containing calendar folder that will be used for confilict checking
3488
	 * @param MAPIFolder $calFolder calendar folder for conflict checking
3489
	 * @return Mixed if boolean then true/false for indicating conflict, if number then items that are conflicting with the message.
3490
	 */
3491
	function isMeetingConflicting($message = false, $userStore = false, $calFolder = false)
3492
	{
3493
		$returnValue = false;
3494
		$noOfInstances = 0;
3495
3496
		if($message === false) {
3497
			$message = $this->message;
3498
		}
3499
3500
		$messageProps = mapi_getprops($message, array(
3501
					PR_MESSAGE_CLASS,
3502
					$this->proptags['goid'],
3503
					$this->proptags['goid2'],
3504
					$this->proptags['startdate'],
3505
					$this->proptags['duedate'],
3506
					$this->proptags['recurring'],
3507
					$this->proptags['clipstart'],
3508
					$this->proptags['clipend'],
3509
					PR_RCVD_REPRESENTING_ENTRYID,
3510
					$this->proptags['basedate'],
3511
					PR_RCVD_REPRESENTING_NAME
3512
				)
3513
		);
3514
3515
		if($userStore === false) {
3516
			$userStore = $this->store;
3517
3518
			// check if delegate is processing the response
3519
			if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) {
3520
				$delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID], array(PR_IPM_APPOINTMENT_ENTRYID));
3521
3522
				$userStore = $delegatorStore['store'];
3523
				$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
3524
			}
3525
		}
3526
3527
		if($calFolder === false) {
3528
			$calFolder = $this->openDefaultCalendar($userStore);
3529
		}
3530
3531
		if($calFolder) {
3532
			// Meeting request is recurring, so get all occurrence and check for each occurrence whether it conflicts with other appointments in Calendar.
3533
			if (isset($messageProps[$this->proptags['recurring']]) && $messageProps[$this->proptags['recurring']] === true) {
3534
				// Apply recurrence class and retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date')
3535
				$recurr = new Recurrence($userStore, $message);
3536
				$items = $recurr->getItems($messageProps[$this->proptags['clipstart']], $messageProps[$this->proptags['clipend']] * (24*24*60), 30);
0 ignored issues
show
Bug introduced by
$messageProps[$this->pro...ipend']] * 24 * 24 * 60 of type integer is incompatible with the type date expected by parameter $end of BaseRecurrence::getItems(). ( Ignorable by Annotation )

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

3536
				$items = $recurr->getItems($messageProps[$this->proptags['clipstart']], /** @scrutinizer ignore-type */ $messageProps[$this->proptags['clipend']] * (24*24*60), 30);
Loading history...
3537
3538
				foreach ($items as $item) {
3539
					// Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
3540
					$calendarItems = $recurr->getCalendarItems($userStore, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], array($this->proptags['goid'], $this->proptags['busystatus']));
3541
3542
					foreach ($calendarItems as $calendarItem) {
3543
						if ($calendarItem[$this->proptags['busystatus']] !== fbFree) {
3544
							/**
3545
							 * Only meeting requests have globalID, normal appointments do not have globalID
3546
							 * so if any normal appointment if found then it is assumed to be conflict.
3547
							 */
3548
							if(isset($calendarItem[$this->proptags['goid']])) {
3549
								if ($calendarItem[$this->proptags['goid']] !== $messageProps[$this->proptags['goid']]) {
3550
									$noOfInstances++;
3551
									break;
3552
								}
3553
							} else {
3554
								$noOfInstances++;
3555
								break;
3556
							}
3557
						}
3558
					}
3559
				}
3560
3561
				if($noOfInstances > 0) {
3562
					$returnValue = $noOfInstances;
3563
				}
3564
			} else {
3565
				// Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
3566
				$items = getCalendarItems($userStore, $calFolder, $messageProps[$this->proptags['startdate']], $messageProps[$this->proptags['duedate']], array($this->proptags['goid'], $this->proptags['busystatus']));
3567
3568
				if(isset($messageProps[$this->proptags['basedate']]) && !empty($messageProps[$this->proptags['basedate']])) {
3569
					$basedate = $messageProps[$this->proptags['basedate']];
3570
					// Get the goid2 from recurring MR which further used to
3571
					// check the resource conflicts item.
3572
					$recurrItemProps = mapi_getprops($this->message, array($this->proptags['goid2']));
3573
					$messageProps[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid2']], $basedate);
3574
					$messageProps[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']];
3575
				}
3576
3577
				foreach($items as $item) {
3578
					if ($item[$this->proptags['busystatus']] !== fbFree) {
3579
						if(isset($item[$this->proptags['goid']])) {
3580
							if (($item[$this->proptags['goid']] !== $messageProps[$this->proptags['goid']])
3581
								&& ($item[$this->proptags['goid']] !== $messageProps[$this->proptags['goid2']])) {
3582
								$returnValue = true;
3583
								break;
3584
							}
3585
						} else {
3586
							$returnValue = true;
3587
							break;
3588
						}
3589
					}
3590
				}
3591
			}
3592
		}
3593
3594
		return $returnValue;
3595
	}
3596
3597
	/**
3598
	 *  Function which adds organizer to recipient list which is passed.
3599
	 *  This function also checks if it has organizer.
3600
	 *
3601
	 * @param array $messageProps message properties
3602
	 * @param array $recipients	recipients list of message.
3603
	 * @param boolean $isException true if we are processing recipient of exception
3604
	 */
3605
	function addDelegator($messageProps, &$recipients)
3606
	{
3607
		$hasDelegator = false;
3608
		// Check if meeting already has an organizer.
3609
		foreach ($recipients as $key => $recipient){
3610
			if (isset($messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) && $recipient[PR_EMAIL_ADDRESS] == $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS])
3611
				$hasDelegator = true;
3612
		}
3613
3614
		if (!$hasDelegator){
3615
			// Create delegator.
3616
			$delegator = array();
3617
			$delegator[PR_ENTRYID] = $messageProps[PR_RCVD_REPRESENTING_ENTRYID];
3618
			$delegator[PR_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
3619
			$delegator[PR_EMAIL_ADDRESS] = $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS];
3620
			$delegator[PR_RECIPIENT_TYPE] = MAPI_TO;
3621
			$delegator[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
3622
			$delegator[PR_ADDRTYPE] = empty($messageProps[PR_RCVD_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_RCVD_REPRESENTING_ADDRTYPE];
3623
			$delegator[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3624
			$delegator[PR_RECIPIENT_FLAGS] = recipSendable;
3625
			$delegator[PR_SEARCH_KEY] = $messageProps[PR_RCVD_REPRESENTING_SEARCH_KEY];
3626
3627
			// Add organizer to recipients list.
3628
			array_unshift($recipients, $delegator);
3629
		}
3630
	}
3631
3632
	/**
3633
	 * Function will return delegator's store and calendar folder for processing meetings
3634
	 * @param String $receivedRepresentingEnryid entryid of the delegator user
3635
	 * @param Array $foldersToOpen contains list of folder types that should be returned in result
3636
	 * @return Array contains store of the delegator and resource of folders if $foldersToOpen is not empty
3637
	 */
3638
	function getDelegatorStore($receivedRepresentingEntryId, $foldersToOpen = array())
3639
	{
3640
		$returnData = Array();
3641
3642
		$delegatorStore = $this->openCustomUserStore($receivedRepresentingEntryId);
3643
		$returnData['store'] = $delegatorStore;
3644
3645
		if(!empty($foldersToOpen)) {
3646
			for($index = 0, $len = count($foldersToOpen); $index < $len; $index++) {
3647
				$folderType = $foldersToOpen[$index];
3648
3649
				// first try with default folders
3650
				$folder = $this->openDefaultFolder($folderType, $delegatorStore);
3651
3652
				// if folder not found then try with base folders
3653
				if($folder === false) {
3654
					$folder = $this->openBaseFolder($folderType, $delegatorStore);
3655
				}
3656
3657
				if($folder === false) {
3658
					// we are still not able to get the folder so give up
3659
					continue;
3660
				}
3661
3662
				$returnData[$folderType] = $folder;
3663
			}
3664
		}
3665
3666
		return $returnData;
3667
	}
3668
3669
	/**
3670
	 * Function returns extra info about meeting timing along with message body
3671
	 * which will be included in body while sending meeting request/response.
3672
	 *
3673
	 * @return string $meetingTimeInfo info about meeting timing along with message body
3674
	 */
3675
	function getMeetingTimeInfo()
3676
	{
3677
		return $this->meetingTimeInfo;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->meetingTimeInfo returns the type boolean which is incompatible with the documented return type string.
Loading history...
3678
	}
3679
3680
	/**
3681
	 * Function sets extra info about meeting timing along with message body
3682
	 * which will be included in body while sending meeting request/response.
3683
	 *
3684
	 * @param string $meetingTimeInfo info about meeting timing along with message body
3685
	 */
3686
	function setMeetingTimeInfo($meetingTimeInfo)
3687
	{
3688
		$this->meetingTimeInfo = $meetingTimeInfo;
0 ignored issues
show
Bug Best Practice introduced by
The property meetingTimeInfo does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
3689
	}
3690
3691
	/**
3692
	 * Helper function which is use to get local categories of all occurrence.
3693
	 *
3694
	 * @param MAPIMessage $calendarItem meeting request item
3695
	 * @param MAPIStore $store store containing calendar folder
3696
	 * @param MAPIFolder $calFolder calendar folder
3697
	 * @return Array $localCategories which contain array of basedate along with categories
3698
	 */
3699
	function getLocalCategories($calendarItem, $store, $calFolder)
3700
	{
3701
		$calendarItemProps = mapi_getprops($calendarItem);
3702
		$recurrence = new Recurrence($store, $calendarItem);
0 ignored issues
show
Bug introduced by
$calendarItem of type MAPIMessage is incompatible with the type resource expected by parameter $message of Recurrence::__construct(). ( Ignorable by Annotation )

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

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

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

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

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

3705
		$items = $recurrence->getItems($calendarItemProps[$this->proptags['clipstart']], /** @scrutinizer ignore-type */ $calendarItemProps[$this->proptags['clipend']] * (24*24*60), 30);
Loading history...
3706
		$localCategories = array();
3707
3708
		foreach ($items as $item) {
3709
			$recurrenceItems = $recurrence->getCalendarItems($store, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], array($this->proptags['goid'], $this->proptags['busystatus'], $this->proptags['categories']));
3710
			foreach ($recurrenceItems as $recurrenceItem) {
3711
3712
				// Check if occurrence is exception then get the local categories of that occurrence.
3713
				if (isset($recurrenceItem[$this->proptags['goid']]) && $recurrenceItem[$this->proptags['goid']] == $calendarItemProps[$this->proptags['goid']]) {
3714
3715
					$exceptionAttach = $recurrence->getExceptionAttachment($recurrenceItem['basedate']);
3716
3717
					if ($exceptionAttach) {
3718
						$exception = mapi_attach_openobj($exceptionAttach, 0);
3719
						$exceptionProps = mapi_getprops($exception, array($this->proptags['categories']));
3720
						if (isset($exceptionProps[$this->proptags['categories']])) {
3721
							$localCategories[$recurrenceItem['basedate']] = $exceptionProps[$this->proptags['categories']];
3722
						}
3723
					}
3724
				}
3725
			}
3726
		}
3727
3728
		return $localCategories;
3729
	}
3730
3731
	/**
3732
	 * Helper function which is use to apply local categories on respective occurrences.
3733
	 *
3734
	 * @param MAPIMessage $calendarItem meeting request item
3735
	 * @param MAPIStore $store store containing calendar folder
3736
	 * @param Array $localCategories array contains basedate and array of categories
3737
	 */
3738
	function applyLocalCategories($calendarItem, $store, $localCategories)
3739
	{
3740
		$calendarItemProps = mapi_getprops($calendarItem, array(PR_PARENT_ENTRYID, PR_ENTRYID));
3741
		$message = mapi_msgstore_openentry($store, $calendarItemProps[PR_ENTRYID]);
3742
		$recurrence = new Recurrence($store, $message);
0 ignored issues
show
Bug introduced by
$store of type MAPIStore is incompatible with the type resource expected by parameter $store of Recurrence::__construct(). ( Ignorable by Annotation )

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

3742
		$recurrence = new Recurrence(/** @scrutinizer ignore-type */ $store, $message);
Loading history...
3743
3744
		// Check for all occurrence if it is exception then modify the exception by setting up categories,
3745
		// Otherwise create new exception with categories.
3746
		foreach ($localCategories as $key => $value) {
3747
			if ($recurrence->isException($key)) {
3748
				$recurrence->modifyException(array($this->proptags['categories'] => $value), $key);
3749
			} else {
3750
				$recurrence->createException(array($this->proptags['categories'] => $value), $key, false);
3751
			}
3752
			mapi_savechanges($message);
3753
		}
3754
	}
3755
}
3756
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...
3757